{"asset_map": {"fishing_boat": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fishing_boat_e42f2566-55df-467e-a450-a77f5fbf3a78.webp", "type": "image", "prompt": "Generate a side view of a small, charming wooden fishing boat floating on water. Cartoon style with soft outlines and bright colors. Light brown wood texture with a distinct red stripe along the hull. Include a simple mast or rod holder. Friendly and inviting aesthetic, professional game asset, high resolution, transparent background.", "description": "A charming wooden fishing boat with a red stripe, viewed from the side.", "subtype": "object", "aspect_ratio": [1266, 904], "compression_suggestion": "low", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fishing_hook": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fishing_hook_b63060f1-bd61-4368-8b99-04f54663efa6.webp", "type": "image", "prompt": "Generate a classic silver fishing hook with a small, wiggly pinkish-red worm attached as bait. Shiny metallic texture, cartoon style with soft outlines. Side view, simple and clear design, high resolution, professional game asset, transparent background.", "description": "A shiny silver fishing hook with a small red worm bait attached.", "subtype": "object", "aspect_ratio": [653, 815], "compression_suggestion": "low", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_bluegill": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_bluegill_4ce0a4b0-e1b1-4a93-bba3-a88b6c5c573a.webp", "type": "image", "prompt": "Generate a small bluegill fish, side view facing left. Bright blue-green body with a yellow belly, visible scales pattern, and a large friendly eye. Bright, cheerful cartoon style with soft outlines and clearly defined fins. High quality game illustration, transparent background.", "description": "Common bluegill fish with blue-green body and yellow belly.", "subtype": "character", "aspect_ratio": [1167, 912], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_perch": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_perch_1b595ed5-f257-4da8-93bc-9acfb1f4642c.webp", "type": "image", "prompt": "Generate a medium-sized perch fish, side view facing left. Yellow-orange body with dark vertical stripes, visible scales, and a prominent dorsal fin. Match the cartoon art style and soft outlines of fish_bluegill. Friendly appearance, high quality game asset, transparent background.", "description": "Medium-sized perch fish with yellow-orange body and dark stripes.", "subtype": "character", "aspect_ratio": [1067, 830], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_bass": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_bass_a4e11ba7-045e-46e2-ab1c-a538ec8b2b88.webp", "type": "image", "prompt": "Generate a large bass fish, side view facing left. Dark green body with a lighter green belly, subtle scale pattern, and a large mouth. Match the cartoon art style of fish_bluegill and fish_perch. Impressive presence, slightly aggressive but friendly look, soft outlines, high resolution, transparent background.", "description": "Large green bass fish with a prominent mouth and defined fins.", "subtype": "character", "aspect_ratio": [1086, 800], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_golden_carp": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_golden_carp_3b9356e2-bc79-4b96-b178-a0f4621ab4e0.webp", "type": "image", "prompt": "Generate a beautiful golden carp fish, side view facing left. Shimmering gold and yellow body with orange accents, elegant flowing fins, and large scales with a subtle sparkle effect. Premium rare appearance, matching the cartoon style of fish_bluegill. High quality, detailed game asset, transparent background.", "description": "Rare golden carp with shimmering scales and elegant fins.", "subtype": "character", "aspect_ratio": [1143, 809], "compression_suggestion": "low", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "obstacle_boot": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/obstacle_boot_73f06664-9cab-4ae2-988e-31702cab2477.webp", "type": "image", "prompt": "Generate an old, worn leather boot, profile side view. Cartoon style with soft outlines. Brown and tan leather with a slight green underwater tinge, visible stitching, and small barnacles. Weathered and aged appearance, high quality game asset, transparent background.", "description": "Weathered underwater leather boot obstacle.", "subtype": "object", "aspect_ratio": [983, 940], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "obstacle_tin_can": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/obstacle_tin_can_12bd5067-ca4a-42fe-9dac-bcf118e0df5c.webp", "type": "image", "prompt": "Generate a rusty, dented tin can, side view showing cylindrical shape. Cartoon style with soft outlines. Dented metal surface with orange-brown rust spots and faded label remnants. High resolution, professional game asset, transparent background.", "description": "Rusty, dented tin can underwater obstacle.", "subtype": "object", "aspect_ratio": [689, 944], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "seaweed_patch": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/seaweed_patch_a04d69c9-983e-4596-95fc-4c8677ffaca0.webp", "type": "image", "prompt": "Generate a cluster of underwater seaweed kelp. Cartoon style with soft outlines. Dark green with lighter green highlights, flowing organic shapes suggesting a gentle swaying motion. Vertical orientation, professional game asset, transparent background.", "description": "Cluster of dark green swaying seaweed.", "subtype": "ground", "aspect_ratio": [828, 1457], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "water_surface": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/water_surface_8f35f13e-87cf-4c58-88a1-d6f3ab644c09.webp", "type": "image", "prompt": "Generate a horizontally tiling water surface texture, side-view hybrid showing the boundary between air and water. Light blue water with gentle ripples, wave patterns, and subtle foam. Cartoon style, designed to loop seamlessly from left to right. Top half transparent (air), bottom half water.", "description": "Horizontally tiling water surface with gentle ripples.", "subtype": "background", "aspect_ratio": [1536, 1024], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "sandy_bottom": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/sandy_bottom_9fe15a1a-bf92-4339-bfc7-3cc94bb434f4.webp", "type": "image", "prompt": "Generate a horizontally tiling underwater sandy lake bottom texture. Tan and beige sand with scattered darker brown rocks, pebbles, and small shells. Cartoon style with soft outlines, top-down view, designed to repeat seamlessly in horizontal direction. High quality terrain texture.", "description": "Horizontally tiling sandy lake bottom with rocks.", "subtype": "ground", "aspect_ratio": [1536, 1024], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "splash_effect": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/splash_effect_73b54e6c-94bc-4508-9603-176ad6cbe473.webp", "type": "image", "prompt": "Generate a dynamic water splash effect. Light blue water droplets and spray radiating outward from a central impact point. Cartoon style with soft outlines, energetic and cheerful appearance. Single frame illustration, transparent background.", "description": "Dynamic light blue water splash effect.", "subtype": "effect", "aspect_ratio": [1266, 901], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_clownfish": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_clownfish_f354c146-2caa-47d6-83c1-e6ba0061914e.webp", "type": "image", "prompt": "Generate a side-view illustration of a cute clownfish with a bright orange body and distinct white stripes outlined in black. Clean cartoon style, polished game asset, high resolution with sharp edges, side view, right-facing character, transparent background.", "description": "A colorful cartoon clownfish with orange and white stripes for the fishing game.", "subtype": "character", "aspect_ratio": [1245, 903], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_swordfish": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_swordfish_d5286819-bd15-496f-8296-1d3d9303a760.webp", "type": "image", "prompt": "Generate a sleek blue-silver swordfish with a long pointed bill in a dynamic swimming pose. 2D side view, cartoon style, clean polished game-ready asset, transparent background. Use the same artistic style and level of detail as fish_clownfish and fish_bass. Professional quality, high resolution.", "description": "A sleek blue-silver swordfish with a long bill, designed as a medium-large catchable fish.", "subtype": "character", "aspect_ratio": [1248, 618], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_pufferfish": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_pufferfish_625963b7-6f48-4039-ad18-3a94ec20fc2f.webp", "type": "image", "prompt": "Generate a cute round pufferfish, slightly inflated, with yellow-green skin and dark spots. Side view, right-facing, standing illustration. Stylized cartoon game art, clean lines, high resolution, transparent background. Match the artistic style of fish_clownfish and fish_bluegill.", "description": "A cute, slightly inflated yellow-green pufferfish for the 2D fishing game.", "subtype": "character", "aspect_ratio": [1086, 724], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_jellyfish": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_jellyfish_b4814aca-dfe5-4f6a-90b8-bc6a40422bc6.webp", "type": "image", "prompt": "Generate a translucent purple-pink jellyfish with flowing tentacles, front-side view, cartoon style, transparent background. High resolution, detailed, professional quality game asset. The jellyfish should have a glowing ethereal look with soft lighting effects. Side view to match the existing fish assets, transparent background.", "description": "A translucent purple-pink glowing jellyfish with flowing tentacles for the underwater scene.", "subtype": "character", "aspect_ratio": [614, 1192], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_seahorse": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_seahorse_ae4f24c7-7437-4c80-828d-264b5faee30e.webp", "type": "image", "prompt": "Generate a vibrant orange-yellow seahorse, side view, right-facing, standing illustration, cartoon style, transparent background. Features a gracefully curled tail and elegant fins. Polished, clean, game-ready asset for a 2D fishing game, matching the artistic style of the existing fish assets.", "description": "A vibrant orange-yellow seahorse with a curled tail for the fishing game.", "subtype": "character", "aspect_ratio": [439, 827], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_hammerhead": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_hammerhead_55e5e623-3c8f-47f2-8d88-b4d3cba517cc.webp", "type": "image", "prompt": "Generate a side view of a grey hammerhead shark with its distinctive wide head and dorsal fin, cartoon style to match existing fish assets, professional game-ready asset, high resolution, detailed scales and fins, intimidating but stylized appearance, transparent background.", "description": "A large, intimidating grey hammerhead shark for the fishing game.", "subtype": "character", "aspect_ratio": [1219, 538], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "treasure_chest": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/treasure_chest_575455fe-cdc8-4b02-819a-a43059ce6a95.webp", "type": "image", "prompt": "Generate a 2D cartoon style underwater treasure chest. The chest is made of golden-yellow metal and dark wood planks with iron rivets and a large central lock. It is slightly ajar, with bright golden light rays spilling out from the crack. Add weathered details like small barnacles on the corners. Professional game asset, vibrant colors, clean lines, side-perspective suitable for a 2D fishing game, transparent background.", "description": "A glowing golden treasure chest with barnacles, emitting light rays, for underwater discovery.", "subtype": "object", "aspect_ratio": [954, 909], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_soccerball": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_soccerball_f9b4e5fb-dab8-4650-ad96-dadd62695a96.webp", "type": "image", "prompt": "Generate a 2D game sprite of a whimsical cartoon fish designed like a soccer ball. The round body features a classic black and white pentagon and hexagon football pattern. Include small fins, a cute tail, and playful googly eyes. Side view, right-facing, vibrant colors, clean polished game-ready asset, professional quality, transparent background.", "description": "A whimsical soccer-themed fish with a black and white pentagon pattern and googly eyes.", "subtype": "character", "aspect_ratio": [929, 821], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_trophy": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_trophy_34f4bc8e-cc2c-4038-abc9-6475ba4e257e.webp", "type": "image", "prompt": "Generate a legendary golden World Cup trophy fish for a 2D fishing game. The fish's body is shaped and colored like the FIFA World Cup trophy, featuring shimmering solid gold textures and gleaming surfaces. It has ornate, filigree-style golden fins, a crown-like dorsal fin, and small magical sparkles surrounding it. Side-view, right-facing character, professional 2D game sprite style, clean edges, transparent background, epic and prestigious aesthetic.", "description": "A legendary golden World Cup trophy fish, appearing as a rare and prestigious boss catch.", "subtype": "character", "aspect_ratio": [811, 915], "compression_suggestion": "low", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "obstacle_soccerball": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/obstacle_soccerball_4d90e4c1-cf6c-4515-aa50-bcb15a8ccfa3.webp", "type": "image", "prompt": "Generate a 2D game sprite of a classic black and white soccer ball floating underwater. The ball should look slightly worn and waterlogged, with small decorative air bubbles rising around it. Side view, clean lines, polished game-ready asset. Match the playful cartoon art style of fish_soccerball. Transparent background.", "description": "A waterlogged soccer ball obstacle for the underwater fishing game.", "subtype": "object", "aspect_ratio": [697, 757], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_jersey": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_jersey_0a307be3-946e-4e3f-8e57-dc0706f86cc7.webp", "type": "image", "prompt": "Generate a side-view 2D cartoon game sprite of a colorful tropical fish wearing a tiny red and blue striped soccer jersey with the number 10 visible on its side. The fish should have vibrant colors, expressive eyes, and a playful personality. High-quality game asset, professional digital art, transparent background, side-facing to the right.", "description": "A whimsical tropical fish wearing a soccer jersey, used as a catchable thematic fish.", "subtype": "character", "aspect_ratio": [1048, 911], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_lanternfish": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_lanternfish_82a2d811-5a8e-42d1-9e18-db115818fe24.webp", "type": "image", "prompt": "Generate a side view, right-facing cartoon illustration of a deep-sea lantern fish. It has a dark, slightly rounded body with glowing blue-green bioluminescent spots and a prominent glowing lure dangling from its head. The lure should have a vibrant blue-green glow effect. Professional 2D game asset style, clean outlines, high resolution, transparent background.", "description": "A bioluminescent deep-sea angler fish with a glowing lure, designed as a catchable fish.", "subtype": "character", "aspect_ratio": [1262, 815], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_firefly_squid": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_firefly_squid_881f9bfc-95bf-433f-8b28-7b6c4038ce45.webp", "type": "image", "prompt": "Generate a magical glowing firefly squid, side view, facing right. The creature has a translucent purple-blue body with bright bioluminescent spots scattered across its tentacles and mantle. Clean cartoon style, 2D game asset, professional quality. Ethereal glow effect, vibrant neon highlights. High resolution, transparent background.", "description": "A rare, bioluminescent firefly squid with an ethereal purple-blue glow for the fishing game.", "subtype": "character", "aspect_ratio": [844, 562], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "moon_pearl": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/moon_pearl_2653fe78-0648-4a17-a8f8-28d34579f94b.webp", "type": "image", "prompt": "Generate a luminous moon pearl nestled inside a half-open oyster shell. The pearl should emit a soft white-blue moonlight glow with tiny magical sparkles. Clean cartoon style, side view, high resolution, professional game asset quality with a transparent background. Ensure the lighting matches the bioluminescent aesthetic of existing fish like the lanternfish and firefly squid.", "description": "A rare collectible moon pearl inside a glowing oyster shell for the underwater game.", "subtype": "object", "aspect_ratio": [702, 765], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_ember_guppy": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_ember_guppy_2774a531-2047-48d7-a1bc-6da6bf76a9f1.webp", "type": "image", "prompt": "Generate a side-view, right-facing cartoon Ember Guppy fish for a 2D fishing game. The fish has a chubby body with a deep orange-to-red gradient and glowing yellow-white ember speckles. It features a flowing fan tail that looks like a flame, fading from red to bright yellow. Use a bright, friendly vector illustration style with clean bold outlines and soft cel shading, matching the style of fish_bluegill, fish_clownfish, and fish_pufferfish. Include a faint warm inner glow around the fins. Transparent background, high resolution, professional game asset.", "description": "A small molten-themed Ember Guppy fish with glowing speckles and flame-like fins for the underwater fishing scene.", "subtype": "character", "aspect_ratio": [1107, 753], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_magma_ray": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_magma_ray_5922ccc2-c331-4294-bb95-5bf338155dfe.webp", "type": "image", "prompt": "Generate a sleek 'Magma Ray' fish for a 2D fishing game. The fish is a manta ray with dark charcoal basalt skin featuring glowing cracks of molten orange-red lava along its wings and spine. Long thin whip tail with a glowing tip, small glowing amber eyes, and a subtle heat-glow effect. Style: Bright, friendly cartoon vector illustration with bold clean outlines and soft cel shading, matching the style of fish_hammerhead and fish_swordfish. Side three-quarter view, facing RIGHT. Transparent background, high resolution, professional game asset.", "description": "A rare lava-themed manta ray fish with charcoal skin and glowing orange cracks for the deep-water scene.", "subtype": "character", "aspect_ratio": [1132, 564], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "fish_obsidian_kraken": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/fish_obsidian_kraken_23ad4a03-b9ac-4356-ad10-b0dcad0e2a27.webp", "type": "image", "prompt": "Generate a legendary boss 'Obsidian Kraken' for a 2D fishing game, side view facing RIGHT. The creature has a glossy black obsidian body with glowing molten orange-red lava veins cracking across its mantle and tentacles. Large bulbous head, two glowing yellow-orange eyes, and thick curling tentacles trailing to the left with glowing suckers and tiny ember sparks. Bright cartoon vector illustration, bold clean outlines, dramatic cel shading, high contrast to stand out against dark blue backgrounds. Full body standing illustration, transparent background.", "description": "A legendary boss Kraken with a black obsidian body and molten lava cracks for the volcanic trench zone.", "subtype": "character", "aspect_ratio": [1192, 719], "compression_suggestion": "medium", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "hazard_lava_bubble": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/hazard_lava_bubble_48d449b2-3d73-4cb5-93af-3aa5c87f61b1.webp", "type": "image", "prompt": "Generate a high-quality 2D cartoon vector game asset of a molten lava bubble. The subject is a spherical blob of glowing molten lava with a bright yellow-white core fading into orange and deep red edges, featuring small dark crusty patches on the surface. Include tiny ember specks and a subtle steam wisp at the top. Bold clean outlines, bright glowing highlights, and a strong internal heat glow. Front-facing, symmetrical circular composition, transparent background, style matched to existing cartoon game obstacles.", "description": "A rising molten lava bubble hazard sprite that players must dodge.", "subtype": "object", "aspect_ratio": [850, 849], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}, "obsidian_gem": {"url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/obsidian_gem_d72e83a2-96ec-4500-8de0-02e861bdf47a.webp", "type": "image", "prompt": "Generate a game-ready collectible icon of a faceted obsidian gem. The gem is glossy black-purple volcanic glass with glowing molten orange-red light trapped inside internal cracks, resting on a small chunk of dark porous volcanic rock. Use a bright cartoon vector illustration style with bold clean outlines, faceted shading, and sharp white sparkle glints. Three-quarter front view, high resolution, detailed, transparent background. Stylistically match the treasure_chest and moon_pearl assets.", "description": "A rare collectible obsidian crystal shard with molten cracks for the volcanic trench area.", "subtype": "object", "aspect_ratio": [690, 933], "compression_suggestion": "high", "duration_secs": null, "loop": null, "opus_url": null, "animation": null}}, "game_code": "<!DOCTYPE html>\n<html>\n<head>\n    <title>Deep Blue Dragger</title>\n    <link href=\"https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n    <style>\n        :root {\n            --transition-fast: 150ms;\n            --transition-normal: 300ms;\n            --transition-slow: 500ms;\n            --ease-out: cubic-bezier(0.0, 0, 0.2, 1);\n            --ease-bounce: cubic-bezier(0.68, -0.6, 0.32, 1.6);\n            --space-xs: 8px;\n            --space-sm: 16px;\n            --space-md: 24px;\n            --space-lg: 32px;\n            --space-xl: 48px;\n            --color-primary: #4FC3F7;\n            --color-primary-dark: #0288D1;\n            --color-accent: #FFD54F;\n            --color-danger: #EF5350;\n            --color-success: #66BB6A;\n            --color-panel: rgba(255, 255, 255, 0.95);\n            --color-panel-dark: rgba(30, 58, 95, 0.9);\n            --radius: 16px;\n            --shadow: 0 4px 16px rgba(0,0,0,0.2);\n        }\n\n        body, html {\n            margin: 0;\n            padding: 0;\n            width: 100%;\n            height: 100%;\n            overflow: hidden;\n            touch-action: none;\n            -ms-touch-action: none;\n            font-family: 'Inter', sans-serif;\n        }\n\n        #game-world {\n            position: relative;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(180deg, #87CEEB 0%, #4A90D9 30%, #1E3A5F 100%);\n        }\n\n        canvas {\n            display: block;\n            width: 100%;\n            height: 100%;\n        }\n\n        .ui-overlay {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            pointer-events: none;\n            z-index: 10;\n        }\n\n        .ui-overlay > * {\n            pointer-events: auto;\n        }\n\n        .hud {\n            position: absolute;\n            top: 16px;\n            left: 16px;\n            right: 16px;\n            display: flex;\n            justify-content: space-between;\n            align-items: flex-start;\n        }\n\n        .hud-item {\n            background: var(--color-panel);\n            border-radius: 12px;\n            padding: 8px 16px;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n            box-shadow: var(--shadow);\n            font-family: 'Fredoka', sans-serif;\n            font-size: 24px;\n            font-weight: 600;\n            color: #1E3A5F;\n        }\n\n        .hud-coins .coin-icon {\n            width: 28px;\n            height: 28px;\n            background: #FFD54F;\n            border-radius: 50%;\n            border: 3px solid #F9A825;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 16px;\n        }\n\n        .depth-meter {\n            position: absolute;\n            right: 16px;\n            top: 80px;\n            bottom: 200px;\n            width: 40px;\n            background: rgba(255,255,255,0.2);\n            border-radius: 20px;\n            border: 2px solid rgba(255,255,255,0.4);\n            overflow: hidden;\n        }\n\n        .depth-fill {\n            position: absolute;\n            bottom: 0;\n            left: 0;\n            right: 0;\n            background: linear-gradient(180deg, #4FC3F7, #1E3A5F);\n            border-radius: 20px;\n            transition: height 0.3s ease;\n        }\n\n        .depth-label {\n            position: absolute;\n            bottom: -30px;\n            left: 50%;\n            transform: translateX(-50%);\n            font-family: 'Fredoka', sans-serif;\n            font-size: 18px;\n            color: white;\n            text-shadow: 0 2px 4px rgba(0,0,0,0.5);\n            white-space: nowrap;\n        }\n\n        .btn {\n            background: var(--color-primary);\n            color: white;\n            border: none;\n            border-radius: 12px;\n            padding: 12px 24px;\n            font-family: 'Fredoka', sans-serif;\n            font-size: 24px;\n            font-weight: 600;\n            cursor: pointer;\n            box-shadow: 0 4px 0 var(--color-primary-dark), var(--shadow);\n            transition: transform var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out);\n        }\n\n        .btn:active {\n            transform: translateY(3px);\n            box-shadow: 0 1px 0 var(--color-primary-dark);\n        }\n\n        .btn:hover {\n            transform: translateY(-3px) scale(1.03);\n            filter: brightness(1.08);\n        }\n\n        .hud-item {\n            animation: hudFloat 3.2s ease-in-out infinite;\n        }\n\n        .hud-coins .coin-icon {\n            animation: coinSpin 2.8s ease-in-out infinite;\n        }\n\n        .shop-panel.show .shop-content {\n            animation: panelPop 420ms var(--ease-bounce) both;\n        }\n\n        .catch-popup.show {\n            animation: catchBounce 700ms var(--ease-bounce) both;\n        }\n\n        @keyframes hudFloat {\n            0%, 100% { transform: translateY(0); }\n            50% { transform: translateY(-4px); }\n        }\n\n        @keyframes coinSpin {\n            0%, 70%, 100% { transform: rotateY(0deg) scale(1); }\n            84% { transform: rotateY(180deg) scale(1.12); }\n        }\n\n        @keyframes panelPop {\n            0% { opacity: 0; transform: translateY(28px) scale(0.92); }\n            100% { opacity: 1; transform: translateY(0) scale(1); }\n        }\n\n        @keyframes catchBounce {\n            0% { transform: translate(-50%, -50%) scale(0) rotate(-8deg); }\n            55% { transform: translate(-50%, -50%) scale(1.12) rotate(2deg); }\n            100% { transform: translate(-50%, -50%) scale(1) rotate(0); }\n        }\n\n        @keyframes journalRowIn {\n            0% { opacity: 0; transform: translateX(-28px) scale(0.96); }\n            70% { transform: translateX(5px) scale(1.01); }\n            100% { opacity: 1; transform: translateX(0) scale(1); }\n        }\n\n        @keyframes journalFloat {\n            0%, 100% { transform: translateY(0) rotate(-2deg); }\n            50% { transform: translateY(-5px) rotate(2deg); }\n        }\n\n        @keyframes journalShimmer {\n            0%, 100% { box-shadow: 0 2px 0 rgba(30,58,95,0.04), 0 0 0 rgba(79,195,247,0); }\n            50% { box-shadow: 0 5px 18px rgba(79,195,247,0.24), 0 0 12px rgba(255,213,79,0.16); }\n        }\n\n        @keyframes modalFadeIn {\n            from { opacity: 0; }\n            to { opacity: 1; }\n        }\n\n        @keyframes shopRowIn {\n            0% { opacity: 0; transform: translateY(24px) scale(0.94); }\n            65% { transform: translateY(-4px) scale(1.015); }\n            100% { opacity: 1; transform: translateY(0) scale(1); }\n        }\n\n        @keyframes shopCostPulse {\n            0%, 100% { transform: scale(1); text-shadow: 0 0 0 rgba(249,168,37,0); }\n            50% { transform: scale(1.08); text-shadow: 0 0 12px rgba(249,168,37,0.55); }\n        }\n\n        @keyframes shopButtonGlow {\n            0%, 100% { box-shadow: 0 3px 0 #388E3C, 0 0 0 rgba(102,187,106,0); }\n            50% { box-shadow: 0 3px 0 #388E3C, 0 0 14px rgba(102,187,106,0.65); }\n        }\n\n        .shop-panel.show {\n            animation: modalFadeIn 220ms ease-out both;\n        }\n\n        #shop-panel.show .upgrade-item {\n            animation: shopRowIn 480ms var(--ease-bounce) both;\n        }\n\n        #shop-panel.show .upgrade-item:nth-child(1) { animation-delay: 80ms; }\n        #shop-panel.show .upgrade-item:nth-child(2) { animation-delay: 170ms; }\n        #shop-panel.show .upgrade-item:nth-child(3) { animation-delay: 260ms; }\n\n        #shop-panel.show .upgrade-cost {\n            animation: shopCostPulse 1.8s ease-in-out infinite;\n        }\n\n        #shop-panel.show .btn-upgrade:not(:disabled) {\n            animation: shopButtonGlow 1.7s ease-in-out infinite;\n            animation-delay: 420ms;\n        }\n\n        #shop-panel.show .shop-content h2 {\n            animation: titleBreathe 2.2s ease-in-out 500ms infinite;\n        }\n\n        @keyframes titleBreathe {\n            0%, 100% { transform: scale(1); }\n            50% { transform: scale(1.035); }\n        }\n\n        .btn, .upgrade-item, .leaderboard-entry {\n\n            transition: transform var(--transition-fast) var(--ease-out), filter var(--transition-fast) var(--ease-out), box-shadow var(--transition-fast) var(--ease-out);\n        }\n\n        .upgrade-item:hover, .leaderboard-entry:hover {\n            transform: translateX(4px);\n            box-shadow: 0 6px 18px rgba(30, 58, 95, 0.16);\n        }\n\n        @media (prefers-reduced-motion: reduce) {\n            *, *::before, *::after { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }\n        }\n\n        /* Halloween 2026 seasonal world and thumb-friendly event UI */\n        .halloween-menu {\n            position: absolute; inset: 0; z-index: 180; display: flex; flex-direction: column;\n            align-items: center; justify-content: center; padding: 36px; box-sizing: border-box;\n            text-align: center; color: #fff; overflow: hidden;\n            background: radial-gradient(ellipse at 50% 42%, rgba(91,35,130,.45), rgba(4,10,35,.82) 64%, rgba(2,7,24,.92));\n            backdrop-filter: blur(2px);\n        }\n        .halloween-menu.hidden { display: none; }\n        .halloween-menu h1 { font: 700 62px/1.05 'Fredoka', sans-serif; margin: 12px 0; text-shadow: 0 4px 0 #38105c, 0 0 28px rgba(255,120,40,.8); }\n        .halloween-menu .halloween-subtitle { font: 600 25px 'Fredoka', sans-serif; color: #ffd59b; letter-spacing: 3px; text-shadow: 0 2px 8px #160b36; }\n        .halloween-menu .halloween-copy { max-width: 560px; font-size: 22px; line-height: 1.45; color: #eee4ff; margin: 14px 0 26px; }\n        .halloween-menu .menu-pumpkins { font-size: 74px; letter-spacing: 18px; filter: drop-shadow(0 0 18px #ff762e); animation: pumpkinFloat 2.8s ease-in-out infinite; }\n        .halloween-menu .btn { min-width: 340px; min-height: 96px; margin: 8px; background: #ff852e; box-shadow: 0 6px 0 #a93d18, 0 0 24px rgba(255,120,40,.4); font-size: 30px; }\n        .halloween-menu .btn-event { background: #8746bd; box-shadow: 0 6px 0 #4e2479, 0 0 20px rgba(165,103,255,.35); }\n        .halloween-menu .menu-progress { margin-top: 12px; padding: 12px 20px; border-radius: 18px; background: rgba(18,12,50,.72); border: 1px solid rgba(214,178,255,.45); font: 600 22px 'Fredoka', sans-serif; color: #ffe0a8; }\n        @keyframes pumpkinFloat { 0%,100% { transform: translateY(0) rotate(-2deg); } 50% { transform: translateY(-10px) rotate(2deg); } }\n        #halloween-panel { z-index: 260; }\n        #halloween-panel .shop-content { background: linear-gradient(155deg,#21183e,#101c3d); border: 3px solid #a56be0; color: #fff; }\n        #halloween-panel h2 { color: #ffe1a6; text-shadow: 0 0 14px rgba(255,130,54,.5); }\n        .event-summary { text-align: center; font: 600 22px/1.4 'Fredoka',sans-serif; color: #e4d3ff; margin: -8px 0 18px; }\n        .event-mission { display: flex; align-items: center; gap: 14px; margin: 12px 0; padding: 16px; min-height: 88px; border-radius: 16px; background: rgba(83,67,132,.55); border: 1px solid rgba(209,177,255,.35); }\n        .event-mission-info { flex: 1; min-width: 0; }\n        .event-mission-info strong { display:block; font: 600 22px 'Fredoka',sans-serif; color:#fff0ce; }\n        .event-mission-info span { display:block; font-size:17px; color:#e0d9f4; margin-top:4px; }\n        .event-progress-track { height: 10px; margin-top: 9px; border-radius: 8px; background: rgba(0,0,0,.35); overflow:hidden; }\n        .event-progress-fill { height:100%; border-radius:inherit; background:linear-gradient(90deg,#a565e6,#ff9b43); }\n        .event-claim { min-width: 106px; min-height: 56px; border:0; border-radius:12px; background:#ff9b43; color:#2b1644; font:700 19px 'Fredoka',sans-serif; }\n        .event-claim:disabled { background:#665a78; color:#d7cee1; }\n        .event-collection { text-align:center; margin-top:16px; padding:15px; border-radius:16px; background:rgba(255,137,58,.13); color:#ffdba8; font:600 20px/1.5 'Fredoka',sans-serif; }\n        .halloween-event-btn { position:absolute; top:88px; right:16px; min-height:56px; padding:10px 18px; background:#8144b5; box-shadow:0 4px 0 #4e2479,var(--shadow); }\n        @media (max-width: 560px) {\n            .halloween-menu { padding: 20px; }\n            .halloween-menu h1 { font-size: 48px; }\n            .halloween-menu .halloween-copy { font-size: 19px; }\n            .halloween-menu .btn { min-width: min(340px, 90vw); }\n            .event-mission { align-items:flex-start; flex-wrap:wrap; }\n            .event-claim { margin-left:auto; }\n        }\n\n        /* Halloween 2026 seasonal world and thumb-friendly event UI */\n        .halloween-menu { position:absolute; inset:0; z-index:180; display:flex; flex-direction:column; align-items:center; justify-content:center; padding:36px; box-sizing:border-box; text-align:center; color:#fff; overflow:hidden; background:radial-gradient(ellipse at 50% 42%,rgba(91,35,130,.45),rgba(4,10,35,.82) 64%,rgba(2,7,24,.92)); backdrop-filter:blur(2px); }\n        .halloween-menu.hidden { display:none; }\n        .halloween-menu h1 { font:700 62px/1.05 'Fredoka',sans-serif; margin:12px 0; text-shadow:0 4px 0 #38105c,0 0 28px rgba(255,120,40,.8); }\n        .halloween-menu .halloween-subtitle { font:600 25px 'Fredoka',sans-serif; color:#ffd59b; letter-spacing:3px; text-shadow:0 2px 8px #160b36; }\n        .halloween-menu .halloween-copy { max-width:560px; font-size:22px; line-height:1.45; color:#eee4ff; margin:14px 0 26px; }\n        .halloween-menu .menu-pumpkins { font-size:74px; letter-spacing:18px; filter:drop-shadow(0 0 18px #ff762e); animation:pumpkinFloat 2.8s ease-in-out infinite; }\n        .halloween-menu .btn { min-width:340px; min-height:96px; margin:8px; background:#ff852e; box-shadow:0 6px 0 #a93d18,0 0 24px rgba(255,120,40,.4); font-size:30px; }\n        .halloween-menu .btn-event { background:#8746bd; box-shadow:0 6px 0 #4e2479,0 0 20px rgba(165,103,255,.35); }\n        .halloween-menu .menu-progress { margin-top:12px; padding:12px 20px; border-radius:18px; background:rgba(18,12,50,.72); border:1px solid rgba(214,178,255,.45); font:600 22px 'Fredoka',sans-serif; color:#ffe0a8; }\n        @keyframes pumpkinFloat { 0%,100% { transform:translateY(0) rotate(-2deg); } 50% { transform:translateY(-10px) rotate(2deg); } }\n        #halloween-panel { z-index:260; }\n        #halloween-panel .shop-content { background:linear-gradient(155deg,#21183e,#101c3d); border:3px solid #a56be0; color:#fff; }\n        #halloween-panel h2 { color:#ffe1a6; text-shadow:0 0 14px rgba(255,130,54,.5); }\n        .event-summary { text-align:center; font:600 22px/1.4 'Fredoka',sans-serif; color:#e4d3ff; margin:-8px 0 18px; }\n        .event-mission { display:flex; align-items:center; gap:14px; margin:12px 0; padding:16px; min-height:88px; border-radius:16px; background:rgba(83,67,132,.55); border:1px solid rgba(209,177,255,.35); }\n        .event-mission-info { flex:1; min-width:0; }\n        .event-mission-info strong { display:block; font:600 22px 'Fredoka',sans-serif; color:#fff0ce; }\n        .event-mission-info span { display:block; font-size:17px; color:#e0d9f4; margin-top:4px; }\n        .event-progress-track { height:10px; margin-top:9px; border-radius:8px; background:rgba(0,0,0,.35); overflow:hidden; }\n        .event-progress-fill { height:100%; border-radius:inherit; background:linear-gradient(90deg,#a565e6,#ff9b43); }\n        .event-claim { min-width:106px; min-height:56px; border:0; border-radius:12px; background:#ff9b43; color:#2b1644; font:700 19px 'Fredoka',sans-serif; }\n        .event-claim:disabled { background:#665a78; color:#d7cee1; }\n        .event-collection { text-align:center; margin-top:16px; padding:15px; border-radius:16px; background:rgba(255,137,58,.13); color:#ffdba8; font:600 20px/1.5 'Fredoka',sans-serif; }\n        .halloween-event-btn { position:absolute; top:88px; right:16px; min-height:56px; padding:10px 18px; background:#8144b5; box-shadow:0 4px 0 #4e2479,var(--shadow); }\n        @media (max-width:560px) { .halloween-menu { padding:20px; } .halloween-menu h1 { font-size:48px; } .halloween-menu .halloween-copy { font-size:19px; } .halloween-menu .btn { min-width:min(340px,90vw); } .event-mission { align-items:flex-start; flex-wrap:wrap; } .event-claim { margin-left:auto; } }\n\n        .btn-accent {\n            background: var(--color-accent);\n            color: #5D4037;\n            box-shadow: 0 4px 0 #F9A825, var(--shadow);\n        }\n\n        .btn-accent:active {\n            box-shadow: 0 1px 0 #F9A825;\n        }\n\n        .shop-btn {\n            position: absolute;\n            top: 16px;\n            right: 16px;\n        }\n\n        .cast-prompt {\n            position: absolute;\n            bottom: 200px;\n            left: 50%;\n            transform: translateX(-50%);\n            font-family: 'Fredoka', sans-serif;\n            font-size: 32px;\n            color: white;\n            text-shadow: 0 2px 8px rgba(0,0,0,0.5);\n            animation: pulse 1.5s ease-in-out infinite;\n            text-align: center;\n        }\n\n        @keyframes pulse {\n            0%, 100% { opacity: 1; transform: translateX(-50%) scale(1); }\n            50% { opacity: 0.7; transform: translateX(-50%) scale(0.95); }\n        }\n\n        .reel-bar-container {\n            position: absolute;\n            bottom: 160px;\n            left: 50%;\n            transform: translateX(-50%);\n            width: 400px;\n            height: 48px;\n            background: rgba(0,0,0,0.5);\n            border-radius: 24px;\n            border: 3px solid white;\n            overflow: hidden;\n            display: none;\n        }\n\n        .reel-bar-fill {\n            height: 100%;\n            background: linear-gradient(90deg, var(--color-success), #81C784);\n            border-radius: 24px;\n            transition: width 0.1s ease;\n        }\n\n        .reel-text {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            font-family: 'Fredoka', sans-serif;\n            font-size: 22px;\n            color: white;\n            text-shadow: 0 1px 3px rgba(0,0,0,0.5);\n        }\n\n        .catch-popup {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%) scale(0);\n            background: var(--color-panel);\n            border-radius: 24px;\n            padding: 32px;\n            text-align: center;\n            box-shadow: var(--shadow);\n            z-index: 100;\n            transition: transform var(--transition-normal) var(--ease-bounce);\n        }\n\n        .catch-popup.show {\n            transform: translate(-50%, -50%) scale(1);\n        }\n\n        .catch-popup h2 {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 36px;\n            color: #1E3A5F;\n            margin: 0 0 8px;\n        }\n\n        .catch-popup .coins-earned {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 28px;\n            color: #F9A825;\n            margin: 8px 0 16px;\n        }\n\n        .shop-panel {\n            position: absolute;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            background: rgba(0,0,0,0.7);\n            display: none;\n            flex-direction: column;\n            align-items: center;\n            justify-content: center;\n            z-index: 200;\n        }\n\n        .shop-panel.show {\n            display: flex;\n        }\n\n        .shop-content {\n            background: #FFFFFF;\n            border: 3px solid rgba(79,195,247,0.55);\n            border-radius: 24px;\n            padding: 36px 32px 32px;\n            width: min(680px, calc(100% - 24px));\n            max-height: calc(100% - 40px);\n            overflow-y: auto;\n            box-sizing: border-box;\n            color: #17324D;\n            box-shadow: 0 18px 50px rgba(0,0,0,0.35);\n        }\n\n        /* High-contrast, thumb-friendly journal layout */\n        #journal-panel .shop-content {\n            width: min(660px, calc(100% - 24px));\n            padding: 28px 24px 32px;\n        }\n\n        #journal-panel h2 {\n            font-size: 44px;\n            color: #102A43;\n            letter-spacing: 0.2px;\n        }\n\n        #journal-summary {\n            color: #294B67 !important;\n            font-weight: 600;\n            font-size: 22px !important;\n            line-height: 1.35;\n        }\n\n        #journal-entries > div {\n            min-height: 86px;\n            padding: 16px !important;\n            border: 2px solid #B7C9D8 !important;\n            opacity: 1 !important;\n            transform-origin: left center;\n            animation: journalRowIn 420ms var(--ease-bounce) both;\n            animation-delay: calc(var(--journal-index, 0) * 45ms);\n            transition: transform 180ms var(--ease-out), box-shadow 180ms var(--ease-out), filter 180ms var(--ease-out);\n        }\n\n        #journal-entries > div:hover {\n            transform: translateX(6px) scale(1.015);\n            filter: brightness(1.03);\n            animation: journalShimmer 1.4s ease-in-out infinite;\n        }\n\n        #journal-entries > div > div:first-child {\n            animation: journalFloat 2.4s ease-in-out infinite;\n        }\n\n        #journal-entries > div:nth-child(3n) > div:first-child { animation-delay: 260ms; }\n        #journal-entries > div:nth-child(3n + 1) > div:first-child { animation-delay: 80ms; }\n\n        #journal-entries > div > div:last-child {\n            transition: transform 180ms var(--ease-out);\n        }\n\n        #journal-entries > div:hover > div:last-child {\n            transform: scale(1.12) rotate(-4deg);\n        }\n\n        #journal-entries > div > div:first-child {\n            font-size: 36px !important;\n            flex: 0 0 44px;\n            text-align: center;\n        }\n\n        #journal-entries > div > div:nth-child(2) > div:first-child {\n            font-size: 21px !important;\n            color: #102A43 !important;\n            line-height: 1.2;\n        }\n\n        #journal-entries > div > div:nth-child(2) > div:last-child {\n            font-size: 17px !important;\n            color: #355C78 !important;\n            line-height: 1.3;\n        }\n\n        #journal-entries > div > div:last-child {\n            font-size: 24px !important;\n            color: #176B45 !important;\n        }\n\n        #journal-entries > div[style*=\"opacity:0.5\"] {\n            background: #E8EEF3 !important;\n        }\n\n        .shop-content h2 {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 42px;\n            line-height: 1.1;\n            color: #102A43;\n            margin: 0 56px 28px 0;\n            text-align: center;\n        }\n\n        .upgrade-item {\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n            gap: 20px;\n            background: #F0F8FF;\n            border: 2px solid #C7E1F2;\n            border-radius: 16px;\n            padding: 20px 22px;\n            min-height: 92px;\n            margin-bottom: 16px;\n            transform-origin: center bottom;\n        }\n\n        .upgrade-info h3 {\n            font-family: 'Fredoka', sans-serif;\n            font-size: 24px;\n            color: #1E3A5F;\n            margin: 0;\n        }\n\n        .upgrade-info p {\n            font-size: 18px;\n            line-height: 1.3;\n            color: #294B67;\n            margin: 5px 0 4px;\n        }\n\n        .upgrade-cost {\n            display: inline-block;\n            font-family: 'Fredoka', sans-serif;\n            font-size: 23px;\n            font-weight: 700;\n            color: #A76100;\n        }\n\n        .btn-upgrade {\n            flex: 0 0 auto;\n            min-width: 104px;\n            min-height: 56px;\n            background: var(--color-success);\n            color: white;\n            border: none;\n            border-radius: 12px;\n            padding: 12px 20px;\n            font-family: 'Fredoka', sans-serif;\n            font-size: 22px;\n            font-weight: 700;\n            cursor: pointer;\n            box-shadow: 0 4px 0 #388E3C;\n        }\n\n        .btn-upgrade:disabled {\n            background: #B0BEC5;\n            box-shadow: 0 3px 0 #78909C;\n            cursor: not-allowed;\n        }\n\n        .btn-upgrade:active:not(:disabled) {\n            transform: translateY(2px) scale(0.96);\n            box-shadow: 0 1px 0 #388E3C;\n        }\n\n        .btn-upgrade:not(:disabled):hover {\n            transform: translateY(-3px) scale(1.06) rotate(-1deg);\n            filter: brightness(1.08);\n        }\n\n        .close-shop {\n            position: absolute;\n            top: 14px;\n            right: 14px;\n            z-index: 2;\n            width: 52px;\n            height: 52px;\n            background: white;\n            border: none;\n            border-radius: 50%;\n            font-size: 28px;\n            cursor: pointer;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            box-shadow: var(--shadow);\n        }\n    </style>\n</head>\n<body>\n    <div id=\"game-world\">\n        <canvas id=\"game-canvas\"></canvas>\n        <div class=\"ui-overlay\" id=\"ui-overlay\"></div>\n    </div>\n\n    <script>\n    /*\n     * DEEP BLUE DRAGGER\n     * A casual fishing arcade game where players cast their line,\n     * guide the hook through underwater obstacles, catch colorful fish,\n     * earn coins, and upgrade their equipment.\n     */\n    async function run(mode) {\n        const gameWorld = document.getElementById('game-world');\n        const canvas = document.getElementById('game-canvas');\n        const ctx = canvas.getContext('2d');\n        const uiOverlay = document.getElementById('ui-overlay');\n\n        function resizeCanvas() {\n            canvas.width = gameWorld.clientWidth;\n            canvas.height = gameWorld.clientHeight;\n        }\n        resizeCanvas();\n        window.addEventListener('resize', resizeCanvas);\n\n        const config = window.gameConfig;\n\n        // Asset loading\n        const assetCache = {};\n        const assetIds = [\n            'fishing_boat', 'fishing_hook', 'fish_bluegill', 'fish_perch',\n            'fish_bass', 'fish_golden_carp', 'fish_clownfish', 'fish_swordfish',\n            'fish_pufferfish', 'fish_jellyfish', 'fish_seahorse', 'fish_hammerhead',\n            'fish_soccerball', 'fish_trophy', 'fish_jersey',\n            'fish_lanternfish', 'fish_firefly_squid', 'moon_pearl',\n            'fish_ember_guppy', 'fish_magma_ray', 'fish_obsidian_kraken',\n            'hazard_lava_bubble', 'obsidian_gem',\n            'obstacle_boot', 'obstacle_tin_can', 'obstacle_soccerball',\n            'seaweed_patch', 'water_surface', 'sandy_bottom', 'splash_effect',\n            'treasure_chest'\n        ];\n\n        function loadImage(id) {\n            return new Promise((resolve) => {\n                const info = lib.getAsset(id);\n                if (!info) { resolve(null); return; }\n                const img = new Image();\n                img.crossOrigin = 'anonymous';\n                img.onload = () => { assetCache[id] = img; resolve(img); };\n                img.onerror = () => { resolve(null); };\n                img.src = info.url;\n            });\n        }\n\n        await Promise.all(assetIds.map(id => loadImage(id)));\n\n        // Audio context\n        let audioCtx = null;\n        function getAudioCtx() {\n            if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n            return audioCtx;\n        }\n\n        function playSfx(freq, duration, type = 'sine') {\n            try {\n                const ctx = getAudioCtx();\n                const osc = ctx.createOscillator();\n                const gain = ctx.createGain();\n                osc.type = type;\n                osc.frequency.setValueAtTime(freq, ctx.currentTime);\n                gain.gain.setValueAtTime(0.15, ctx.currentTime);\n                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);\n                osc.connect(gain);\n                gain.connect(ctx.destination);\n                osc.start();\n                osc.stop(ctx.currentTime + duration);\n            } catch(e) {}\n        }\n\n        function playCastSfx() { playSfx(600, 0.3, 'sine'); setTimeout(() => playSfx(300, 0.2, 'sine'), 100); }\n        function playCatchSfx() { playSfx(800, 0.1); setTimeout(() => playSfx(1000, 0.1), 100); setTimeout(() => playSfx(1200, 0.2), 200); }\n        function playReelSfx() { playSfx(400, 0.05, 'square'); }\n        function playBounceSfx() { playSfx(150, 0.2, 'triangle'); }\n        function playCoinSfx() { playSfx(1047, 0.1); setTimeout(() => playSfx(1319, 0.15), 80); }\n        function playSuccessSfx() { [523,659,784,1047].forEach((f,i) => setTimeout(() => playSfx(f, 0.2), i*80)); }\n\n        // Show game parameters\n        lib.showGameParameters({\n            name: 'Deep Blue Dragger',\n            params: {\n                'Water Color': { key: 'settings.waterColor', type: 'dropdown', options: [\n                    { label: 'Light Blue', value: 'light_blue' },\n                    { label: 'Dark Blue', value: 'dark_blue' },\n                    { label: 'Green', value: 'green' },\n                    { label: 'Murky', value: 'murky' }\n                ]},\n                'Fish Spawn Rate': { key: 'settings.fishSpawnRate', type: 'slider', min: 0.5, max: 2, step: 0.1 },\n                'Obstacle Density': { key: 'settings.obstacleDensity', type: 'dropdown', options: [\n                    { label: 'None', value: 'none' },\n                    { label: 'Low', value: 'low' },\n                    { label: 'Normal', value: 'normal' },\n                    { label: 'High', value: 'high' }\n                ]},\n                'Hook Speed': { key: 'settings.hookSpeed', type: 'dropdown', options: [\n                    { label: 'Slow', value: 'slow' },\n                    { label: 'Normal', value: 'normal' },\n                    { label: 'Fast', value: 'fast' }\n                ]},\n                'Volcanic Eruptions': { key: 'settings.eruptionsEnabled', type: 'checkbox' },\n                'Eruption Every N Casts': { key: 'settings.eruptionEveryNCasts', type: 'slider', min: 2, max: 8, step: 1 },\n                'Show Depth Meter': { key: 'settings.showDepthMeter', type: 'checkbox' },\n                'Starting Coins': { key: 'settings.startingCoins', type: 'number' }\n            }\n        });\n\n        // Game constants\n        const WATER_SURFACE_Y = 200; // pixels from top where water starts\n        const METERS_TO_PX = 18; // 1 meter = 18px\n        const HOOK_DESCENT_SPEED_BASE = 80; // px/s\n        const HOOK_H_SPEED = 300; // px/s horizontal\n        const MAX_DEPTH_BASE = 80; // meters\n\n        // Fish types\n        const FISH_TYPES = [\n            { id: 'bluegill', asset: 'fish_bluegill', name: 'Bluegill', coins: 5, minDepth: 0, maxDepth: 30, speed: 40, width: 60, height: 36 },\n            { id: 'clownfish', asset: 'fish_clownfish', name: 'Clownfish', coins: 8, minDepth: 0, maxDepth: 25, speed: 50, width: 50, height: 35 },\n            { id: 'seahorse', asset: 'fish_seahorse', name: 'Seahorse', coins: 12, minDepth: 5, maxDepth: 35, speed: 25, width: 40, height: 65, flip: true },\n            { id: 'perch', asset: 'fish_perch', name: 'Perch', coins: 10, minDepth: 10, maxDepth: 50, speed: 60, width: 80, height: 48 },\n            { id: 'pufferfish', asset: 'fish_pufferfish', name: 'Pufferfish', coins: 18, minDepth: 15, maxDepth: 55, speed: 35, width: 70, height: 65, flip: true },\n            { id: 'bass', asset: 'fish_bass', name: 'Bass', coins: 25, minDepth: 25, maxDepth: 70, speed: 80, width: 100, height: 60 },\n            { id: 'golden_carp', asset: 'fish_golden_carp', name: 'Golden Carp', coins: 50, minDepth: 40, maxDepth: 80, speed: 100, width: 110, height: 66 },\n            { id: 'swordfish', asset: 'fish_swordfish', name: 'Swordfish', coins: 60, minDepth: 45, maxDepth: 90, speed: 140, width: 130, height: 50 },\n            { id: 'jellyfish', asset: 'fish_jellyfish', name: 'Jellyfish', coins: 40, minDepth: 50, maxDepth: 100, speed: 20, width: 70, height: 90 },\n            { id: 'abyssal_eel', asset: 'fish_perch', name: 'Abyssal Eel', coins: 75, minDepth: 60, maxDepth: 100, speed: 120, width: 130, height: 40 },\n            { id: 'hammerhead', asset: 'fish_hammerhead', name: 'Hammerhead Shark', coins: 90, minDepth: 70, maxDepth: 120, speed: 110, width: 150, height: 70 },\n            { id: 'phantom_ray', asset: 'fish_golden_carp', name: 'Phantom Ray', coins: 100, minDepth: 80, maxDepth: 120, speed: 90, width: 140, height: 80 },\n            { id: 'deep_angler', asset: 'fish_bass', name: 'Deep Angler', coins: 130, minDepth: 100, maxDepth: 140, speed: 60, width: 120, height: 72 },\n            { id: 'leviathan', asset: 'fish_bass', name: '\u26a1 LEVIATHAN', coins: 200, minDepth: 60, maxDepth: 140, speed: 130, width: 160, height: 96, isBoss: true },\n            // WORLD CUP 2026 UPDATE\n            { id: 'soccerball_fish', asset: 'fish_soccerball', name: '\u26bd Soccer Fish', coins: 35, minDepth: 5, maxDepth: 60, speed: 75, width: 65, height: 65, isWorldCup: true, flip: true },\n            { id: 'jersey_fish', asset: 'fish_jersey', name: '\ud83c\udfc5 Jersey Fish', coins: 55, minDepth: 20, maxDepth: 80, speed: 95, width: 85, height: 55, isWorldCup: true, flip: true },\n            { id: 'trophy_fish', asset: 'fish_trophy', name: '\ud83c\udfc6 TROPHY FISH', coins: 300, minDepth: 50, maxDepth: 130, speed: 145, width: 140, height: 100, isBoss: true, isWorldCup: true, flip: true },\n            // NIGHT DIVE UPDATE\n            { id: 'lanternfish', asset: 'fish_lanternfish', name: '\ud83d\udd26 Lantern Fish', coins: 45, minDepth: 35, maxDepth: 90, speed: 55, width: 80, height: 55, isNightDive: true, glows: true, flip: true },\n            { id: 'firefly_squid', asset: 'fish_firefly_squid', name: '\u2728 Firefly Squid', coins: 85, minDepth: 50, maxDepth: 110, speed: 40, width: 75, height: 90, isNightDive: true, glows: true, flip: true },\n            { id: 'ghost_whale', asset: 'fish_hammerhead', name: '\ud83d\udc7b GHOST WHALE', coins: 250, minDepth: 80, maxDepth: 140, speed: 70, width: 170, height: 90, isBoss: true, isNightDive: true, glows: true },\n            // \ud83c\udf0b VOLCANIC TRENCH UPDATE\n            { id: 'ember_guppy', asset: 'fish_ember_guppy', name: '\ud83d\udd25 Ember Guppy', coins: 40, minDepth: 20, maxDepth: 70, speed: 85, width: 70, height: 50, isVolcanic: true, glows: true, glowColor: '#FF7043', flip: true },\n            { id: 'magma_ray', asset: 'fish_magma_ray', name: '\ud83c\udf0b Magma Ray', coins: 95, minDepth: 45, maxDepth: 110, speed: 65, width: 130, height: 70, isVolcanic: true, glows: true, glowColor: '#FF5722', flip: true },\n            { id: 'obsidian_kraken', asset: 'fish_obsidian_kraken', name: '\ud83d\udc19 OBSIDIAN KRAKEN', coins: 350, minDepth: 70, maxDepth: 140, speed: 100, width: 180, height: 95, isBoss: true, isVolcanic: true, glows: true, glowColor: '#FF3D00', flip: true },\n            // HALLOWEEN 2026 seasonal species\n            { id: 'ghost_jelly', asset: 'fish_jellyfish', name: '\ud83d\udc7b Ghost Jelly', coins: 65, minDepth: 20, maxDepth: 90, speed: 30, width: 78, height: 95, isHalloween: true, glows: true, glowColor: '#C58AFF', flip: true },\n            { id: 'pumpkin_puffer', asset: 'fish_pufferfish', name: '\ud83c\udf83 Pumpkin Puffer', coins: 75, minDepth: 10, maxDepth: 75, speed: 38, width: 76, height: 70, isHalloween: true, glows: true, glowColor: '#FF9638', flip: true },\n            { id: 'spectral_koi', asset: 'fish_golden_carp', name: '\ud83d\udd6f\ufe0f Spectral Koi', coins: 120, minDepth: 45, maxDepth: 120, speed: 95, width: 120, height: 74, isHalloween: true, glows: true, glowColor: '#B58AFF', flip: true },\n            { id: 'phantom_leviathan', asset: 'fish_hammerhead', name: '\ud83c\udf83 PHANTOM LEVIATHAN', coins: 450, minDepth: 75, maxDepth: 150, speed: 115, width: 175, height: 92, isBoss: true, isHalloween: true, isHalloweenBoss: true, glows: true, glowColor: '#FF7B39', flip: true }\n        ];\n\n        // Particle system\n        let particles = [];\n        function spawnParticles(x, y, color, count, opts = {}) {\n            for (let i = 0; i < count; i++) {\n                particles.push({\n                    x, y,\n                    vx: (Math.random() - 0.5) * (opts.spread || 200),\n                    vy: (Math.random() - 0.5) * (opts.spread || 200) - (opts.upward || 0),\n                    size: opts.size || (3 + Math.random() * 4),\n                    color: color,\n                    life: 1,\n                    decay: 0.8 + Math.random() * 1.2,\n                    gravity: opts.gravity || 0,\n                    shape: opts.shape || 'circle'\n                });\n            }\n        }\n\n        // Screen shake\n        let shakeIntensity = 0;\n        let shakeDecay = 5;\n\n        // Floating texts\n        let floatingTexts = [];\n        function spawnFloatingText(x, y, text, color = '#FFD54F', size = 28) {\n            floatingTexts.push({ x, y, text, color, size, life: 1, vy: -80 });\n        }\n\n        // Runtime state\n        let coins = config.upgrades.coins || config.settings.startingCoins || 0;\n        let rodLevel = config.upgrades.rodLevel || 1;\n        let lineLevel = config.upgrades.lineLevel || 1;\n        let reelLevel = config.upgrades.reelLevel || 1;\n\n        const STATE_IDLE = 0, STATE_CASTING = 1, STATE_DESCENDING = 2, STATE_REELING = 3, STATE_CAUGHT = 4;\n        let gameState = STATE_IDLE;\n\n        let hookX = 360, hookY = WATER_SURFACE_Y;\n        let hookVelX = 0;\n        let hookDepthMeters = 0;\n        let cameraY = 0; // scroll offset\n\n        let fishes = [];\n        let obstacles = [];\n        let bubbles = [];\n        let caughtFish = null;\n        let reelProgress = 0;\n        let reelTarget = 1;\n        let splashTimer = 0;\n        let splashX = 0;\n\n        let catchPopupVisible = false;\n        let catchPopupFish = null;\n        let catchPopupTimer = 0;\n\n        let shopOpen = false;\n        let leaderboardOpen = false;\n        let inputX = null;\n        let isTapping = false;\n        let lastTime = 0;\n\n        // NEW UPDATE: Combo system\n        let comboCount = 0;\n        let comboTimer = 0;\n        const COMBO_TIMEOUT = 8;\n\n        // NEW UPDATE: Fish journal\n        let fishJournal = { bluegill: 0, clownfish: 0, seahorse: 0, perch: 0, pufferfish: 0, bass: 0, golden_carp: 0, swordfish: 0, jellyfish: 0, abyssal_eel: 0, hammerhead: 0, phantom_ray: 0, deep_angler: 0, leviathan: 0, soccerball_fish: 0, jersey_fish: 0, trophy_fish: 0, lanternfish: 0, firefly_squid: 0, ghost_whale: 0, ember_guppy: 0, magma_ray: 0, obsidian_kraken: 0, ghost_jelly: 0, pumpkin_puffer: 0, spectral_koi: 0, phantom_leviathan: 0 };\n        let totalCatches = 0;\n\n        // Halloween event progress is player-specific and merged with legacy saves.\n        const HALLOWEEN_EVENT_ACTIVE = true;\n        const HALLOWEEN_EVENT_NAME = 'Halloween 2026';\n        let halloweenEvent = { candy: 0, fishCaught: 0, halloweenBosses: 0, rareCollectibles: 0, claimed: [], spookyRodUnlocked: false };\n        let halloweenCandy = [];\n        let hauntedPearls = [];\n        let halloweenMotes = Array.from({ length: 26 }, () => ({ x: Math.random() * 720, y: Math.random() * 1280, size: 1.5 + Math.random() * 3.5, speed: 8 + Math.random() * 18, phase: Math.random() * 6.28, color: Math.random() < 0.55 ? '#bb83ff' : '#ff9b43' }));\n\n        // NEW UPDATE: Water ripples\n        let ripples = [];\n\n        // ANIMATION UPDATE: hook motion trail and sparkle bursts\n        let hookTrail = [];\n        let ambientSparkTimer = 0;\n\n        // NEW UPDATE: Treasure Chests\n        let treasureChests = [];\n\n        // NEW UPDATE: Lucky Lure power-up\n        let luckyLure = false;\n        let luckyLureTimer = 0;\n\n        // WORLD CUP 2026 UPDATE\n        let goldenBootActive = false;\n        let goldenBootTimer = 0;\n        const GOLDEN_BOOT_DURATION = 12;\n        let worldCupConfetti = [];\n        let stadiumWave = 0;\n        let goalCelebrationTimer = 0;\n\n        // NEW UPDATE: Fish Journal\n        let journalOpen = false;\n        let isMainMenuActive = mode === 'play';\n        let halloweenPanelOpen = false;\n        let halloweenMusicStarted = false;\n        let halloweenMusicTimer = null;\n\n        // \ud83c\udf19 NIGHT DIVE UPDATE\n        let nightMode = false;\n        let nightModeTimer = 0;\n        let nightTransition = 0; // 0 = day, 1 = full night\n        let castCount = 0;\n        const NIGHT_EVERY_N_CASTS = 4; // Night falls every 4 casts\n        const NIGHT_DURATION = 45; // Night lasts 45 seconds\n        let sonarActive = false;\n        let sonarTimer = 0;\n        let sonarCooldown = 0;\n        const SONAR_DURATION = 2.5;\n        const SONAR_COOLDOWN = 8;\n        let sonarRings = [];\n        let moonPearls = [];\n        let glowParticles = [];\n\n        // \ud83c\udf0b VOLCANIC TRENCH UPDATE\n        let eruptionActive = false;\n        let eruptionTimer = 0;\n        let eruptionIntensity = 0; // 0 = calm, 1 = full eruption\n        const ERUPTION_EVERY_N_CASTS = Math.max(2, Math.round(config.settings.eruptionEveryNCasts || 3)); // Trench erupts on this cast cycle\n        const ERUPTIONS_ENABLED = config.settings.eruptionsEnabled !== false;\n        const ERUPTION_DURATION = 30;\n        let lavaBubbles = [];\n        let obsidianGems = [];\n        let emberSparks = [];\n        let ventGlowPhase = 0;\n        let heatShield = false;\n        let heatShieldTimer = 0;\n        const HEAT_SHIELD_DURATION = 10;\n\n        // Save/Load Functions\n        async function saveProgress() {\n            try {\n                const saveData = {\n                    coins: coins,\n                    rodLevel: rodLevel,\n                    lineLevel: lineLevel,\n                    reelLevel: reelLevel,\n                    totalCatches: totalCatches,\n                    fishJournal: fishJournal,\n                    halloweenEvent: halloweenEvent\n                };\n                await lib.saveUserGameState(saveData);\n                lib.log('Progress saved successfully');\n            } catch (error) {\n                lib.log(`Failed to save progress: ${error.message}`);\n            }\n        }\n\n        async function loadProgress() {\n            try {\n                const response = await lib.getUserGameState();\n                if (response.state) {\n                    // Merge old saves over current defaults so new event fields never erase progression.\n                    const saved = response.state;\n                    coins = Number.isFinite(saved.coins) ? saved.coins : 0;\n                    rodLevel = Number.isFinite(saved.rodLevel) ? saved.rodLevel : 1;\n                    lineLevel = Number.isFinite(saved.lineLevel) ? saved.lineLevel : 1;\n                    reelLevel = Number.isFinite(saved.reelLevel) ? saved.reelLevel : 1;\n                    totalCatches = Number.isFinite(saved.totalCatches) ? saved.totalCatches : 0;\n                    fishJournal = { ...fishJournal, ...(saved.fishJournal || {}) };\n                    halloweenEvent = { ...halloweenEvent, ...(saved.halloweenEvent || {}) };\n                    halloweenEvent.claimed = Array.isArray(halloweenEvent.claimed) ? halloweenEvent.claimed : [];\n                    halloweenEvent.candy = Math.max(0, Number(halloweenEvent.candy) || 0);\n                    halloweenEvent.fishCaught = Math.max(0, Number(halloweenEvent.fishCaught) || 0);\n                    halloweenEvent.halloweenBosses = Math.max(0, Number(halloweenEvent.halloweenBosses) || 0);\n                    halloweenEvent.rareCollectibles = Math.max(0, Number(halloweenEvent.rareCollectibles) || 0);\n                    \n                    // Update config with loaded values\n                    config.upgrades.coins = coins;\n                    config.upgrades.rodLevel = rodLevel;\n                    config.upgrades.lineLevel = lineLevel;\n                    config.upgrades.reelLevel = reelLevel;\n                    \n                    lib.log('Progress loaded successfully');\n                } else {\n                    lib.log('No saved progress found, starting fresh');\n                }\n            } catch (error) {\n                lib.log(`Failed to load progress: ${error.message}`);\n            }\n        }\n\n        // NEW UPDATE: Weather system\n        let weatherTimer = 0;\n        let currentWeather = 'clear';\n        let raindrops = [];\n        let weatherDuration = 30;\n\n        // Load saved progress in play mode\n        if (mode === 'play') {\n            await loadProgress();\n            updateCoinDisplay();\n        }\n\n        // Computed values\n        function getMaxDepth() { return MAX_DEPTH_BASE + (lineLevel - 1) * 20; }\n        function getDescentSpeed() {\n            let base = HOOK_DESCENT_SPEED_BASE + (rodLevel - 1) * 15;\n            const speedMod = config.settings.hookSpeed;\n            if (speedMod === 'slow') base *= 0.7;\n            else if (speedMod === 'fast') base *= 1.4;\n            return base;\n        }\n        function getReelSpeed() { return 0.4 + (reelLevel - 1) * 0.15; }\n\n        // Generate fish and obstacles for the level\n        function generateLevel() {\n            fishes = [];\n            obstacles = [];\n            const maxD = getMaxDepth();\n            const spawnRate = config.settings.fishSpawnRate || 1;\n            const numFish = Math.floor((10 + Math.random() * 8) * spawnRate);\n            halloweenCandy = []; hauntedPearls = [];\n            const halloweenTypes = FISH_TYPES.filter(t => t.isHalloween && !t.isHalloweenBoss && t.minDepth <= maxD);\n            for (let i = 0; i < 4; i++) {\n                const ft = halloweenTypes[Math.floor(Math.random() * halloweenTypes.length)];\n                const depth = ft.minDepth + Math.random() * Math.max(1, Math.min(ft.maxDepth, maxD) - ft.minDepth);\n                const x = 60 + Math.random() * 600;\n                fishes.push({ type:ft, x, y:WATER_SURFACE_Y+depth*METERS_TO_PX, dir:Math.random()>0.5?1:-1, startX:x, speed:ft.speed*(0.8+Math.random()*0.4), amplitude:90+Math.random()*180, phase:Math.random()*Math.PI*2, caught:false });\n            }\n            for (let i=0; i<5+Math.floor(Math.random()*3); i++) {\n                const depth=8+Math.random()*Math.max(1,maxD-12);\n                halloweenCandy.push({ x:50+Math.random()*620, y:WATER_SURFACE_Y+depth*METERS_TO_PX, caught:false, phase:Math.random()*Math.PI*2, value:1 });\n            }\n            if (Math.random()<0.28 && maxD>=35) {\n                const depth=25+Math.random()*Math.max(1,maxD-30);\n                hauntedPearls.push({ x:70+Math.random()*580, y:WATER_SURFACE_Y+depth*METERS_TO_PX, caught:false, phase:Math.random()*Math.PI*2 });\n            }\n\n            // Preserve the existing every-five-catches boss encounter (Obsidian Kraken).\n            if (totalCatches > 0 && totalCatches % 5 === 0) {\n                const bossType = FISH_TYPES.find(t => t.id === 'obsidian_kraken');\n                if (bossType && bossType.minDepth <= maxD) {\n                    const depth = bossType.minDepth + Math.random() * (Math.min(bossType.maxDepth, maxD) - bossType.minDepth);\n                    const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                    fishes.push({ type: bossType, x: 360, y, dir: 1, startX: 360, speed: bossType.speed, amplitude: 250, phase: 0, caught: false });\n                }\n            }\n            // Halloween's Phantom Leviathan is an additional seasonal boss, not a replacement.\n            if (totalCatches >= 10 && totalCatches % 10 === 0 && maxD >= 75) {\n                const halloweenBoss = FISH_TYPES.find(t => t.id === 'phantom_leviathan');\n                const depth = halloweenBoss.minDepth + Math.random() * (Math.min(halloweenBoss.maxDepth, maxD) - halloweenBoss.minDepth);\n                fishes.push({ type:halloweenBoss, x:360, y:WATER_SURFACE_Y+depth*METERS_TO_PX, dir:1, startX:360, speed:halloweenBoss.speed, amplitude:230, phase:0, caught:false });\n            }\n\n            // World Cup 2026: Spawn World Cup fish\n            const wcTypes = FISH_TYPES.filter(t => t.isWorldCup && !t.isBoss);\n            const numWCFish = 2 + Math.floor(Math.random() * 3);\n            for (let i = 0; i < numWCFish; i++) {\n                const ft = wcTypes[Math.floor(Math.random() * wcTypes.length)];\n                const depth = ft.minDepth + Math.random() * (Math.min(ft.maxDepth, maxD) - ft.minDepth);\n                const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                const x = 60 + Math.random() * 600;\n                fishes.push({\n                    type: ft, x, y, dir: Math.random() > 0.5 ? 1 : -1, startX: x,\n                    speed: ft.speed * (0.8 + Math.random() * 0.4),\n                    amplitude: 100 + Math.random() * 200,\n                    phase: Math.random() * Math.PI * 2, caught: false\n                });\n            }\n\n            // \ud83c\udf19 Night Dive: Spawn Night Dive fish\n            const nightTypes = FISH_TYPES.filter(t => t.isNightDive && !t.isBoss);\n            const numNightFish = nightMode ? (3 + Math.floor(Math.random() * 3)) : 1;\n            for (let i = 0; i < numNightFish; i++) {\n                const ft = nightTypes[Math.floor(Math.random() * nightTypes.length)];\n                if (ft.minDepth > maxD) continue;\n                const depth = ft.minDepth + Math.random() * (Math.min(ft.maxDepth, maxD) - ft.minDepth);\n                const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                const x = 60 + Math.random() * 600;\n                fishes.push({\n                    type: ft, x, y, dir: Math.random() > 0.5 ? 1 : -1, startX: x,\n                    speed: ft.speed * (0.8 + Math.random() * 0.4),\n                    amplitude: 80 + Math.random() * 150,\n                    phase: Math.random() * Math.PI * 2, caught: false\n                });\n            }\n\n            // \ud83c\udf19 Night Dive: Boss spawn during night\n            if (nightMode && totalCatches > 3 && Math.random() < 0.3) {\n                const ghostWhale = FISH_TYPES.find(t => t.id === 'ghost_whale');\n                if (ghostWhale && ghostWhale.minDepth <= maxD) {\n                    const depth = ghostWhale.minDepth + Math.random() * (Math.min(ghostWhale.maxDepth, maxD) - ghostWhale.minDepth);\n                    const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                    fishes.push({\n                        type: ghostWhale, x: 360, y, dir: 1, startX: 360,\n                        speed: ghostWhale.speed, amplitude: 280, phase: 0, caught: false\n                    });\n                }\n            }\n\n            // \ud83c\udf19 Night Dive: Moon Pearls (only at night)\n            moonPearls = [];\n            if (nightMode) {\n                const numPearls = 1 + Math.floor(Math.random() * 2);\n                for (let i = 0; i < numPearls; i++) {\n                    const depth = 25 + Math.random() * (maxD - 30);\n                    moonPearls.push({\n                        x: 80 + Math.random() * 560,\n                        y: WATER_SURFACE_Y + depth * METERS_TO_PX,\n                        width: 50, height: 45,\n                        coins: 100 + Math.floor(Math.random() * 100),\n                        caught: false,\n                        glowPhase: Math.random() * Math.PI * 2\n                    });\n                }\n            }\n\n            // \ud83c\udf0b Volcanic Trench: Spawn magma fish\n            const volcanicTypes = FISH_TYPES.filter(t => t.isVolcanic && !t.isBoss);\n            const numVolcanicFish = eruptionActive ? (3 + Math.floor(Math.random() * 3)) : 1;\n            for (let i = 0; i < numVolcanicFish; i++) {\n                const ft = volcanicTypes[Math.floor(Math.random() * volcanicTypes.length)];\n                if (ft.minDepth > maxD) continue;\n                const depth = ft.minDepth + Math.random() * (Math.min(ft.maxDepth, maxD) - ft.minDepth);\n                const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                const x = 60 + Math.random() * 600;\n                fishes.push({\n                    type: ft, x, y, dir: Math.random() > 0.5 ? 1 : -1, startX: x,\n                    speed: ft.speed * (0.8 + Math.random() * 0.4),\n                    amplitude: 90 + Math.random() * 180,\n                    phase: Math.random() * Math.PI * 2, caught: false\n                });\n            }\n\n            // \ud83c\udf0b Volcanic Trench: Obsidian Kraken boss during eruptions\n            if (eruptionActive && totalCatches > 2 && Math.random() < 0.35) {\n                const kraken = FISH_TYPES.find(t => t.id === 'obsidian_kraken');\n                if (kraken && kraken.minDepth <= maxD) {\n                    const depth = kraken.minDepth + Math.random() * (Math.min(kraken.maxDepth, maxD) - kraken.minDepth);\n                    fishes.push({\n                        type: kraken, x: 360, y: WATER_SURFACE_Y + depth * METERS_TO_PX,\n                        dir: 1, startX: 360, speed: kraken.speed,\n                        amplitude: 270, phase: 0, caught: false\n                    });\n                }\n            }\n\n            // \ud83c\udf0b Volcanic Trench: Rising lava bubble hazards\n            lavaBubbles = [];\n            const numLava = eruptionActive ? 7 + Math.floor(Math.random() * 5) : 0;\n            for (let i = 0; i < numLava; i++) {\n                lavaBubbles.push({\n                    x: 60 + Math.random() * 600,\n                    y: WATER_SURFACE_Y + (25 + Math.random() * Math.max(10, maxD - 25)) * METERS_TO_PX,\n                    size: 42 + Math.random() * 26,\n                    speed: 45 + Math.random() * 55,\n                    wobble: Math.random() * Math.PI * 2,\n                    wobbleSpeed: 1.5 + Math.random() * 1.5\n                });\n            }\n\n            // \ud83c\udf0b Volcanic Trench: Obsidian Gems (only during eruptions)\n            obsidianGems = [];\n            if (eruptionActive) {\n                const numGems = 1 + Math.floor(Math.random() * 3);\n                for (let i = 0; i < numGems; i++) {\n                    const minGemDepth = Math.max(20, maxD * 0.35);\n                    if (minGemDepth >= maxD - 5) break;\n                    const depth = minGemDepth + Math.random() * (maxD - minGemDepth - 5);\n                    obsidianGems.push({\n                        x: 70 + Math.random() * 580,\n                        y: WATER_SURFACE_Y + depth * METERS_TO_PX,\n                        width: 52, height: 44,\n                        coins: 80 + Math.floor(Math.random() * 120),\n                        caught: false,\n                        glowPhase: Math.random() * Math.PI * 2\n                    });\n                }\n            }\n\n            for (let i = 0; i < numFish; i++) {\n                // Weight fish types based on depth access\n                const maxD_accessible = maxD;\n                let weights = [0.2, 0.15, 0.1, 0.15, 0.1, 0.12, 0.06, 0, 0, 0, 0, 0, 0];\n                if (maxD_accessible >= 45) weights[7] = 0.05;\n                if (maxD_accessible >= 50) weights[8] = 0.04;\n                if (maxD_accessible >= 60) weights[9] = 0.06;\n                if (maxD_accessible >= 70) weights[10] = 0.04;\n                if (maxD_accessible >= 80) weights[11] = 0.04;\n                if (maxD_accessible >= 100) weights[12] = 0.03;\n                const typeIdx = weightedRandom(weights);\n                const fishType = FISH_TYPES[typeIdx];\n                const depth = fishType.minDepth + Math.random() * (Math.min(fishType.maxDepth, maxD) - fishType.minDepth);\n                const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                const x = 60 + Math.random() * 600;\n                const dir = Math.random() > 0.5 ? 1 : -1;\n                fishes.push({\n                    type: fishType,\n                    x, y, dir,\n                    startX: x,\n                    speed: fishType.speed * (0.8 + Math.random() * 0.4),\n                    amplitude: 100 + Math.random() * 200,\n                    phase: Math.random() * Math.PI * 2,\n                    caught: false\n                });\n            }\n\n            // Obstacles\n            const density = config.settings.obstacleDensity || 'normal';\n            let numObs = 0;\n            if (density === 'low') numObs = 3;\n            else if (density === 'normal') numObs = 6;\n            else if (density === 'high') numObs = 10;\n\n            for (let i = 0; i < numObs; i++) {\n                const roll = Math.random();\n                let obsType, ow, oh;\n                if (roll < 0.35) { obsType = 'boot'; ow = 70; oh = 50; }\n                else if (roll < 0.7) { obsType = 'can'; ow = 45; oh = 55; }\n                else { obsType = 'soccerball'; ow = 55; oh = 55; } // World Cup 2026\n                const depth = 5 + Math.random() * (maxD - 10);\n                const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                const x = 80 + Math.random() * 560;\n                obstacles.push({ type: obsType, x, y, width: ow, height: oh });\n            }\n\n            // Treasure Chests\n            treasureChests = [];\n            const numChests = Math.random() < 0.7 ? Math.floor(1 + Math.random() * 2) : 0;\n            for (let i = 0; i < numChests; i++) {\n                const minChestDepth = Math.max(30, maxD * 0.4);\n                const depth = minChestDepth + Math.random() * (maxD - minChestDepth - 5);\n                if (depth < maxD) {\n                    treasureChests.push({\n                        x: 80 + Math.random() * 560,\n                        y: WATER_SURFACE_Y + depth * METERS_TO_PX,\n                        width: 64, height: 52,\n                        coins: 60 + Math.floor(Math.random() * 140),\n                        caught: false,\n                        glowPhase: Math.random() * Math.PI * 2,\n                        rare: Math.random() < 0.15\n                    });\n                }\n            }\n\n            // Seaweed\n            const numSeaweed = 3 + Math.floor(Math.random() * 3);\n            for (let i = 0; i < numSeaweed; i++) {\n                const depth = 20 + Math.random() * (maxD - 25);\n                const y = WATER_SURFACE_Y + depth * METERS_TO_PX;\n                obstacles.push({\n                    type: 'seaweed',\n                    x: 50 + Math.random() * 620,\n                    y,\n                    width: 70,\n                    height: 110\n                });\n            }\n        }\n\n        function weightedRandom(weights) {\n            const total = weights.reduce((a, b) => a + b, 0);\n            let r = Math.random() * total;\n            for (let i = 0; i < weights.length; i++) {\n                r -= weights[i];\n                if (r <= 0) return i;\n            }\n            return weights.length - 1;\n        }\n\n        // Initialize bubbles\n        function spawnBubble() {\n            bubbles.push({\n                x: 50 + Math.random() * 620,\n                y: WATER_SURFACE_Y + getMaxDepth() * METERS_TO_PX * Math.random(),\n                size: 3 + Math.random() * 6,\n                speed: 20 + Math.random() * 40,\n                wobble: Math.random() * Math.PI * 2\n            });\n        }\n        for (let i = 0; i < 20; i++) spawnBubble();\n\n        generateLevel();\n\n        // UI creation\n        function buildUI() {\n            uiOverlay.innerHTML = `\n                <div class=\"hud\">\n                    <div class=\"hud-item hud-coins\"><div class=\"coin-icon\">$</div><span id=\"coin-display\">${coins}</span></div>\n                </div>\n                ${config.settings.showDepthMeter ? `<div class=\"depth-meter\"><div class=\"depth-fill\" id=\"depth-fill\"></div><div class=\"depth-label\" id=\"depth-label\">0m</div></div>` : ''}\n                <div class=\"cast-prompt\" id=\"cast-prompt\">Tap to Cast!</div>\n                <div class=\"reel-bar-container\" id=\"reel-bar\">\n                    <div class=\"reel-bar-fill\" id=\"reel-fill\"></div>\n                    <div class=\"reel-text\">TAP to Reel!</div>\n                </div>\n                <div class=\"catch-popup\" id=\"catch-popup\">\n                    <h2 id=\"catch-name\"></h2>\n                    <div class=\"coins-earned\" id=\"catch-coins\"></div>\n                    <button class=\"btn btn-accent\" id=\"catch-ok\">Nice!</button>\n                </div>\n                ${mode === 'play' ? `\n                <button class=\"btn shop-btn\" id=\"shop-btn\">\ud83c\udfa3 Shop</button>\n                <button class=\"btn shop-btn\" id=\"leaderboard-btn\" style=\"right: 136px;\">\ud83c\udfc6</button>\n                <button class=\"btn shop-btn\" id=\"journal-btn\" style=\"right: 256px;\">\ud83d\udcd6</button>\n                <button class=\"btn\" id=\"sonar-btn\" style=\"position:absolute; bottom:100px; right:16px; width:72px; height:72px; border-radius:50%; font-size:28px; padding:0; display:flex; align-items:center; justify-content:center; background:#1E88E5; box-shadow: 0 4px 0 #1565C0, 0 4px 16px rgba(0,0,0,0.3);\">\ud83d\udce1</button>\n                ` : ''}\n                <button class=\"btn halloween-event-btn\" id=\"halloween-event-btn\" style=\"display:${mode === 'play' ? 'block' : 'none'}\">\ud83c\udf83 Event</button>\n                <div class=\"shop-panel\" id=\"halloween-panel\">\n                    <div class=\"shop-content\" style=\"position:relative;\">\n                        <button class=\"close-shop\" id=\"close-halloween\">\u2715</button>\n                        <h2>\ud83c\udf83 Halloween 2026</h2>\n                        <div class=\"event-summary\" id=\"event-summary\"></div>\n                        <div id=\"event-missions\"></div>\n                        <div class=\"event-collection\" id=\"event-rewards\"></div>\n                    </div>\n                </div>\n                <div class=\"halloween-menu${mode === 'play' ? '' : ' hidden'}\" id=\"halloween-menu\">\n                    <div class=\"menu-pumpkins\" aria-hidden=\"true\">\ud83c\udf83\u3000\ud83d\udd6f\ufe0f\u3000\ud83c\udf83</div>\n                    <div class=\"halloween-subtitle\">A HAUNTED UNDERWATER ADVENTURE</div>\n                    <h1>Deep Blue<br>Dragger</h1>\n                    <div class=\"halloween-copy\">The lake has turned eerie for Halloween. Cast into the haunted deep, catch spectral fish, collect candy, and claim seasonal rewards!</div>\n                    <button class=\"btn\" id=\"halloween-start\">\ud83c\udfa3 Start Fishing</button>\n                    <button class=\"btn btn-event\" id=\"halloween-menu-event\">\ud83c\udf83 Event Missions</button>\n                    <div class=\"menu-progress\" id=\"menu-event-progress\"></div>\n                </div>\n                <div class=\"shop-panel\" id=\"shop-panel\">\n                    <div class=\"shop-content\" style=\"position:relative;\">\n                        <button class=\"close-shop\" id=\"close-shop\">\u2715</button>\n                        <h2>\ud83c\udfa3 Upgrade Shop</h2>\n                        <div id=\"shop-items\"></div>\n                    </div>\n                </div>\n                <div class=\"shop-panel\" id=\"leaderboard-panel\">\n                    <div class=\"shop-content\" style=\"position:relative;\">\n                        <button class=\"close-shop\" id=\"close-leaderboard\">\u2715</button>\n                        <h2>\ud83c\udfc6 Leaderboard</h2>\n                        <div id=\"leaderboard-rank\" style=\"text-align: center; margin-bottom: 16px; font-family: 'Fredoka', sans-serif; font-size: 20px; color: #1E3A5F;\"></div>\n                        <div id=\"leaderboard-entries\"></div>\n                    </div>\n                </div>\n                <div class=\"shop-panel\" id=\"journal-panel\">\n                    <div class=\"shop-content\" style=\"position:relative;\">\n                        <button class=\"close-shop\" id=\"close-journal\">\u2715</button>\n                        <h2>\ud83d\udcd6 Fish Journal</h2>\n                        <div style=\"text-align:center; margin-bottom:16px; font-family:'Fredoka',sans-serif; font-size:20px; color:#546E7A;\" id=\"journal-summary\"></div>\n                        <div id=\"journal-entries\" style=\"display:grid; grid-template-columns:1fr; gap:14px;\"></div>\n                    </div>\n                </div>\n            `;\n        }\n        buildUI();\n        updateMenuEventProgress();\n\n        function updateCoinDisplay() {\n            const el = document.getElementById('coin-display');\n            if (el) el.textContent = coins;\n        }\n\n        function updateDepthMeter() {\n            if (!config.settings.showDepthMeter) return;\n            const fill = document.getElementById('depth-fill');\n            const label = document.getElementById('depth-label');\n            if (fill && label) {\n                const pct = Math.min(hookDepthMeters / getMaxDepth() * 100, 100);\n                fill.style.height = pct + '%';\n                label.textContent = Math.floor(hookDepthMeters) + 'm';\n            }\n        }\n\n        function showCastPrompt(show) {\n            const el = document.getElementById('cast-prompt');\n            if (el) el.style.display = show ? 'block' : 'none';\n        }\n\n        function showReelBar(show) {\n            const el = document.getElementById('reel-bar');\n            if (el) el.style.display = show ? 'block' : 'none';\n        }\n\n        function updateReelBar() {\n            const el = document.getElementById('reel-fill');\n            if (el) el.style.width = (reelProgress / reelTarget * 100) + '%';\n        }\n\n        function showCatchPopup(fish) {\n            const popup = document.getElementById('catch-popup');\n            const nameEl = document.getElementById('catch-name');\n            const coinsEl = document.getElementById('catch-coins');\n            if (popup && nameEl && coinsEl) {\n                const comboBonus = comboCount > 1 ? ` (x${comboCount} COMBO!)` : '';\n                const depthBonus = 1 + (hookDepthMeters / getMaxDepth()) * 2;\n                const totalCoins = Math.round(fish.type.coins * Math.max(1, comboCount) * depthBonus);\n                nameEl.textContent = (fish.type.isBoss ? '\ud83c\udfc6 ' : '\ud83d\udc1f ') + fish.type.name + '!';\n                coinsEl.textContent = '+' + totalCoins + ' coins' + comboBonus + (depthBonus > 1.5 ? ' \ud83c\udfca Deep catch!' : '');\n                popup.classList.add('show');\n                catchPopupVisible = true;\n            }\n        }\n\n        function hideCatchPopup() {\n            const popup = document.getElementById('catch-popup');\n            if (popup) popup.classList.remove('show');\n            catchPopupVisible = false;\n        }\n\n        function openShop() {\n            shopOpen = true;\n            const panel = document.getElementById('shop-panel');\n            if (panel) panel.classList.add('show');\n            renderShopItems();\n        }\n\n        function closeShop() {\n            shopOpen = false;\n            const panel = document.getElementById('shop-panel');\n            if (panel) panel.classList.remove('show');\n        }\n\n        function openLeaderboard() {\n            leaderboardOpen = true;\n            const panel = document.getElementById('leaderboard-panel');\n            if (panel) panel.classList.add('show');\n            fetchAndDisplayLeaderboard();\n        }\n\n        function closeLeaderboard() {\n            leaderboardOpen = false;\n            const panel = document.getElementById('leaderboard-panel');\n            if (panel) panel.classList.remove('show');\n        }\n\n        function openJournal() {\n            journalOpen = true;\n            const panel = document.getElementById('journal-panel');\n            if (panel) panel.classList.add('show');\n            renderJournal();\n        }\n\n        const HALLOWEEN_MISSIONS = [\n            { id:'candy', title:'Sweet Haul', detail:'Collect 10 pieces of Halloween candy', target:10, reward:'100 coins', progress:()=>halloweenEvent.candy, grant:()=>{ coins += 100; } },\n            { id:'fish', title:'Haunted Catch', detail:'Reel in 8 fish during the event', target:8, reward:'Spooky Rod skin', progress:()=>halloweenEvent.fishCaught, grant:()=>{ halloweenEvent.spookyRodUnlocked = true; } },\n            { id:'relic', title:'Lost in the Ruins', detail:'Find a rare Haunted Pearl', target:1, reward:'250 coins + 5 candy', progress:()=>halloweenEvent.rareCollectibles, grant:()=>{ coins += 250; halloweenEvent.candy += 5; } },\n            { id:'boss', title:'Face the Phantom', detail:'Catch the Phantom Leviathan', target:1, reward:'300 coins', progress:()=>halloweenEvent.halloweenBosses, grant:()=>{ coins += 300; } }\n        ];\n        function openHalloweenEvent() {\n            halloweenPanelOpen = true;\n            document.getElementById('halloween-panel')?.classList.add('show');\n            renderHalloweenEvent();\n        }\n        function closeHalloweenEvent() {\n            halloweenPanelOpen = false;\n            document.getElementById('halloween-panel')?.classList.remove('show');\n            updateMenuEventProgress();\n        }\n        function renderHalloweenEvent() {\n            const summary = document.getElementById('event-summary');\n            const container = document.getElementById('event-missions');\n            const rewards = document.getElementById('event-rewards');\n            if (!summary || !container || !rewards) return;\n            summary.textContent = `Limited-time seasonal event \u2022 \ud83c\udf6c ${halloweenEvent.candy} candy \u2022 ${halloweenEvent.fishCaught} event catches`;\n            container.innerHTML = HALLOWEEN_MISSIONS.map(m => {\n                const progress = Math.min(m.target, Math.max(0, m.progress()));\n                const claimed = halloweenEvent.claimed.includes(m.id);\n                const ready = progress >= m.target && !claimed;\n                return `<div class=\"event-mission\"><div class=\"event-mission-info\"><strong>${m.title}</strong><span>${m.detail} \u2022 Reward: ${m.reward}</span><div class=\"event-progress-track\"><div class=\"event-progress-fill\" style=\"width:${progress / m.target * 100}%\"></div></div><span>${progress} / ${m.target}${claimed ? ' \u2022 Claimed' : ''}</span></div><button class=\"event-claim\" data-mission=\"${m.id}\" ${ready ? '' : 'disabled'}>${claimed ? 'Claimed' : ready ? 'Claim' : 'In progress'}</button></div>`;\n            }).join('');\n            container.querySelectorAll('.event-claim:not(:disabled)').forEach(button => button.addEventListener('click', () => claimHalloweenMission(button.dataset.mission)));\n            rewards.textContent = `\ud83c\udf81 Exclusive reward: ${halloweenEvent.spookyRodUnlocked ? 'Spooky Rod unlocked!' : 'Complete \u201cHaunted Catch\u201d to unlock the Spooky Rod skin.'} \u2022 Rare find: Haunted Pearl ${halloweenEvent.rareCollectibles > 0 ? '\u2713' : '???'}`;\n            updateMenuEventProgress();\n        }\n        function claimHalloweenMission(id) {\n            const mission = HALLOWEEN_MISSIONS.find(m => m.id === id);\n            if (!mission || halloweenEvent.claimed.includes(id) || mission.progress() < mission.target) return;\n            mission.grant(); halloweenEvent.claimed.push(id);\n            config.upgrades.coins = coins; updateCoinDisplay(); playSuccessSfx();\n            renderHalloweenEvent(); saveProgress();\n        }\n        function updateMenuEventProgress() {\n            const el = document.getElementById('menu-event-progress');\n            if (el) el.textContent = `\ud83c\udf6c Candy ${halloweenEvent.candy} \u2022 \ud83d\udc1f ${halloweenEvent.fishCaught}/8 event fish \u2022 ${halloweenEvent.spookyRodUnlocked ? '\ud83e\ude84 Spooky Rod earned' : '\ud83c\udf81 Spooky Rod reward'}`;\n        }\n        function startHalloweenMusic() {\n            if (halloweenMusicStarted) return;\n            halloweenMusicStarted = true;\n            try {\n                const audio = getAudioCtx(); if (audio.state === 'suspended') audio.resume();\n                const notes = [146.83,174.61,220,196,130.81,164.81,220,174.61]; let beat = 0;\n                const playNote = () => {\n                    if (!audioCtx || audioCtx.state === 'closed') return;\n                    const osc = audio.createOscillator(), gain = audio.createGain(), filter = audio.createBiquadFilter();\n                    osc.type='triangle'; filter.type='lowpass'; filter.frequency.value=700; osc.frequency.setValueAtTime(notes[beat++ % notes.length],audio.currentTime);\n                    gain.gain.setValueAtTime(0.0001,audio.currentTime); gain.gain.linearRampToValueAtTime(0.035,audio.currentTime+0.08); gain.gain.exponentialRampToValueAtTime(0.0001,audio.currentTime+0.72);\n                    osc.connect(filter); filter.connect(gain); gain.connect(audio.destination); osc.start(); osc.stop(audio.currentTime+0.75);\n                };\n                playNote(); halloweenMusicTimer = setInterval(playNote,760);\n            } catch(e) { lib.log('Halloween ambience unavailable'); }\n        }\n\n        function closeJournal() {\n            journalOpen = false;\n            const panel = document.getElementById('journal-panel');\n            if (panel) panel.classList.remove('show');\n        }\n\n        function renderJournal() {\n            const container = document.getElementById('journal-entries');\n            const summary = document.getElementById('journal-summary');\n            if (!container || !summary) return;\n            const caught = FISH_TYPES.filter(ft => (fishJournal[ft.id] || 0) > 0).length;\n            summary.textContent = `${caught} / ${FISH_TYPES.length} species discovered \u2022 ${totalCatches} total catches`;\n            container.innerHTML = FISH_TYPES.map((ft, index) => {\n                const count = fishJournal[ft.id] || 0;\n                const discovered = count > 0;\n                return `\n                    <div style=\"--journal-index:${index}; background:${discovered ? (ft.isBoss ? '#FFF8E1' : '#F0F8FF') : '#E8EEF3'}; border-radius:14px; padding:16px; display:flex; align-items:center; gap:14px; border: ${ft.isBoss ? '2px solid #D99A00' : '2px solid #B7C9D8'}; opacity:1\">\n                        <div style=\"font-size:28px;\">${discovered ? '\ud83d\udc1f' : '\u2753'}</div>\n                        <div style=\"flex:1; min-width:0;\">\n                            <div style=\"font-family:'Fredoka',sans-serif; font-size:17px; font-weight:600; color:#1E3A5F; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;\">${discovered ? ft.name : '???'}</div>\n                            <div style=\"font-size:14px; color:#546E7A;\">${discovered ? `Depth: ${ft.minDepth}\u2013${ft.maxDepth}m` : 'Undiscovered'}</div>\n                        </div>\n                        <div style=\"font-family:'Fredoka',sans-serif; font-size:20px; color:${ft.isBoss ? '#F9A825' : '#66BB6A'}; font-weight:700;\">${discovered ? '\u00d7'+count : ''}</div>\n                    </div>\n                `;\n            }).join('');\n        }\n\n        async function fetchAndDisplayLeaderboard() {\n            const entriesContainer = document.getElementById('leaderboard-entries');\n            const rankContainer = document.getElementById('leaderboard-rank');\n            \n            if (!entriesContainer || !rankContainer) return;\n\n            // Show loading state\n            entriesContainer.innerHTML = '<div style=\"text-align: center; padding: 32px; font-family: Fredoka; color: #1E3A5F;\">Loading...</div>';\n            rankContainer.innerHTML = '';\n\n            try {\n                const response = await lib.getTopNEntriesFromLeaderboard(10);\n                \n                // Display user rank\n                if (response.userRank !== null) {\n                    rankContainer.innerHTML = `Your Rank: <strong>#${response.userRank}</strong>`;\n                } else {\n                    rankContainer.innerHTML = 'Catch a fish to join the leaderboard!';\n                }\n\n                // Display leaderboard entries\n                if (response.entries && response.entries.length > 0) {\n                    entriesContainer.innerHTML = response.entries.map((entry, index) => {\n                        const rank = index + 1;\n                        const medal = rank === 1 ? '\ud83e\udd47' : rank === 2 ? '\ud83e\udd48' : rank === 3 ? '\ud83e\udd49' : `#${rank}`;\n                        return `\n                            <div class=\"leaderboard-entry\" style=\"display: flex; align-items: center; background: #F0F8FF; border-radius: 16px; padding: 12px 16px; margin-bottom: 12px;\">\n                                <div style=\"font-family: 'Fredoka', sans-serif; font-size: 24px; font-weight: 700; width: 50px;\">${medal}</div>\n                                <img src=\"${entry.profilePicture}\" alt=\"${entry.username}\" style=\"width: 48px; height: 48px; border-radius: 50%; margin-right: 12px; border: 3px solid #4FC3F7;\" onerror=\"this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2248%22 height=%2248%22%3E%3Ccircle cx=%2224%22 cy=%2224%22 r=%2224%22 fill=%22%23B0BEC5%22/%3E%3C/svg%3E'\">\n                                <div style=\"flex: 1;\">\n                                    <div style=\"font-family: 'Fredoka', sans-serif; font-size: 20px; font-weight: 600; color: #1E3A5F;\">${entry.username}</div>\n                                </div>\n                                <div style=\"font-family: 'Fredoka', sans-serif; font-size: 24px; font-weight: 700; color: #F9A825;\">\ud83d\udcb0 ${entry.score}</div>\n                            </div>\n                        `;\n                    }).join('');\n                } else {\n                    entriesContainer.innerHTML = '<div style=\"text-align: center; padding: 32px; font-family: Fredoka; color: #546E7A;\">No entries yet. Be the first!</div>';\n                }\n            } catch (error) {\n                lib.log(`Failed to fetch leaderboard: ${error.message}`);\n                entriesContainer.innerHTML = '<div style=\"text-align: center; padding: 32px; font-family: Fredoka; color: #EF5350;\">Failed to load leaderboard</div>';\n                rankContainer.innerHTML = '';\n            }\n        }\n\n        async function submitScoreToLeaderboard() {\n            try {\n                const response = await lib.addPlayerScoreToLeaderboard(coins, 10);\n                if (response.success) {\n                    lib.log(`Score submitted! Rank: ${response.userRank}`);\n                }\n            } catch (error) {\n                lib.log(`Failed to submit score: ${error.message}`);\n                // Game continues to work without leaderboard\n            }\n        }\n\n        function renderShopItems() {\n            const container = document.getElementById('shop-items');\n            if (!container) return;\n\n            const upgrades = [\n                { name: 'Rod Power', desc: `Level ${rodLevel} \u2192 ${rodLevel + 1} (Cast deeper, faster descent)`, cost: rodLevel * 30, key: 'rod' },\n                { name: 'Line Length', desc: `Level ${lineLevel} \u2192 ${lineLevel + 1} (Max depth: ${getMaxDepth()}m \u2192 ${getMaxDepth()+20}m)`, cost: lineLevel * 25, key: 'line' },\n                { name: 'Reel Speed', desc: `Level ${reelLevel} \u2192 ${reelLevel + 1} (Reel in faster)`, cost: reelLevel * 20, key: 'reel' }\n            ];\n\n            container.innerHTML = upgrades.map(u => `\n                <div class=\"upgrade-item\">\n                    <div class=\"upgrade-info\">\n                        <h3>${u.name}</h3>\n                        <p>${u.desc}</p>\n                        <span class=\"upgrade-cost\">\ud83d\udcb0 ${u.cost} coins</span>\n                    </div>\n                    <button class=\"btn-upgrade\" data-key=\"${u.key}\" data-cost=\"${u.cost}\" ${coins < u.cost ? 'disabled' : ''}>Buy</button>\n                </div>\n            `).join('');\n\n            container.querySelectorAll('.btn-upgrade').forEach(btn => {\n                btn.addEventListener('click', (e) => {\n                    const key = e.target.dataset.key;\n                    const cost = parseInt(e.target.dataset.cost);\n                    if (coins >= cost) {\n                        coins -= cost;\n                        if (key === 'rod') rodLevel++;\n                        else if (key === 'line') lineLevel++;\n                        else if (key === 'reel') reelLevel++;\n                        config.upgrades.coins = coins;\n                        config.upgrades.rodLevel = rodLevel;\n                        config.upgrades.lineLevel = lineLevel;\n                        config.upgrades.reelLevel = reelLevel;\n                        playCoinSfx();\n                        updateCoinDisplay();\n                        renderShopItems();\n                        generateLevel();\n                        \n                        // Auto-save after purchase\n                        saveProgress();\n                    }\n                });\n            });\n        }\n\n        // Event handlers\n        if (mode === 'play') {\n            document.getElementById('halloween-start')?.addEventListener('click', () => {\n                startHalloweenMusic(); isMainMenuActive=false; document.getElementById('halloween-menu')?.classList.add('hidden'); showCastPrompt(true); updateMenuEventProgress();\n            });\n            document.getElementById('halloween-menu-event')?.addEventListener('click', () => { startHalloweenMusic(); openHalloweenEvent(); });\n            document.getElementById('halloween-event-btn')?.addEventListener('click', () => { if(gameState===STATE_IDLE) openHalloweenEvent(); });\n            document.getElementById('close-halloween')?.addEventListener('click', closeHalloweenEvent);\n            document.getElementById('halloween-panel')?.addEventListener('click',e=>{ if(e.target.id==='halloween-panel') closeHalloweenEvent(); });\n            document.getElementById('shop-btn')?.addEventListener('click', () => {\n                if (gameState === STATE_IDLE) openShop();\n            });\n            document.getElementById('close-shop')?.addEventListener('click', closeShop);\n            document.getElementById('leaderboard-btn')?.addEventListener('click', () => {\n                if (gameState === STATE_IDLE) openLeaderboard();\n            });\n            document.getElementById('journal-btn')?.addEventListener('click', () => {\n                if (gameState === STATE_IDLE) openJournal();\n            });\n            document.getElementById('close-leaderboard')?.addEventListener('click', closeLeaderboard);\n            document.getElementById('close-journal')?.addEventListener('click', closeJournal);\n            document.getElementById('catch-ok')?.addEventListener('click', () => {\n                hideCatchPopup();\n                resetCast();\n            });\n\n            // \ud83c\udf19 Night Dive: Sonar button\n            document.getElementById('sonar-btn')?.addEventListener('click', () => {\n                if (sonarCooldown <= 0 && gameState === STATE_DESCENDING) {\n                    activateSonar();\n                }\n            });\n\n            // Touch/mouse input\n            canvas.addEventListener('pointerdown', (e) => {\n                if (isMainMenuActive || shopOpen || leaderboardOpen || journalOpen || halloweenPanelOpen || catchPopupVisible) return;\n                e.preventDefault();\n                const rect = canvas.getBoundingClientRect();\n                inputX = (e.clientX - rect.left) / rect.width * canvas.width;\n                isTapping = true;\n\n                if (gameState === STATE_IDLE) {\n                    startCast();\n                } else if (gameState === STATE_REELING) {\n                    reelProgress += getReelSpeed() * 0.15;\n                    playReelSfx();\n                }\n            });\n\n            canvas.addEventListener('pointermove', (e) => {\n                if (isMainMenuActive || shopOpen || leaderboardOpen || journalOpen || halloweenPanelOpen || catchPopupVisible) return;\n                const rect = canvas.getBoundingClientRect();\n                inputX = (e.clientX - rect.left) / rect.width * canvas.width;\n            });\n\n            canvas.addEventListener('pointerup', () => { isTapping = false; });\n\n            // Keyboard\n            document.addEventListener('keydown', (e) => {\n                if (isMainMenuActive || shopOpen || leaderboardOpen || journalOpen || halloweenPanelOpen || catchPopupVisible) return;\n                if (e.code === 'Space') {\n                    if (isMainMenuActive) return;\n                    if (gameState === STATE_IDLE) startCast();\n                    else if (gameState === STATE_REELING) {\n                        reelProgress += getReelSpeed() * 0.15;\n                        playReelSfx();\n                    }\n                }\n                if (e.code === 'KeyA' || e.code === 'ArrowLeft') hookVelX = -HOOK_H_SPEED;\n                if (e.code === 'KeyD' || e.code === 'ArrowRight') hookVelX = HOOK_H_SPEED;\n                if (e.code === 'Escape') {\n                    if (shopOpen) closeShop();\n                    else if (leaderboardOpen) closeLeaderboard();\n                    else if (journalOpen) closeJournal();\n                    else if (halloweenPanelOpen) closeHalloweenEvent();\n                    else if (isMainMenuActive) { isMainMenuActive=false; document.getElementById('halloween-menu')?.classList.add('hidden'); showCastPrompt(true); }\n                    else if (gameState === STATE_IDLE) openShop();\n                }\n            });\n            document.addEventListener('keyup', (e) => {\n                if (e.code === 'KeyA' || e.code === 'ArrowLeft') { if (hookVelX < 0) hookVelX = 0; }\n                if (e.code === 'KeyD' || e.code === 'ArrowRight') { if (hookVelX > 0) hookVelX = 0; }\n            });\n        }\n\n        // \ud83c\udf19 Night Dive: Sonar activation\n        function activateSonar() {\n            sonarActive = true;\n            sonarTimer = SONAR_DURATION;\n            sonarCooldown = SONAR_COOLDOWN;\n            playSonarSfx();\n            shakeIntensity = 3;\n            // Create sonar rings from hook position\n            sonarRings.push({ x: hookX, y: hookY, radius: 0, maxRadius: 300, life: 1 });\n            spawnFloatingText(hookX, hookY - 30, '\ud83d\udce1 SONAR!', '#64B5F6', 26);\n        }\n\n        function playSonarSfx() {\n            try {\n                const ctx = getAudioCtx();\n                const osc = ctx.createOscillator();\n                const gain = ctx.createGain();\n                osc.type = 'sine';\n                osc.frequency.setValueAtTime(1200, ctx.currentTime);\n                osc.frequency.exponentialRampToValueAtTime(400, ctx.currentTime + 0.5);\n                gain.gain.setValueAtTime(0.12, ctx.currentTime);\n                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.6);\n                osc.connect(gain);\n                gain.connect(ctx.destination);\n                osc.start();\n                osc.stop(ctx.currentTime + 0.6);\n            } catch(e) {}\n        }\n\n        // \ud83c\udf0b Volcanic Trench: deep rumbling eruption sound\n        function playRumbleSfx() {\n            try {\n                const ctx = getAudioCtx();\n                const bufferSize = ctx.sampleRate * 1.4;\n                const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);\n                const data = buffer.getChannelData(0);\n                for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * 0.6;\n                const source = ctx.createBufferSource();\n                source.buffer = buffer;\n                const filter = ctx.createBiquadFilter();\n                filter.type = 'lowpass';\n                filter.frequency.setValueAtTime(220, ctx.currentTime);\n                filter.frequency.exponentialRampToValueAtTime(70, ctx.currentTime + 1.3);\n                const gain = ctx.createGain();\n                gain.gain.setValueAtTime(0.001, ctx.currentTime);\n                gain.gain.exponentialRampToValueAtTime(0.16, ctx.currentTime + 0.25);\n                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 1.4);\n                source.connect(filter);\n                filter.connect(gain);\n                gain.connect(ctx.destination);\n                source.start();\n            } catch(e) {}\n        }\n\n        // \ud83c\udf0b Volcanic Trench: sizzling hiss when the hook is scorched\n        function playSizzleSfx() {\n            try {\n                const ctx = getAudioCtx();\n                const bufferSize = ctx.sampleRate * 0.5;\n                const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);\n                const data = buffer.getChannelData(0);\n                for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * 0.5;\n                const source = ctx.createBufferSource();\n                source.buffer = buffer;\n                const filter = ctx.createBiquadFilter();\n                filter.type = 'highpass';\n                filter.frequency.value = 2200;\n                const gain = ctx.createGain();\n                gain.gain.setValueAtTime(0.12, ctx.currentTime);\n                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.5);\n                source.connect(filter);\n                filter.connect(gain);\n                gain.connect(ctx.destination);\n                source.start();\n            } catch(e) {}\n        }\n\n        function startCast() {\n            gameState = STATE_CASTING;\n            hookX = 360;\n            hookY = WATER_SURFACE_Y;\n            hookDepthMeters = 0;\n            cameraY = 0;\n            splashTimer = 0.5;\n            splashX = hookX;\n            showCastPrompt(false);\n            playCastSfx();\n\n            // \ud83c\udf19 Night Dive: Track casts and trigger night mode\n            castCount++;\n            if (!nightMode && castCount % NIGHT_EVERY_N_CASTS === 0) {\n                nightMode = true;\n                nightModeTimer = NIGHT_DURATION;\n                spawnFloatingText(360, WATER_SURFACE_Y - 100, '\ud83c\udf19 Night Falls...', '#B388FF', 32);\n                spawnParticles(360, WATER_SURFACE_Y - 60, '#7C4DFF', 15, { spread: 300, upward: 80, shape: 'star', size: 5 });\n                playSonarSfx();\n                generateLevel(); // Regenerate with night fish\n            }\n\n            // \ud83c\udf0b Volcanic Trench: Trigger eruption on a cycle\n            if (ERUPTIONS_ENABLED && !eruptionActive && castCount % ERUPTION_EVERY_N_CASTS === 0) {\n                eruptionActive = true;\n                eruptionTimer = ERUPTION_DURATION;\n                spawnFloatingText(360, WATER_SURFACE_Y - 130, '\ud83c\udf0b THE TRENCH ERUPTS!', '#FF5722', 32);\n                spawnParticles(360, WATER_SURFACE_Y - 60, '#FF7043', 18, { spread: 320, upward: 90, shape: 'star', size: 6 });\n                shakeIntensity = 16;\n                playRumbleSfx();\n                generateLevel(); // Regenerate with magma fish, lava bubbles and gems\n            }\n\n            // \ud83c\udf0b Volcanic Trench: Heat Shield \u2014 protects against one lava bubble\n            if (eruptionActive && !heatShield && Math.random() < 0.4) {\n                heatShield = true;\n                heatShieldTimer = HEAT_SHIELD_DURATION;\n                spawnFloatingText(360, WATER_SURFACE_Y - 40, '\ud83d\udee1\ufe0f Heat Shield!', '#4FC3F7', 28);\n                spawnParticles(360, WATER_SURFACE_Y, '#4FC3F7', 12, { spread: 160, upward: 70, size: 4 });\n            }\n\n            // Lucky Lure power-up \u2014 15% chance per cast\n            luckyLure = Math.random() < 0.15;\n            if (luckyLure) {\n                spawnFloatingText(360, WATER_SURFACE_Y - 40, '\u2728 Lucky Lure!', '#FFD54F', 28);\n                spawnParticles(360, WATER_SURFACE_Y, '#FFD54F', 10, { spread: 120, upward: 80, shape: 'star', size: 5 });\n            }\n\n            // World Cup 2026: Golden Boot power-up \u2014 10% chance per cast\n            if (!goldenBootActive && Math.random() < 0.10) {\n                goldenBootActive = true;\n                goldenBootTimer = GOLDEN_BOOT_DURATION;\n                spawnFloatingText(360, WATER_SURFACE_Y - 80, '\ud83d\udc5f Golden Boot! \u00d73 Coins!', '#FFD700', 30);\n                spawnParticles(360, WATER_SURFACE_Y - 40, '#FFD700', 15, { spread: 200, upward: 100, shape: 'star', size: 6 });\n                playGoalSfx();\n            }\n\n            setTimeout(() => { gameState = STATE_DESCENDING; }, 300);\n        }\n\n        function resetCast() {\n            gameState = STATE_IDLE;\n            hookX = 360;\n            hookY = WATER_SURFACE_Y;\n            hookDepthMeters = 0;\n            cameraY = 0;\n            caughtFish = null;\n            reelProgress = 0;\n            showCastPrompt(true);\n            showReelBar(false);\n            generateLevel();\n        }\n\n        function catchFishAction(fish) {\n            gameState = STATE_REELING;\n            caughtFish = fish;\n            fish.caught = true;\n            reelProgress = 0;\n            reelTarget = fish.type.isBoss ? 3 : 1 + (fish.type.coins / 20);\n            showReelBar(true);\n            playCatchSfx();\n            shakeIntensity = fish.type.isBoss ? 15 : 8;\n            spawnParticles(hookX, hookY, '#4FC3F7', 12, { spread: 180, upward: 50 });\n            spawnParticles(hookX, hookY, '#FFFFFF', 6, { spread: 100, size: 3 });\n            if (fish.type.isBoss) {\n                spawnParticles(hookX, hookY, '#FFD700', 20, { spread: 300, upward: 100, shape: 'star', size: 8 });\n                spawnFloatingText(hookX, hookY - 40, fish.type.isHalloweenBoss ? '\ud83c\udf83 PHANTOM LEVIATHAN HOOKED!' : fish.type.isWorldCup ? '\ud83c\udfc6 TROPHY HOOKED!' : fish.type.isNightDive ? '\ud83d\udc7b GHOST WHALE HOOKED!' : fish.type.isVolcanic ? '\ud83d\udc19 KRAKEN HOOKED!' : '\u26a1 BOSS HOOKED!', fish.type.isHalloween ? '#FF9A3C' : '#FF5722', 34);\n            }\n            // \ud83c\udf0b Volcanic Trench: molten burst for magma catches\n            if (fish.type.isVolcanic && !fish.type.isBoss) {\n                spawnParticles(hookX, hookY, '#FF7043', 12, { spread: 220, upward: 80, shape: 'star', size: 5 });\n                spawnFloatingText(hookX, hookY - 40, '\ud83d\udd25 Magma Catch!', '#FFAB91', 28);\n            }\n            // \ud83c\udf19 Night Dive: Glow burst for bioluminescent catches\n            if (fish.type.isNightDive && !fish.type.isBoss) {\n                spawnParticles(hookX, hookY, '#B388FF', 12, { spread: 200, upward: 80, shape: 'star', size: 5 });\n                spawnFloatingText(hookX, hookY - 40, '\ud83c\udf19 Night Catch!', '#CE93D8', 28);\n            }\n            // World Cup 2026: Goal celebration for WC fish\n            if (fish.type.isWorldCup) {\n                goalCelebrationTimer = 2.5;\n                spawnParticles(hookX, hookY, '#00BF57', 10, { spread: 200, upward: 80, shape: 'star', size: 6 });\n                spawnParticles(hookX, hookY, '#FFD700', 10, { spread: 200, upward: 80, shape: 'star', size: 6 });\n                // Spawn confetti\n                for (let i = 0; i < 25; i++) {\n                    worldCupConfetti.push({\n                        x: Math.random() * canvas.width,\n                        y: -20 - Math.random() * 100,\n                        vx: (Math.random() - 0.5) * 80,\n                        vy: 100 + Math.random() * 150,\n                        size: 4 + Math.random() * 6,\n                        color: ['#FFD700','#00BF57','#EF3340','#FFFFFF','#0057B7'][Math.floor(Math.random()*5)],\n                        rotation: Math.random() * Math.PI * 2,\n                        rotSpeed: (Math.random() - 0.5) * 8,\n                        life: 3\n                    });\n                }\n                playGoalSfx();\n            }\n            ripples.push({ x: hookX, y: hookY, radius: 0, maxRadius: 80, life: 1 });\n        }\n\n        // World Cup 2026: Goal sound effect\n        function playGoalSfx() {\n            // Stadium horn / vuvuzela style\n            try {\n                const ctx = getAudioCtx();\n                const osc1 = ctx.createOscillator();\n                const osc2 = ctx.createOscillator();\n                const gain = ctx.createGain();\n                osc1.type = 'sawtooth';\n                osc1.frequency.setValueAtTime(233, ctx.currentTime);\n                osc2.type = 'sawtooth';\n                osc2.frequency.setValueAtTime(466, ctx.currentTime);\n                gain.gain.setValueAtTime(0.08, ctx.currentTime);\n                gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.8);\n                osc1.connect(gain);\n                osc2.connect(gain);\n                gain.connect(ctx.destination);\n                osc1.start();\n                osc2.start();\n                osc1.stop(ctx.currentTime + 0.8);\n                osc2.stop(ctx.currentTime + 0.8);\n            } catch(e) {}\n            // Crowd roar\n            setTimeout(() => {\n                try {\n                    const ctx = getAudioCtx();\n                    const bufferSize = ctx.sampleRate * 0.4;\n                    const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);\n                    const data = buffer.getChannelData(0);\n                    for (let i = 0; i < bufferSize; i++) data[i] = (Math.random() * 2 - 1) * 0.5;\n                    const source = ctx.createBufferSource();\n                    source.buffer = buffer;\n                    const filter = ctx.createBiquadFilter();\n                    filter.type = 'lowpass';\n                    filter.frequency.value = 800;\n                    const gain = ctx.createGain();\n                    gain.gain.setValueAtTime(0.06, ctx.currentTime);\n                    gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.6);\n                    source.connect(filter);\n                    filter.connect(gain);\n                    gain.connect(ctx.destination);\n                    source.start();\n                } catch(e) {}\n            }, 200);\n        }\n\n        // Lightweight decorative Halloween dressing; bounded particles and reused geometry keep mobile rendering smooth.\n        let timeNow = 0;\n        function updateAmbientEvent(dt) {\n            halloweenMotes.forEach(p => { p.y -= p.speed * dt; if (p.y < 180) p.y = canvas.height + 20; });\n        }\n        function drawHauntedWorld(time, dt) {\n            const w=canvas.width,h=canvas.height;\n            if (mode === 'play' && isMainMenuActive) {\n                const menuGlow=ctx.createRadialGradient(w*0.5,h*0.48,30,w*0.5,h*0.48,Math.max(w,h)*0.65);\n                menuGlow.addColorStop(0,'rgba(130,56,166,0.20)'); menuGlow.addColorStop(1,'rgba(5,6,28,0.34)');\n                ctx.fillStyle=menuGlow; ctx.fillRect(0,0,w,h);\n            }\n            const fog=ctx.createLinearGradient(0,WATER_SURFACE_Y-cameraY,0,h);\n            fog.addColorStop(0,'rgba(92,42,139,0.12)'); fog.addColorStop(0.55,'rgba(67,27,117,0.20)'); fog.addColorStop(1,'rgba(18,8,47,0.43)');\n            ctx.fillStyle=fog; ctx.fillRect(0,Math.max(0,WATER_SURFACE_Y-cameraY),w,h);\n            [520,1050,1650,2300].forEach((worldY,i)=>{\n                const y=worldY-cameraY; if(y < -220 || y > h+220) return;\n                const x=i%2?38:w-138;\n                ctx.save(); ctx.globalAlpha=0.34; ctx.fillStyle='#171c3e'; ctx.strokeStyle='#7770a6'; ctx.lineWidth=4;\n                ctx.fillRect(x,y-110,86,220); ctx.strokeRect(x,y-110,86,220);\n                for(let k=0;k<3;k++) ctx.fillRect(x+12+k*24,y-70,12,18);\n                ctx.beginPath(); ctx.moveTo(x-18,y-110); ctx.lineTo(x+43,y-150); ctx.lineTo(x+104,y-110); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore();\n            });\n            [360,900,1480,2140].forEach((worldY,i)=>{\n                const y=worldY-cameraY; if(y < -90 || y > h+90) return;\n                const x=i%2?92:w-92,pulse=0.75+0.25*Math.sin(time*0.003+i);\n                ctx.save(); ctx.shadowColor='#ff7b2e'; ctx.shadowBlur=22*pulse; ctx.fillStyle='#f47a26';\n                ctx.beginPath(); ctx.ellipse(x,y,30,25,0,0,Math.PI*2); ctx.fill(); ctx.fillStyle='#372045';\n                ctx.beginPath(); ctx.moveTo(x-13,y-4); ctx.lineTo(x-5,y+5); ctx.lineTo(x-16,y+5); ctx.closePath(); ctx.fill();\n                ctx.beginPath(); ctx.moveTo(x+13,y-4); ctx.lineTo(x+5,y+5); ctx.lineTo(x+16,y+5); ctx.closePath(); ctx.fill();\n                ctx.fillRect(x-9,y+10,18,4); ctx.fillStyle='#76a75a'; ctx.fillRect(x-3,y-31,6,9); ctx.restore();\n            });\n            ctx.save(); halloweenMotes.forEach(p=>{\n                if (mode === 'edit') { p.y -= p.speed * dt; if (p.y < 180) p.y = h + 20; }\n                const sy=p.y-cameraY*0.12;\n                const x=p.x+Math.sin(time*0.0007+p.phase)*10; if(sy<0||sy>h)return;\n                ctx.globalAlpha=0.3+0.25*Math.sin(time*0.002+p.phase); ctx.fillStyle=p.color; ctx.beginPath(); ctx.arc(x,sy,p.size,0,Math.PI*2); ctx.fill();\n            }); ctx.restore();\n        }\n        function drawBackground(dt) {\n            const w = canvas.width, h = canvas.height;\n\n            // Atmospheric Halloween night sky visible above the waterline.\n            const skyGrad = ctx.createLinearGradient(0, 0, 0, Math.max(1, WATER_SURFACE_Y - cameraY));\n            skyGrad.addColorStop(0, HALLOWEEN_EVENT_ACTIVE ? '#100d2e' : '#87CEEB');\n            skyGrad.addColorStop(1, HALLOWEEN_EVENT_ACTIVE ? '#45245f' : '#B3E5FC');\n            ctx.fillStyle = skyGrad;\n            ctx.fillRect(0, 0, w, Math.max(0, WATER_SURFACE_Y - cameraY));\n\n            // Water gradient\n            let waterColor1 = '#4FC3F7', waterColor2 = '#1E3A5F';\n            if (config.settings.waterColor === 'dark_blue') { waterColor1 = '#2196F3'; waterColor2 = '#0D1B2A'; }\n            else if (config.settings.waterColor === 'green') { waterColor1 = '#4DB6AC'; waterColor2 = '#1B5E20'; }\n            else if (config.settings.waterColor === 'murky') { waterColor1 = '#8D6E63'; waterColor2 = '#3E2723'; }\n\n            if (HALLOWEEN_EVENT_ACTIVE) { waterColor1 = '#152e68'; waterColor2 = '#080d2d'; }\n            const waterTop = Math.max(0, WATER_SURFACE_Y - cameraY);\n            const waterGrad = ctx.createLinearGradient(0, waterTop, 0, h);\n            waterGrad.addColorStop(0, waterColor1);\n            waterGrad.addColorStop(0.48, HALLOWEEN_EVENT_ACTIVE ? '#18224d' : waterColor2);\n            waterGrad.addColorStop(1, waterColor2);\n            ctx.fillStyle = waterGrad;\n            ctx.fillRect(0, waterTop, w, h - waterTop);\n\n            // Water surface\n            const surfaceImg = assetCache['water_surface'];\n            if (surfaceImg) {\n                const surfY = WATER_SURFACE_Y - cameraY - 50;\n                ctx.globalAlpha = 0.8;\n                ctx.drawImage(surfaceImg, 0, surfY, w, 100);\n                ctx.globalAlpha = 1;\n            }\n\n            // Sandy bottom\n            const bottomY = WATER_SURFACE_Y + getMaxDepth() * METERS_TO_PX - cameraY;\n            const bottomImg = assetCache['sandy_bottom'];\n            if (bottomImg && bottomY < h + 150) {\n                ctx.drawImage(bottomImg, 0, bottomY - 30, w, 150);\n            }\n\n            if (HALLOWEEN_EVENT_ACTIVE) drawHauntedWorld(timeNow, dt);\n\n            // Deep zone darkness overlay\n            if (cameraY > 600) {\n                const darkAlpha = Math.min(0.4, (cameraY - 600) / 2000);\n                ctx.fillStyle = `rgba(5,5,30,${darkAlpha})`;\n                ctx.fillRect(0, 0, w, h);\n            }\n\n            // Eerie soft lighting shafts instead of daylight sunrays.\n            ctx.save();\n            ctx.globalAlpha = HALLOWEEN_EVENT_ACTIVE ? 0.055 : 0.05;\n            for (let i = 0; i < 5; i++) {\n                const rx = 100 + i * 150 + (HALLOWEEN_EVENT_ACTIVE ? Math.sin(timeNow * 0.00025 + i) * 18 : 0);\n                ctx.beginPath(); ctx.moveTo(rx, waterTop); ctx.lineTo(rx - 40, h); ctx.lineTo(rx + 40, h); ctx.closePath();\n                ctx.fillStyle = HALLOWEEN_EVENT_ACTIVE ? (i % 2 ? '#B38BFF' : '#FFAC5E') : '#FFFFFF'; ctx.fill();\n            }\n            ctx.restore();\n        }\n\n        function drawBubbles(dt) {\n            ctx.fillStyle = 'rgba(255,255,255,0.4)';\n            bubbles.forEach(b => {\n                b.y -= b.speed * dt;\n                b.wobble += dt * 2;\n                const drawX = b.x + Math.sin(b.wobble) * 5;\n                const drawY = b.y - cameraY;\n                if (b.y < WATER_SURFACE_Y) {\n                    b.y = WATER_SURFACE_Y + getMaxDepth() * METERS_TO_PX * Math.random();\n                    b.x = 50 + Math.random() * 620;\n                }\n                if (drawY > 0 && drawY < canvas.height) {\n                    ctx.beginPath();\n                    ctx.arc(drawX, drawY, b.size, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n            });\n        }\n\n        function drawBoat() {\n            const boatImg = assetCache['fishing_boat'];\n            if (!boatImg) return;\n            const boatY = WATER_SURFACE_Y - cameraY - 100;\n            if (boatY > -150) {\n                const aspect = boatImg.naturalWidth / boatImg.naturalHeight;\n                const bw = 180;\n                const bh = bw / aspect;\n                // Boat bobbing\n                const bob = Math.sin(Date.now() * 0.001) * 4;\n                const tilt = Math.sin(Date.now() * 0.0008) * 0.03;\n                ctx.save();\n                ctx.translate(360, boatY + bh/2 + bob);\n                ctx.rotate(tilt);\n                ctx.drawImage(boatImg, -bw/2, -bh/2, bw, bh);\n                ctx.restore();\n            }\n        }\n\n        function drawFishingLine() {\n            if (gameState === STATE_IDLE) return;\n            const startX = 360;\n            const startY = Math.max(0, WATER_SURFACE_Y - cameraY - 60);\n            const endX = hookX;\n            const endY = hookY - cameraY;\n\n            ctx.beginPath();\n            ctx.moveTo(startX, startY);\n            ctx.lineTo(endX, endY);\n            ctx.strokeStyle = 'rgba(200,200,200,0.8)';\n            ctx.lineWidth = 2;\n            ctx.stroke();\n        }\n\n        function drawHookTrail() {\n            if (hookTrail.length < 2) return;\n            ctx.save();\n            for (let i = 0; i < hookTrail.length; i++) {\n                const t = hookTrail[i];\n                const alpha = (i / hookTrail.length) * 0.32;\n                ctx.globalAlpha = alpha;\n                ctx.fillStyle = i % 2 ? '#B3E5FC' : '#FFFFFF';\n                ctx.beginPath();\n                ctx.arc(t.x, t.y - cameraY, 3 + (i / hookTrail.length) * 5, 0, Math.PI * 2);\n                ctx.fill();\n            }\n            ctx.restore();\n        }\n\n        function drawHook() {\n            if (gameState === STATE_IDLE) return;\n            const hookImg = assetCache['fishing_hook'];\n            const drawY = hookY - cameraY;\n            // Wobble animation while descending\n            const wobble = gameState === STATE_DESCENDING ? Math.sin(Date.now() * 0.005) * 3 : 0;\n            const rotation = gameState === STATE_DESCENDING ? Math.sin(Date.now() * 0.003) * 0.15 : 0;\n            if (hookImg) {\n                const aspect = hookImg.naturalWidth / hookImg.naturalHeight;\n                const hw = 40;\n                const hh = hw / aspect;\n                ctx.save();\n                if (halloweenEvent.spookyRodUnlocked) { ctx.shadowColor = '#c58aff'; ctx.shadowBlur = 20; }\n                ctx.translate(hookX + wobble, drawY);\n                ctx.rotate(rotation);\n                ctx.drawImage(hookImg, -hw/2, -hh/2, hw, hh);\n                ctx.restore();\n            } else {\n                ctx.fillStyle = '#C0C0C0';\n                ctx.beginPath();\n                ctx.arc(hookX + wobble, drawY, 8, 0, Math.PI * 2);\n                ctx.fill();\n            }\n        }\n\n        function drawFishes(dt, time) {\n            fishes.forEach(f => {\n                if (f.caught) return;\n                // Animate\n                f.x = f.startX + Math.sin(time * 0.001 * (f.speed / 60) + f.phase) * f.amplitude;\n                f.x = Math.max(30, Math.min(690, f.x));\n                const dir = Math.cos(time * 0.001 * (f.speed / 60) + f.phase) > 0 ? 1 : -1;\n\n                const drawX = f.x;\n                const drawY = f.y - cameraY;\n                if (drawY < -100 || drawY > canvas.height + 100) return;\n\n                const img = assetCache[f.type.asset];\n                if (img) {\n                    const aspect = img.naturalWidth / img.naturalHeight;\n                    const fw = f.type.width;\n                    const fh = fw / aspect;\n                    // Vertical bob animation\n                    const bob = Math.sin(time * 0.003 + f.phase * 3) * 4;\n                    ctx.save();\n                    ctx.translate(drawX, drawY + bob);\n                    if ((dir > 0 && !f.type.flip) || (dir < 0 && f.type.flip)) ctx.scale(-1, 1);\n                    // Tail wiggle via slight skew\n                    const wiggle = Math.sin(time * 0.008 + f.phase) * 0.05;\n                    ctx.transform(1, wiggle, 0, 1, 0, 0);\n                    ctx.drawImage(img, -fw/2, -fh/2, fw, fh);\n                    ctx.restore();\n                } else {\n                    ctx.fillStyle = '#FF6B35';\n                    ctx.beginPath();\n                    ctx.ellipse(drawX, drawY, f.type.width/2, f.type.height/2, 0, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n            });\n        }\n\n        function drawObstacles() {\n            obstacles.forEach(o => {\n                const drawX = o.x;\n                const drawY = o.y - cameraY;\n                if (drawY < -100 || drawY > canvas.height + 100) return;\n\n                let img;\n                if (o.type === 'boot') img = assetCache['obstacle_boot'];\n                else if (o.type === 'can') img = assetCache['obstacle_tin_can'];\n                else if (o.type === 'soccerball') img = assetCache['obstacle_soccerball'];\n                else if (o.type === 'seaweed') img = assetCache['seaweed_patch'];\n\n                if (img) {\n                    const aspect = img.naturalWidth / img.naturalHeight;\n                    const ow = o.width;\n                    const oh = ow / aspect;\n                    // Soccer balls slowly spin\n                    if (o.type === 'soccerball') {\n                        ctx.save();\n                        ctx.translate(drawX, drawY);\n                        ctx.rotate(Date.now() * 0.001);\n                        ctx.drawImage(img, -ow/2, -oh/2, ow, oh);\n                        ctx.restore();\n                    } else {\n                        ctx.drawImage(img, drawX - ow/2, drawY - oh/2, ow, oh);\n                    }\n                } else {\n                    ctx.fillStyle = o.type === 'seaweed' ? '#2E7D32' : o.type === 'soccerball' ? '#FFFFFF' : '#795548';\n                    if (o.type === 'soccerball') {\n                        ctx.beginPath();\n                        ctx.arc(drawX, drawY, o.width/2, 0, Math.PI * 2);\n                        ctx.fill();\n                        ctx.strokeStyle = '#000';\n                        ctx.lineWidth = 2;\n                        ctx.stroke();\n                    } else {\n                        ctx.fillRect(drawX - o.width/2, drawY - o.height/2, o.width, o.height);\n                    }\n                }\n            });\n        }\n\n        function drawSplash(dt) {\n            if (splashTimer > 0) {\n                splashTimer -= dt;\n                const splashImg = assetCache['splash_effect'];\n                const sy = WATER_SURFACE_Y - cameraY;\n                if (splashImg) {\n                    const alpha = splashTimer / 0.5;\n                    ctx.globalAlpha = alpha;\n                    const aspect = splashImg.naturalWidth / splashImg.naturalHeight;\n                    const sw = 120 + (1 - alpha) * 60;\n                    const sh = sw / aspect;\n                    ctx.drawImage(splashImg, splashX - sw/2, sy - sh/2, sw, sh);\n                    ctx.globalAlpha = 1;\n                }\n                // Splash particles\n                if (splashTimer > 0.4) {\n                    spawnParticles(splashX, sy, '#B3E5FC', 2, { spread: 150, upward: 100, size: 4, gravity: 300 });\n                }\n            }\n        }\n\n        function drawParticles(dt) {\n            for (let i = particles.length - 1; i >= 0; i--) {\n                const p = particles[i];\n                p.life -= p.decay * dt;\n                if (p.life <= 0) { particles.splice(i, 1); continue; }\n                p.x += p.vx * dt;\n                p.y += p.vy * dt;\n                p.vy += p.gravity * dt;\n                ctx.globalAlpha = p.life;\n                ctx.fillStyle = p.color;\n                if (p.shape === 'star') {\n                    ctx.save();\n                    ctx.translate(p.x, p.y);\n                    ctx.rotate(p.life * 5);\n                    ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);\n                    ctx.restore();\n                } else {\n                    ctx.beginPath();\n                    ctx.arc(p.x, p.y - cameraY, p.size * p.life, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n                ctx.globalAlpha = 1;\n            }\n        }\n\n        function drawFloatingTexts(dt) {\n            for (let i = floatingTexts.length - 1; i >= 0; i--) {\n                const t = floatingTexts[i];\n                t.life -= dt * 0.8;\n                if (t.life <= 0) { floatingTexts.splice(i, 1); continue; }\n                t.y += t.vy * dt;\n                ctx.globalAlpha = t.life;\n                ctx.font = `bold ${t.size}px Fredoka`;\n                ctx.fillStyle = t.color;\n                ctx.textAlign = 'center';\n                ctx.fillText(t.text, t.x, t.y - cameraY);\n                ctx.globalAlpha = 1;\n            }\n        }\n\n        function applyScreenShake(dt) {\n            if (shakeIntensity > 0.1) {\n                const sx = (Math.random() - 0.5) * shakeIntensity;\n                const sy = (Math.random() - 0.5) * shakeIntensity;\n                ctx.translate(sx, sy);\n                shakeIntensity *= Math.exp(-shakeDecay * dt);\n            } else {\n                shakeIntensity = 0;\n            }\n        }\n\n        function drawCaughtFish() {\n            if (!caughtFish || gameState !== STATE_REELING) return;\n            const img = assetCache[caughtFish.type.asset];\n            const drawX = hookX;\n            const drawY = hookY - cameraY + 30;\n            if (img) {\n                const aspect = img.naturalWidth / img.naturalHeight;\n                const fw = caughtFish.type.width * 0.8;\n                const fh = fw / aspect;\n                ctx.save();\n                ctx.translate(drawX, drawY);\n                // Wiggle\n                ctx.rotate(Math.sin(Date.now() * 0.01) * 0.15);\n                ctx.drawImage(img, -fw/2, -fh/2, fw, fh);\n                ctx.restore();\n            }\n        }\n\n        // Update logic\n        function update(dt, time) {\n            if (isMainMenuActive) { updateAmbientEvent(dt); return; }\n            if (mode === 'edit' || shopOpen || leaderboardOpen || journalOpen || halloweenPanelOpen || catchPopupVisible) return;\n\n            // Combo timer countdown\n            if (comboTimer > 0) {\n                comboTimer -= dt;\n                if (comboTimer <= 0) { comboCount = 0; }\n            }\n\n            // World Cup 2026: Golden Boot timer\n            if (goldenBootActive) {\n                goldenBootTimer -= dt;\n                if (goldenBootTimer <= 0) {\n                    goldenBootActive = false;\n                    goldenBootTimer = 0;\n                }\n            }\n\n            // \ud83c\udf19 Night Dive: Night mode timer and transitions\n            if (nightMode) {\n                nightModeTimer -= dt;\n                nightTransition = Math.min(1, nightTransition + dt * 0.5);\n                if (nightModeTimer <= 0) {\n                    nightMode = false;\n                    spawnFloatingText(360, 200, '\u2600\ufe0f Dawn breaks!', '#FFD54F', 30);\n                }\n            } else {\n                nightTransition = Math.max(0, nightTransition - dt * 0.3);\n            }\n\n            // \ud83c\udf0b Volcanic Trench: eruption timers and ambience\n            if (eruptionActive) {\n                eruptionTimer -= dt;\n                eruptionIntensity = Math.min(1, eruptionIntensity + dt * 0.8);\n                ventGlowPhase += dt * 3;\n                // Constant faint rumble shake\n                if (shakeIntensity < 2) shakeIntensity = 1.5 + Math.random();\n                // Ember sparks drifting upward\n                if (Math.random() < 0.5) {\n                    emberSparks.push({\n                        x: 20 + Math.random() * (canvas.width - 40),\n                        y: WATER_SURFACE_Y + getMaxDepth() * METERS_TO_PX * (0.3 + Math.random() * 0.7),\n                        size: 1.5 + Math.random() * 3,\n                        vy: -25 - Math.random() * 45,\n                        wobble: Math.random() * Math.PI * 2,\n                        life: 1\n                    });\n                }\n                if (eruptionTimer <= 0) {\n                    eruptionActive = false;\n                    lavaBubbles = [];\n                    spawnFloatingText(360, 240, '\ud83c\udf0b The trench cools down...', '#FFAB91', 28);\n                }\n            } else {\n                eruptionIntensity = Math.max(0, eruptionIntensity - dt * 0.5);\n            }\n\n            for (let i = emberSparks.length - 1; i >= 0; i--) {\n                const s = emberSparks[i];\n                s.y += s.vy * dt;\n                s.wobble += dt * 3;\n                s.life -= dt * 0.5;\n                if (s.life <= 0 || s.y < WATER_SURFACE_Y) emberSparks.splice(i, 1);\n            }\n\n            // \ud83c\udf0b Heat Shield timer\n            if (heatShield) {\n                heatShieldTimer -= dt;\n                if (heatShieldTimer <= 0) heatShield = false;\n            }\n\n            // \ud83c\udf0b Volcanic Trench: lava bubbles rise and pop at the surface\n            for (let i = lavaBubbles.length - 1; i >= 0; i--) {\n                const b = lavaBubbles[i];\n                b.y -= b.speed * dt;\n                b.wobble += b.wobbleSpeed * dt;\n                if (b.y < WATER_SURFACE_Y + 20) {\n                    ripples.push({ x: b.x, y: WATER_SURFACE_Y, radius: 0, maxRadius: 40, life: 1 });\n                    spawnParticles(b.x, WATER_SURFACE_Y, '#FF7043', 4, { spread: 120, upward: 60, size: 4, gravity: 200 });\n                    // Recycle from the bottom while the eruption lasts\n                    if (eruptionActive) {\n                        b.y = WATER_SURFACE_Y + getMaxDepth() * METERS_TO_PX + Math.random() * 200;\n                        b.x = 60 + Math.random() * 600;\n                    } else {\n                        lavaBubbles.splice(i, 1);\n                    }\n                }\n            }\n\n            // \ud83c\udf19 Night Dive: Sonar timer\n            if (sonarActive) {\n                sonarTimer -= dt;\n                if (sonarTimer <= 0) sonarActive = false;\n            }\n            if (sonarCooldown > 0) sonarCooldown -= dt;\n\n            // \ud83c\udf19 Night Dive: Update sonar rings\n            for (let i = sonarRings.length - 1; i >= 0; i--) {\n                sonarRings[i].radius += 250 * dt;\n                sonarRings[i].life -= dt * 0.8;\n                if (sonarRings[i].life <= 0) sonarRings.splice(i, 1);\n            }\n\n            // \ud83c\udf19 Night Dive: Glow particles for bioluminescent fish\n            if ((nightMode || eruptionActive) && Math.random() < 0.1) {\n                fishes.forEach(f => {\n                    if (f.type.glows && !f.caught && Math.random() < 0.03) {\n                        glowParticles.push({\n                            x: f.x + (Math.random() - 0.5) * 30,\n                            y: f.y + (Math.random() - 0.5) * 20,\n                            size: 2 + Math.random() * 3,\n                            life: 1,\n                            color: f.type.id === 'lanternfish' ? '#00E5FF' : f.type.id === 'firefly_squid' ? '#E040FB' : f.type.isVolcanic ? '#FF7043' : '#B388FF'\n                        });\n                    }\n                });\n            }\n            for (let i = glowParticles.length - 1; i >= 0; i--) {\n                glowParticles[i].life -= dt * 1.2;\n                glowParticles[i].y -= 10 * dt;\n                if (glowParticles[i].life <= 0) glowParticles.splice(i, 1);\n            }\n\n            // Update sonar btn visual\n            const sonarBtn = document.getElementById('sonar-btn');\n            if (sonarBtn) {\n                if (sonarCooldown > 0) {\n                    sonarBtn.style.opacity = '0.5';\n                    sonarBtn.textContent = Math.ceil(sonarCooldown) + 's';\n                } else {\n                    sonarBtn.style.opacity = gameState === STATE_DESCENDING ? '1' : '0.4';\n                    sonarBtn.textContent = '\ud83d\udce1';\n                }\n            }\n\n            // World Cup 2026: Goal celebration timer\n            if (goalCelebrationTimer > 0) {\n                goalCelebrationTimer -= dt;\n            }\n\n            // World Cup 2026: Update confetti\n            for (let i = worldCupConfetti.length - 1; i >= 0; i--) {\n                const c = worldCupConfetti[i];\n                c.x += c.vx * dt;\n                c.y += c.vy * dt;\n                c.rotation += c.rotSpeed * dt;\n                c.life -= dt;\n                if (c.life <= 0 || c.y > canvas.height + 50) {\n                    worldCupConfetti.splice(i, 1);\n                }\n            }\n\n            // World Cup 2026: Stadium wave effect\n            stadiumWave += dt * 2;\n\n            // Weather system\n            weatherTimer += dt;\n            if (weatherTimer >= weatherDuration) {\n                weatherTimer = 0;\n                const weathers = ['clear', 'clear', 'rain', 'foggy'];\n                currentWeather = weathers[Math.floor(Math.random() * weathers.length)];\n                weatherDuration = 20 + Math.random() * 20;\n            }\n            if (currentWeather === 'rain' && Math.random() < 0.3) {\n                raindrops.push({ x: Math.random() * 720, y: -10, speed: 400 + Math.random() * 200 });\n            }\n            for (let i = raindrops.length - 1; i >= 0; i--) {\n                raindrops[i].y += raindrops[i].speed * dt;\n                if (raindrops[i].y > WATER_SURFACE_Y) {\n                    ripples.push({ x: raindrops[i].x, y: WATER_SURFACE_Y, radius: 0, maxRadius: 20, life: 1 });\n                    raindrops.splice(i, 1);\n                }\n            }\n\n            // Update ripples\n            for (let i = ripples.length - 1; i >= 0; i--) {\n                ripples[i].radius += 60 * dt;\n                ripples[i].life -= dt * 1.5;\n                if (ripples[i].life <= 0) ripples.splice(i, 1);\n            }\n\n            // ANIMATION UPDATE: leave a luminous trail behind the moving hook\n            if (gameState === STATE_DESCENDING || gameState === STATE_REELING) {\n                hookTrail.push({ x: hookX, y: hookY });\n                if (hookTrail.length > 14) hookTrail.shift();\n                ambientSparkTimer -= dt;\n                if (ambientSparkTimer <= 0) {\n                    ambientSparkTimer = 0.12;\n                    spawnParticles(hookX, hookY, nightMode ? '#CE93D8' : eruptionActive ? '#FFAB40' : '#B3E5FC', 1, { spread: 24, upward: 8, size: 2.5 });\n                }\n            } else {\n                hookTrail.length = 0;\n            }\n\n            if (gameState === STATE_DESCENDING) {\n                // Descend\n                hookY += getDescentSpeed() * dt;\n                hookDepthMeters = (hookY - WATER_SURFACE_Y) / METERS_TO_PX;\n\n                // Horizontal movement\n                if (inputX !== null) {\n                    const targetX = inputX;\n                    const diff = targetX - hookX;\n                    hookX += diff * 5 * dt;\n                } else {\n                    hookX += hookVelX * dt;\n                }\n                hookX = Math.max(40, Math.min(680, hookX));\n\n                // Check seaweed slowdown\n                let inSeaweed = false;\n                obstacles.forEach(o => {\n                    if (o.type === 'seaweed') {\n                        if (Math.abs(hookX - o.x) < o.width/2 && Math.abs(hookY - o.y) < o.height/2) {\n                            inSeaweed = true;\n                        }\n                    }\n                });\n                if (inSeaweed) hookY -= getDescentSpeed() * dt * 0.5; // slow by half\n\n                // Camera follow\n                const targetCam = hookY - canvas.height * 0.4;\n                cameraY = Math.max(0, targetCam);\n\n                // Check obstacle collision\n                obstacles.forEach(o => {\n                    if (o.type === 'seaweed') return;\n                    const dx = Math.abs(hookX - o.x);\n                    const dy = Math.abs(hookY - o.y);\n                    if (dx < (o.width/2 + 15) && dy < (o.height/2 + 15)) {\n                        // Bounce off\n                        playBounceSfx();\n                        shakeIntensity = 4;\n                        spawnParticles(hookX, hookY, '#EF5350', 5, { spread: 100 });\n                        if (o.type === 'boot') {\n                            hookX += (hookX > o.x ? 30 : -30);\n                        } else {\n                            // Tin can - lose cast\n                            spawnFloatingText(hookX, hookY, 'Snag!', '#EF5350', 30);\n                            resetCast();\n                        }\n                    }\n                });\n\n                // Check fish collision\n                fishes.forEach(f => {\n                    if (f.caught) return;\n                    const dx = Math.abs(hookX - f.x);\n                    const dy = Math.abs(hookY - f.y);\n                    if (dx < (f.type.width/2) && dy < (f.type.height/2)) {\n                        catchFishAction(f);\n                    }\n                });\n\n                // Halloween event candy and rare Haunted Pearls.\n                halloweenCandy.forEach(c=>{\n                    if(c.caught)return;\n                    if(Math.abs(hookX-c.x)<28 && Math.abs(hookY-c.y)<28){\n                        c.caught=true; halloweenEvent.candy+=c.value; coins+=3; config.upgrades.coins=coins; updateCoinDisplay();\n                        spawnParticles(hookX,hookY,'#ff9b43',8,{spread:100,upward:55,shape:'star',size:4}); spawnFloatingText(hookX,hookY-24,'\ud83c\udf6c Candy +1','#ffd18e',23); playSfx(740,0.12,'triangle');\n                        renderHalloweenEvent(); saveProgress();\n                    }\n                });\n                hauntedPearls.forEach(p=>{\n                    if(p.caught)return;\n                    if(Math.abs(hookX-p.x)<30 && Math.abs(hookY-p.y)<30){\n                        p.caught=true; halloweenEvent.rareCollectibles++; coins+=60; config.upgrades.coins=coins; updateCoinDisplay();\n                        spawnParticles(hookX,hookY,'#c68aff',18,{spread:180,upward:80,shape:'star',size:6}); spawnFloatingText(hookX,hookY-34,'\u2728 Haunted Pearl! +60','#e0c5ff',27); playSuccessSfx();\n                        renderHalloweenEvent(); saveProgress();\n                    }\n                });\n\n                // Check treasure chest collision\n                treasureChests.forEach(c => {\n                    if (c.caught) return;\n                    const dx = Math.abs(hookX - c.x);\n                    const dy = Math.abs(hookY - c.y);\n                    if (dx < c.width/2 + 10 && dy < c.height/2 + 10) {\n                        c.caught = true;\n                        const bonus = c.rare ? c.coins * 3 : c.coins;\n                        coins += bonus;\n                        config.upgrades.coins = coins;\n                        updateCoinDisplay();\n                        shakeIntensity = c.rare ? 18 : 10;\n                        const color = c.rare ? '#FF80FF' : '#FFD700';\n                        spawnParticles(hookX, hookY, color, 20, { spread: 300, upward: 100, shape: 'star', size: 7, gravity: 80 });\n                        spawnParticles(hookX, hookY, '#FFFFFF', 8, { spread: 120, size: 4 });\n                        spawnFloatingText(hookX, hookY - 40, (c.rare ? '\ud83d\udc8e RARE CHEST! +' : '\ud83d\udce6 Chest! +') + bonus + ' \ud83e\ude99', color, c.rare ? 34 : 28);\n                        playCoinSfx();\n                        if (c.rare) { [523,659,784,1047,1319].forEach((f,i) => setTimeout(() => playSfx(f, 0.2), i*60)); }\n                        ripples.push({ x: hookX, y: hookY, radius: 0, maxRadius: 100, life: 1 });\n                        saveProgress();\n                        submitScoreToLeaderboard();\n                    }\n                });\n\n                // \ud83c\udf19 Night Dive: Check moon pearl collision\n                moonPearls.forEach(p => {\n                    if (p.caught) return;\n                    const dx = Math.abs(hookX - p.x);\n                    const dy = Math.abs(hookY - p.y);\n                    if (dx < p.width/2 + 10 && dy < p.height/2 + 10) {\n                        p.caught = true;\n                        const bonus = p.coins;\n                        coins += bonus;\n                        config.upgrades.coins = coins;\n                        updateCoinDisplay();\n                        shakeIntensity = 14;\n                        spawnParticles(hookX, hookY, '#E1BEE7', 20, { spread: 250, upward: 120, shape: 'star', size: 6 });\n                        spawnParticles(hookX, hookY, '#FFFFFF', 12, { spread: 150, size: 4 });\n                        spawnFloatingText(hookX, hookY - 40, '\ud83c\udf19 Moon Pearl! +' + bonus + ' \ud83e\ude99', '#CE93D8', 32);\n                        playCoinSfx();\n                        [659,784,988,1175,1319].forEach((f,i) => setTimeout(() => playSfx(f, 0.25), i*70));\n                        ripples.push({ x: hookX, y: hookY, radius: 0, maxRadius: 120, life: 1 });\n                        saveProgress();\n                        submitScoreToLeaderboard();\n                    }\n                });\n\n                // \ud83c\udf0b Volcanic Trench: lava bubble collision \u2014 scorches the line\n                for (let i = 0; i < lavaBubbles.length; i++) {\n                    const b = lavaBubbles[i];\n                    const dx = hookX - b.x;\n                    const dy = hookY - b.y;\n                    if (Math.sqrt(dx * dx + dy * dy) < b.size / 2 + 14) {\n                        playSizzleSfx();\n                        if (heatShield) {\n                            heatShield = false;\n                            heatShieldTimer = 0;\n                            shakeIntensity = 10;\n                            spawnParticles(hookX, hookY, '#4FC3F7', 16, { spread: 240, upward: 60, size: 5 });\n                            spawnFloatingText(hookX, hookY - 40, '\ud83d\udee1\ufe0f Shield Absorbed!', '#4FC3F7', 28);\n                            // Pop the bubble\n                            spawnParticles(b.x, b.y, '#FF7043', 10, { spread: 200, size: 5, gravity: 120 });\n                            lavaBubbles.splice(i, 1);\n                        } else {\n                            shakeIntensity = 14;\n                            spawnParticles(hookX, hookY, '#FF7043', 14, { spread: 220, upward: 40, size: 5 });\n                            spawnParticles(hookX, hookY, '#FFEB3B', 8, { spread: 140, size: 4 });\n                            spawnFloatingText(hookX, hookY - 30, '\ud83d\udd25 Scorched!', '#FF5722', 32);\n                            resetCast();\n                        }\n                        break;\n                    }\n                }\n\n                // \ud83c\udf0b Volcanic Trench: obsidian gem collection\n                obsidianGems.forEach(g => {\n                    if (g.caught) return;\n                    const dx = Math.abs(hookX - g.x);\n                    const dy = Math.abs(hookY - g.y);\n                    if (dx < g.width/2 + 10 && dy < g.height/2 + 10) {\n                        g.caught = true;\n                        const bonus = g.coins;\n                        coins += bonus;\n                        config.upgrades.coins = coins;\n                        updateCoinDisplay();\n                        shakeIntensity = 13;\n                        spawnParticles(hookX, hookY, '#FF6E40', 18, { spread: 260, upward: 110, shape: 'star', size: 6 });\n                        spawnParticles(hookX, hookY, '#B39DDB', 10, { spread: 150, size: 4 });\n                        spawnFloatingText(hookX, hookY - 40, '\ud83c\udf0b Obsidian Gem! +' + bonus + ' \ud83e\ude99', '#FF8A65', 32);\n                        playCoinSfx();\n                        [440,554,659,880].forEach((f,i) => setTimeout(() => playSfx(f, 0.22), i*70));\n                        ripples.push({ x: hookX, y: hookY, radius: 0, maxRadius: 110, life: 1 });\n                        saveProgress();\n                        submitScoreToLeaderboard();\n                    }\n                });\n\n                // Hit bottom\n                if (hookDepthMeters >= getMaxDepth()) {\n                    resetCast();\n                }\n\n                updateDepthMeter();\n            }\n\n            if (gameState === STATE_REELING) {\n                // Auto reel with tapping\n                if (isTapping) {\n                    reelProgress += getReelSpeed() * dt;\n                    playReelSfx();\n                }\n                // Slowly reel up\n                hookY -= 60 * dt;\n                const targetCam = hookY - canvas.height * 0.4;\n                cameraY = Math.max(0, targetCam);\n                hookDepthMeters = Math.max(0, (hookY - WATER_SURFACE_Y) / METERS_TO_PX);\n                updateDepthMeter();\n                updateReelBar();\n\n                if (reelProgress >= reelTarget) {\n                    // Success!\n                    gameState = STATE_CAUGHT;\n                    showReelBar(false);\n                    comboCount++;\n                    comboTimer = COMBO_TIMEOUT;\n                    const comboMultiplier = Math.max(1, comboCount);\n                    const depthBonus = 1 + (hookDepthMeters / getMaxDepth()) * 2; // Up to 3x at max depth\n                    const luckyMultiplier = luckyLure ? 2 : 1;\n                    const goldenBootMultiplier = goldenBootActive ? 3 : 1;\n                    const nightBonus = nightMode ? 1.5 : 1;\n                    const magmaBonus = eruptionActive ? 2 : 1;\n                    const earnedCoins = Math.round(caughtFish.type.coins * comboMultiplier * depthBonus * luckyMultiplier * goldenBootMultiplier * nightBonus * magmaBonus);\n                    if (eruptionActive) {\n                        spawnFloatingText(hookX, hookY - 100, '\ud83c\udf0b Eruption \u00d72!', '#FF7043', 26);\n                    }\n                    if (luckyLure) {\n                        spawnFloatingText(hookX, hookY - 70, '\u2728 Lucky \u00d72!', '#FFD54F', 26);\n                        luckyLure = false;\n                    }\n                    if (nightMode && !luckyLure) {\n                        spawnFloatingText(hookX, hookY - 70, '\ud83c\udf19 Night \u00d71.5!', '#B388FF', 24);\n                    }\n                    coins += earnedCoins;\n                    config.upgrades.coins = coins;\n                    totalCatches++;\n                    fishJournal[caughtFish.type.id] = (fishJournal[caughtFish.type.id] || 0) + 1;\n                    if (HALLOWEEN_EVENT_ACTIVE) { if (caughtFish.type.isHalloween) halloweenEvent.fishCaught++; if(caughtFish.type.isHalloweenBoss) halloweenEvent.halloweenBosses++; }\n                    updateCoinDisplay();\n                    playCoinSfx();\n                    playSuccessSfx();\n                    shakeIntensity = caughtFish.type.isBoss ? 20 : 12;\n                    spawnParticles(hookX, hookY, '#FFD54F', 15, { spread: 250, upward: 80, shape: 'star', size: 6 });\n                    spawnParticles(hookX, hookY, '#FF8A65', 8, { spread: 150, upward: 60 });\n                    if (comboCount > 1) {\n                        spawnFloatingText(hookX, hookY - 40, 'x' + comboCount + ' COMBO!', '#FF4081', 32);\n                        spawnParticles(hookX, hookY, '#FF4081', 10, { spread: 200, upward: 120, shape: 'star', size: 5 });\n                    }\n                    if (goldenBootActive) {\n                        spawnFloatingText(hookX, hookY - 70, '\ud83d\udc5f GOLDEN BOOT \u00d73!', '#FFD700', 26);\n                    }\n                    // World Cup celebration for WC fish\n                    if (caughtFish.type.isWorldCup) {\n                        spawnFloatingText(hookX, hookY - 100, '\u26bd GOOOAL!', '#00BF57', 38);\n                        for (let i = 0; i < 30; i++) {\n                            worldCupConfetti.push({\n                                x: Math.random() * canvas.width,\n                                y: -20 - Math.random() * 80,\n                                vx: (Math.random() - 0.5) * 100,\n                                vy: 80 + Math.random() * 120,\n                                size: 4 + Math.random() * 6,\n                                color: ['#FFD700','#00BF57','#EF3340','#FFFFFF','#0057B7','#FCD116'][Math.floor(Math.random()*6)],\n                                rotation: Math.random() * Math.PI * 2,\n                                rotSpeed: (Math.random() - 0.5) * 8,\n                                life: 3.5\n                            });\n                        }\n                    }\n                    spawnFloatingText(hookX, hookY, '+' + earnedCoins + ' \ud83e\ude99', '#FFD54F', 36);\n                    showCatchPopup(caughtFish);\n                    renderHalloweenEvent();\n                    \n                    // Auto-save after catching fish\n                    saveProgress();\n                    \n                    // Submit score to leaderboard\n                    submitScoreToLeaderboard();\n                }\n\n                // Fish escapes if takes too long (hook reaches surface)\n                if (hookY <= WATER_SURFACE_Y) {\n                    showReelBar(false);\n                    resetCast();\n                }\n            }\n        }\n\n        // Edit mode\n        let editSelectedObj = null;\n        let editDragging = false;\n        let editDragOffset = { x: 0, y: 0 };\n\n        if (mode === 'edit') {\n            showCastPrompt(false);\n            // Show fish and obstacles in static positions\n            canvas.addEventListener('pointerdown', (e) => {\n                const rect = canvas.getBoundingClientRect();\n                const mx = (e.clientX - rect.left) / rect.width * canvas.width;\n                const my = (e.clientY - rect.top) / rect.height * canvas.height;\n\n                // Check fish\n                for (let f of fishes) {\n                    const dy = f.y - cameraY;\n                    if (Math.abs(mx - f.x) < f.type.width/2 && Math.abs(my - dy) < f.type.height/2) {\n                        editSelectedObj = f;\n                        editDragging = true;\n                        editDragOffset = { x: mx - f.x, y: my - dy };\n                        return;\n                    }\n                }\n                // Check obstacles\n                for (let o of obstacles) {\n                    const dy = o.y - cameraY;\n                    if (Math.abs(mx - o.x) < o.width/2 && Math.abs(my - dy) < o.height/2) {\n                        editSelectedObj = o;\n                        editDragging = true;\n                        editDragOffset = { x: mx - o.x, y: my - dy };\n                        return;\n                    }\n                }\n                editSelectedObj = null;\n            });\n\n            canvas.addEventListener('pointermove', (e) => {\n                if (!editDragging || !editSelectedObj) return;\n                const rect = canvas.getBoundingClientRect();\n                const mx = (e.clientX - rect.left) / rect.width * canvas.width;\n                const my = (e.clientY - rect.top) / rect.height * canvas.height;\n                editSelectedObj.x = mx - editDragOffset.x;\n                editSelectedObj.y = my - editDragOffset.y + cameraY;\n                if (editSelectedObj.startX !== undefined) editSelectedObj.startX = editSelectedObj.x;\n            });\n\n            canvas.addEventListener('pointerup', () => { editDragging = false; });\n\n            // Scroll in edit mode\n            canvas.addEventListener('wheel', (e) => {\n                cameraY = Math.max(0, cameraY + e.deltaY * 0.5);\n            });\n        }\n\n        function drawHalloweenCollectibles(time) {\n            halloweenCandy.forEach(c=>{\n                if(c.caught)return; const y=c.y-cameraY; if(y < -40 || y > canvas.height+40)return;\n                const bob=Math.sin(time*0.004+c.phase)*5; ctx.save(); ctx.translate(c.x,y+bob); ctx.rotate(Math.sin(time*0.002+c.phase)*0.16);\n                ctx.shadowColor='#ff8a35'; ctx.shadowBlur=16; ctx.fillStyle='#ff9a3c'; ctx.beginPath(); ctx.roundRect(-15,-12,30,24,7); ctx.fill();\n                ctx.fillStyle='#ffe2a6'; ctx.fillRect(-4,-9,8,18); ctx.fillStyle='#ffd08b';\n                ctx.beginPath(); ctx.moveTo(-15,-9); ctx.lineTo(-22,-15); ctx.lineTo(-22,-5); ctx.closePath(); ctx.fill();\n                ctx.beginPath(); ctx.moveTo(15,-9); ctx.lineTo(22,-15); ctx.lineTo(22,-5); ctx.closePath(); ctx.fill(); ctx.restore();\n            });\n            hauntedPearls.forEach(p=>{\n                if(p.caught)return; const y=p.y-cameraY; if(y < -50 || y > canvas.height+50)return;\n                const pulse=0.5+0.5*Math.sin(time*0.004+p.phase), glow=ctx.createRadialGradient(p.x,y,2,p.x,y,44+pulse*12);\n                glow.addColorStop(0,'rgba(198,137,255,0.55)'); glow.addColorStop(1,'rgba(148,77,210,0)'); ctx.fillStyle=glow; ctx.beginPath(); ctx.arc(p.x,y,55,0,Math.PI*2); ctx.fill();\n                ctx.fillStyle='#d9c5ff'; ctx.strokeStyle='#a771e6'; ctx.lineWidth=3; ctx.beginPath(); ctx.arc(p.x,y,17,0,Math.PI*2); ctx.fill(); ctx.stroke();\n                ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(p.x-5,y-6,5,0,Math.PI*2); ctx.fill();\n                ctx.font='bold 16px Fredoka'; ctx.textAlign='center'; ctx.fillStyle='#e2c4ff'; ctx.shadowColor='#45136b'; ctx.shadowBlur=5; ctx.fillText('HAUNTED PEARL',p.x,y-28); ctx.restore();\n            });\n        }\n\n        // Draw treasure chests\n        function drawTreasureChests(time) {\n            treasureChests.forEach(c => {\n                if (c.caught) return;\n                const drawY = c.y - cameraY;\n                if (drawY < -80 || drawY > canvas.height + 80) return;\n\n                const glow = 0.5 + 0.5 * Math.sin(time * 0.004 + c.glowPhase);\n                const chestImg = assetCache['treasure_chest'];\n\n                // Glow halo\n                const glowColor = c.rare ? `rgba(255,120,255,${0.25 + glow * 0.35})` : `rgba(255,215,0,${0.2 + glow * 0.3})`;\n                const grad = ctx.createRadialGradient(c.x, drawY, 0, c.x, drawY, 60 + glow * 20);\n                grad.addColorStop(0, glowColor);\n                grad.addColorStop(1, 'rgba(0,0,0,0)');\n                ctx.fillStyle = grad;\n                ctx.beginPath();\n                ctx.arc(c.x, drawY, 70, 0, Math.PI * 2);\n                ctx.fill();\n\n                if (chestImg) {\n                    ctx.save();\n                    ctx.translate(c.x, drawY);\n                    ctx.rotate(Math.sin(time * 0.002 + c.glowPhase) * 0.05);\n                    ctx.drawImage(chestImg, -c.width/2, -c.height/2, c.width, c.height);\n                    ctx.restore();\n                } else {\n                    // Fallback: draw chest procedurally\n                    ctx.save();\n                    ctx.translate(c.x, drawY);\n                    // Body\n                    ctx.fillStyle = c.rare ? '#CC44CC' : '#8B6914';\n                    ctx.fillRect(-c.width/2, -c.height/2 + 10, c.width, c.height - 10);\n                    // Lid\n                    ctx.fillStyle = c.rare ? '#AA22AA' : '#A0790E';\n                    ctx.fillRect(-c.width/2, -c.height/2, c.width, 22);\n                    // Gold trim\n                    ctx.fillStyle = '#FFD700';\n                    ctx.fillRect(-c.width/2, -c.height/2 + 10, c.width, 6);\n                    // Lock\n                    ctx.fillStyle = '#FFD700';\n                    ctx.beginPath();\n                    ctx.arc(0, 4, 10, 0, Math.PI * 2);\n                    ctx.fill();\n                    ctx.restore();\n                }\n\n                // Sparkle particles from chest\n                if (Math.random() < 0.07) {\n                    spawnParticles(c.x + (Math.random()-0.5)*30, c.y + (Math.random()-0.5)*20,\n                        c.rare ? '#FF80FF' : '#FFD700', 1, { spread: 40, upward: 60, size: 3, gravity: 50 });\n                }\n\n                // Label\n                ctx.font = 'bold 16px Fredoka';\n                ctx.fillStyle = c.rare ? '#FF80FF' : '#FFD700';\n                ctx.textAlign = 'center';\n                ctx.textBaseline = 'bottom';\n                ctx.shadowColor = 'rgba(0,0,0,0.8)';\n                ctx.shadowBlur = 6;\n                ctx.fillText(c.rare ? '\ud83d\udc8e RARE! +' + c.coins : '\ud83d\udce6 +' + c.coins, c.x, drawY - c.height/2 - 6);\n                ctx.shadowBlur = 0;\n                ctx.textBaseline = 'alphabetic';\n            });\n        }\n\n        // Draw lucky lure indicator\n        function drawLuckyLureIndicator() {\n            if (!luckyLure || gameState === STATE_IDLE) return;\n            const alpha = 0.6 + 0.4 * Math.sin(Date.now() * 0.006);\n            ctx.save();\n            ctx.globalAlpha = alpha;\n            ctx.font = 'bold 22px Fredoka';\n            ctx.fillStyle = '#FFD54F';\n            ctx.textAlign = 'center';\n            ctx.shadowColor = 'rgba(255,180,0,0.8)';\n            ctx.shadowBlur = 12;\n            ctx.fillText('\u2728 LUCKY LURE \u00d72 \u2728', canvas.width / 2, 130);\n            ctx.shadowBlur = 0;\n            ctx.restore();\n        }\n\n        // WORLD CUP 2026: Draw Golden Boot indicator\n        function drawGoldenBootIndicator() {\n            if (!goldenBootActive) return;\n            const alpha = 0.7 + 0.3 * Math.sin(Date.now() * 0.005);\n            const timeLeft = Math.ceil(goldenBootTimer);\n            ctx.save();\n            ctx.globalAlpha = alpha;\n            ctx.font = 'bold 24px Fredoka';\n            ctx.fillStyle = '#FFD700';\n            ctx.textAlign = 'center';\n            ctx.shadowColor = 'rgba(255,200,0,0.9)';\n            ctx.shadowBlur = 15;\n            ctx.fillText(`\ud83d\udc5f GOLDEN BOOT \u00d73 \u2014 ${timeLeft}s`, canvas.width / 2, 160);\n            ctx.shadowBlur = 0;\n            ctx.restore();\n        }\n\n        // WORLD CUP 2026: Draw confetti\n        function drawWorldCupConfetti() {\n            worldCupConfetti.forEach(c => {\n                ctx.save();\n                ctx.translate(c.x, c.y);\n                ctx.rotate(c.rotation);\n                ctx.globalAlpha = Math.min(1, c.life);\n                ctx.fillStyle = c.color;\n                ctx.fillRect(-c.size/2, -c.size/4, c.size, c.size/2);\n                ctx.restore();\n            });\n        }\n\n        // WORLD CUP 2026: Draw goal celebration banner\n        function drawGoalCelebration() {\n            if (goalCelebrationTimer <= 0) return;\n            const alpha = Math.min(1, goalCelebrationTimer / 0.5);\n            const scale = 1 + Math.sin(goalCelebrationTimer * 4) * 0.05;\n            ctx.save();\n            ctx.globalAlpha = alpha;\n            ctx.translate(canvas.width / 2, canvas.height / 2 - 150);\n            ctx.scale(scale, scale);\n            // Banner background\n            ctx.fillStyle = 'rgba(0,191,87,0.85)';\n            ctx.beginPath();\n            ctx.roundRect(-180, -35, 360, 70, 20);\n            ctx.fill();\n            ctx.strokeStyle = '#FFD700';\n            ctx.lineWidth = 4;\n            ctx.beginPath();\n            ctx.roundRect(-180, -35, 360, 70, 20);\n            ctx.stroke();\n            // Text\n            ctx.font = 'bold 40px Fredoka';\n            ctx.fillStyle = '#FFFFFF';\n            ctx.textAlign = 'center';\n            ctx.textBaseline = 'middle';\n            ctx.fillText('\u26bd GOOOAL! \u26bd', 0, 0);\n            ctx.restore();\n        }\n\n        // Draw ripples\n        function drawRipples() {\n            ripples.forEach(r => {\n                const drawY = r.y - cameraY;\n                if (drawY < -50 || drawY > canvas.height + 50) return;\n                ctx.strokeStyle = `rgba(255,255,255,${r.life * 0.5})`;\n                ctx.lineWidth = 2;\n                ctx.beginPath();\n                ctx.ellipse(r.x, drawY, r.radius, r.radius * 0.3, 0, 0, Math.PI * 2);\n                ctx.stroke();\n            });\n        }\n\n        // \ud83c\udf19 NIGHT DIVE: Draw night overlay with visibility radius\n        function drawNightOverlay() {\n            if (nightTransition <= 0) return;\n            const w = canvas.width, h = canvas.height;\n\n            ctx.save();\n            ctx.globalAlpha = nightTransition * 0.7;\n\n            // Create darkness with glow hole around hook\n            if (gameState === STATE_DESCENDING || gameState === STATE_REELING) {\n                const hookScreenY = hookY - cameraY;\n                const glowRadius = 160 + Math.sin(Date.now() * 0.003) * 15;\n\n                // Dark overlay with radial gradient cutout\n                const grad = ctx.createRadialGradient(hookX, hookScreenY, glowRadius * 0.3, hookX, hookScreenY, glowRadius);\n                grad.addColorStop(0, 'rgba(5,5,40,0)');\n                grad.addColorStop(0.6, 'rgba(5,5,40,0.3)');\n                grad.addColorStop(1, 'rgba(5,5,40,1)');\n                ctx.fillStyle = grad;\n                ctx.fillRect(0, 0, w, h);\n            } else {\n                ctx.fillStyle = 'rgba(5,5,40,0.6)';\n                ctx.fillRect(0, 0, w, h);\n            }\n\n            if (HALLOWEEN_EVENT_ACTIVE) {\n                ctx.fillStyle = 'rgba(56,18,95,0.14)';\n                ctx.fillRect(0, 0, w, h);\n            }\n            ctx.restore();\n\n            // Draw stars at top if night\n            if (nightTransition > 0.5 && cameraY < 100) {\n                ctx.save();\n                ctx.globalAlpha = (nightTransition - 0.5) * 2;\n                for (let i = 0; i < 30; i++) {\n                    const sx = (i * 137.5 + 50) % w;\n                    const sy = (i * 73.3 + 10) % (WATER_SURFACE_Y - cameraY);\n                    if (sy < 0) continue;\n                    const twinkle = 0.5 + 0.5 * Math.sin(Date.now() * 0.002 + i);\n                    ctx.fillStyle = `rgba(255,255,255,${twinkle * 0.8})`;\n                    ctx.beginPath();\n                    ctx.arc(sx, sy, 1.5, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n                // Moon\n                ctx.fillStyle = '#FFF9C4';\n                ctx.shadowColor = '#FFF9C4';\n                ctx.shadowBlur = 20;\n                ctx.beginPath();\n                ctx.arc(580, 60 - cameraY * 0.2, 30, 0, Math.PI * 2);\n                ctx.fill();\n                ctx.shadowBlur = 0;\n                ctx.restore();\n            }\n        }\n\n        // \ud83c\udf19 NIGHT DIVE: Draw glow effects for bioluminescent fish\n        function drawBioGlow(time) {\n            if (nightTransition <= 0 && eruptionIntensity <= 0 && !HALLOWEEN_EVENT_ACTIVE) return;\n            fishes.forEach(f => {\n                if (!f.type.glows || f.caught) return;\n                // Seasonal spectral fish retain their glow outside night mode.\n                const intensity = f.type.isHalloween ? 0.82 : f.type.isVolcanic\n                    ? Math.max(eruptionIntensity, nightTransition * 0.6)\n                    : nightTransition;\n                if (intensity <= 0.02) return;\n                const drawY = f.y - cameraY;\n                if (drawY < -100 || drawY > canvas.height + 100) return;\n\n                const pulse = 0.5 + 0.5 * Math.sin(time * 0.004 + f.phase);\n                const radius = (40 + pulse * 20) * (0.6 + intensity * 0.4);\n                let glowColor;\n                if (f.type.isHalloween && f.type.id === 'pumpkin_puffer') glowColor = `rgba(255,130,42,${(0.25 + pulse * 0.28) * intensity})`;\n                else if (f.type.isHalloween) glowColor = `rgba(184,112,255,${(0.24 + pulse * 0.24) * intensity})`;\n                else if (f.type.id === 'lanternfish') glowColor = `rgba(0,229,255,${(0.2 + pulse * 0.2) * intensity})`;\n                else if (f.type.id === 'firefly_squid') glowColor = `rgba(224,64,251,${(0.15 + pulse * 0.2) * intensity})`;\n                else if (f.type.isVolcanic) glowColor = `rgba(255,87,34,${(0.25 + pulse * 0.25) * intensity})`;\n                else glowColor = `rgba(179,136,255,${(0.2 + pulse * 0.2) * intensity})`;\n\n                const grad = ctx.createRadialGradient(f.x, drawY, 0, f.x, drawY, radius);\n                grad.addColorStop(0, glowColor);\n                grad.addColorStop(1, 'rgba(0,0,0,0)');\n                ctx.fillStyle = grad;\n                ctx.beginPath();\n                ctx.arc(f.x, drawY, radius, 0, Math.PI * 2);\n                ctx.fill();\n            });\n\n            // Glow particles\n            glowParticles.forEach(p => {\n                const drawY = p.y - cameraY;\n                ctx.globalAlpha = p.life * nightTransition;\n                ctx.fillStyle = p.color;\n                ctx.beginPath();\n                ctx.arc(p.x, drawY, p.size, 0, Math.PI * 2);\n                ctx.fill();\n            });\n            ctx.globalAlpha = 1;\n        }\n\n        // \ud83c\udf19 NIGHT DIVE: Draw sonar rings\n        function drawSonarRings() {\n            sonarRings.forEach(r => {\n                const drawY = r.y - cameraY;\n                ctx.strokeStyle = `rgba(100,181,246,${r.life * 0.7})`;\n                ctx.lineWidth = 3;\n                ctx.beginPath();\n                ctx.arc(r.x, drawY, r.radius, 0, Math.PI * 2);\n                ctx.stroke();\n                // Inner ring\n                ctx.strokeStyle = `rgba(100,181,246,${r.life * 0.3})`;\n                ctx.lineWidth = 1;\n                ctx.beginPath();\n                ctx.arc(r.x, drawY, r.radius * 0.6, 0, Math.PI * 2);\n                ctx.stroke();\n            });\n\n            // When sonar is active, highlight fish with outlines\n            if (sonarActive) {\n                fishes.forEach(f => {\n                    if (f.caught) return;\n                    const drawY = f.y - cameraY;\n                    if (drawY < -100 || drawY > canvas.height + 100) return;\n                    const dist = Math.sqrt((hookX - f.x) ** 2 + (hookY - f.y) ** 2);\n                    if (dist < 400) {\n                        ctx.save();\n                        ctx.strokeStyle = f.type.isBoss ? '#FF5252' : '#64B5F6';\n                        ctx.lineWidth = 2;\n                        ctx.setLineDash([4, 4]);\n                        ctx.beginPath();\n                        ctx.ellipse(f.x, drawY, f.type.width/2 + 8, f.type.height/2 + 8, 0, 0, Math.PI * 2);\n                        ctx.stroke();\n                        ctx.setLineDash([]);\n                        // Fish name label\n                        ctx.font = 'bold 14px Fredoka';\n                        ctx.fillStyle = f.type.isBoss ? '#FF5252' : '#64B5F6';\n                        ctx.textAlign = 'center';\n                        ctx.fillText(f.type.name, f.x, drawY - f.type.height/2 - 12);\n                        ctx.restore();\n                    }\n                });\n            }\n        }\n\n        // \ud83c\udf19 NIGHT DIVE: Draw moon pearls\n        function drawMoonPearls(time) {\n            moonPearls.forEach(p => {\n                if (p.caught) return;\n                const drawY = p.y - cameraY;\n                if (drawY < -80 || drawY > canvas.height + 80) return;\n\n                const pulse = 0.5 + 0.5 * Math.sin(time * 0.003 + p.glowPhase);\n\n                // Moonlight glow\n                const grad = ctx.createRadialGradient(p.x, drawY, 0, p.x, drawY, 50 + pulse * 20);\n                grad.addColorStop(0, `rgba(206,147,216,${0.3 + pulse * 0.3})`);\n                grad.addColorStop(1, 'rgba(0,0,0,0)');\n                ctx.fillStyle = grad;\n                ctx.beginPath();\n                ctx.arc(p.x, drawY, 60, 0, Math.PI * 2);\n                ctx.fill();\n\n                const pearlImg = assetCache['moon_pearl'];\n                if (pearlImg) {\n                    ctx.save();\n                    ctx.translate(p.x, drawY);\n                    ctx.rotate(Math.sin(time * 0.001 + p.glowPhase) * 0.08);\n                    const bob = Math.sin(time * 0.002 + p.glowPhase) * 4;\n                    ctx.drawImage(pearlImg, -p.width/2, -p.height/2 + bob, p.width, p.height);\n                    ctx.restore();\n                } else {\n                    // Fallback\n                    ctx.fillStyle = '#CE93D8';\n                    ctx.beginPath();\n                    ctx.arc(p.x, drawY, 18, 0, Math.PI * 2);\n                    ctx.fill();\n                    ctx.fillStyle = '#FFFFFF';\n                    ctx.beginPath();\n                    ctx.arc(p.x - 4, drawY - 4, 6, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n\n                // Label\n                ctx.font = 'bold 16px Fredoka';\n                ctx.fillStyle = '#CE93D8';\n                ctx.textAlign = 'center';\n                ctx.shadowColor = 'rgba(0,0,0,0.8)';\n                ctx.shadowBlur = 6;\n                ctx.fillText('\ud83c\udf19 +' + p.coins, p.x, drawY - p.height/2 - 10);\n                ctx.shadowBlur = 0;\n            });\n        }\n\n        // \ud83c\udf19 NIGHT DIVE: Night mode indicator\n        function drawNightModeUI() {\n            if (!nightMode || nightTransition < 0.3) return;\n            const timeLeft = Math.ceil(nightModeTimer);\n            ctx.save();\n            ctx.globalAlpha = nightTransition;\n            ctx.font = 'bold 20px Fredoka';\n            ctx.fillStyle = '#B388FF';\n            ctx.textAlign = 'left';\n            ctx.shadowColor = 'rgba(0,0,0,0.8)';\n            ctx.shadowBlur = 8;\n            ctx.fillText(`\ud83c\udf19 Night Dive \u2014 ${timeLeft}s`, 16, 58);\n            ctx.shadowBlur = 0;\n            ctx.restore();\n        }\n\n        // \ud83c\udf0b VOLCANIC TRENCH: draw rising lava bubbles\n        function drawLavaBubbles(time) {\n            const img = assetCache['hazard_lava_bubble'];\n            lavaBubbles.forEach(b => {\n                const drawX = b.x + Math.sin(b.wobble) * 14;\n                const drawY = b.y - cameraY;\n                if (drawY < -80 || drawY > canvas.height + 80) return;\n\n                // Heat halo\n                const pulse = 0.6 + 0.4 * Math.sin(time * 0.008 + b.wobble);\n                const grad = ctx.createRadialGradient(drawX, drawY, 0, drawX, drawY, b.size * 1.1);\n                grad.addColorStop(0, `rgba(255,138,60,${0.35 * pulse})`);\n                grad.addColorStop(1, 'rgba(0,0,0,0)');\n                ctx.fillStyle = grad;\n                ctx.beginPath();\n                ctx.arc(drawX, drawY, b.size * 1.1, 0, Math.PI * 2);\n                ctx.fill();\n\n                if (img) {\n                    ctx.save();\n                    ctx.translate(drawX, drawY);\n                    ctx.rotate(Math.sin(time * 0.002 + b.wobble) * 0.2);\n                    const s = b.size * (0.96 + 0.06 * pulse);\n                    ctx.drawImage(img, -s/2, -s/2, s, s);\n                    ctx.restore();\n                } else {\n                    const core = ctx.createRadialGradient(drawX, drawY, 0, drawX, drawY, b.size/2);\n                    core.addColorStop(0, '#FFF176');\n                    core.addColorStop(0.5, '#FF9800');\n                    core.addColorStop(1, '#D84315');\n                    ctx.fillStyle = core;\n                    ctx.beginPath();\n                    ctx.arc(drawX, drawY, b.size/2, 0, Math.PI * 2);\n                    ctx.fill();\n                }\n\n                if (Math.random() < 0.12) {\n                    spawnParticles(b.x, b.y - b.size/2, '#FFAB40', 1, { spread: 40, upward: 40, size: 3 });\n                }\n            });\n        }\n\n        // \ud83c\udf0b VOLCANIC TRENCH: draw obsidian gems\n        function drawObsidianGems(time) {\n            const img = assetCache['obsidian_gem'];\n            obsidianGems.forEach(g => {\n                if (g.caught) return;\n                const drawY = g.y - cameraY;\n                if (drawY < -80 || drawY > canvas.height + 80) return;\n\n                const pulse = 0.5 + 0.5 * Math.sin(time * 0.004 + g.glowPhase);\n                const grad = ctx.createRadialGradient(g.x, drawY, 0, g.x, drawY, 52 + pulse * 20);\n                grad.addColorStop(0, `rgba(255,110,64,${0.3 + pulse * 0.3})`);\n                grad.addColorStop(1, 'rgba(0,0,0,0)');\n                ctx.fillStyle = grad;\n                ctx.beginPath();\n                ctx.arc(g.x, drawY, 62, 0, Math.PI * 2);\n                ctx.fill();\n\n                const bob = Math.sin(time * 0.002 + g.glowPhase) * 4;\n                if (img) {\n                    ctx.save();\n                    ctx.translate(g.x, drawY + bob);\n                    ctx.rotate(Math.sin(time * 0.001 + g.glowPhase) * 0.1);\n                    ctx.drawImage(img, -g.width/2, -g.height/2, g.width, g.height);\n                    ctx.restore();\n                } else {\n                    ctx.save();\n                    ctx.translate(g.x, drawY + bob);\n                    ctx.fillStyle = '#311B92';\n                    ctx.beginPath();\n                    ctx.moveTo(0, -20);\n                    ctx.lineTo(16, 0);\n                    ctx.lineTo(0, 20);\n                    ctx.lineTo(-16, 0);\n                    ctx.closePath();\n                    ctx.fill();\n                    ctx.strokeStyle = '#FF6E40';\n                    ctx.lineWidth = 3;\n                    ctx.stroke();\n                    ctx.restore();\n                }\n\n                if (Math.random() < 0.06) {\n                    spawnParticles(g.x + (Math.random()-0.5)*24, g.y + (Math.random()-0.5)*20, '#FF6E40', 1, { spread: 40, upward: 50, size: 3 });\n                }\n\n                ctx.font = 'bold 16px Fredoka';\n                ctx.fillStyle = '#FF8A65';\n                ctx.textAlign = 'center';\n                ctx.shadowColor = 'rgba(0,0,0,0.8)';\n                ctx.shadowBlur = 6;\n                ctx.fillText('\ud83c\udf0b +' + g.coins, g.x, drawY - g.height/2 - 10);\n                ctx.shadowBlur = 0;\n            });\n        }\n\n        // \ud83c\udf0b VOLCANIC TRENCH: ember sparks drifting up from the vents\n        function drawEmberSparks() {\n            emberSparks.forEach(s => {\n                const drawY = s.y - cameraY;\n                if (drawY < -20 || drawY > canvas.height + 20) return;\n                ctx.globalAlpha = Math.max(0, s.life) * 0.9;\n                ctx.fillStyle = Math.random() < 0.4 ? '#FFD54F' : '#FF7043';\n                ctx.beginPath();\n                ctx.arc(s.x + Math.sin(s.wobble) * 8, drawY, s.size, 0, Math.PI * 2);\n                ctx.fill();\n            });\n            ctx.globalAlpha = 1;\n        }\n\n        // \ud83c\udf0b VOLCANIC TRENCH: hot glow from the trench floor + red vignette\n        function drawEruptionOverlay() {\n            if (eruptionIntensity <= 0) return;\n            const w = canvas.width, h = canvas.height;\n            const bottomY = WATER_SURFACE_Y + getMaxDepth() * METERS_TO_PX - cameraY;\n\n            ctx.save();\n            // Molten glow rising from the floor\n            if (bottomY < h + 200) {\n                const glowTop = Math.max(0, bottomY - 420);\n                const grad = ctx.createLinearGradient(0, bottomY, 0, glowTop);\n                const flicker = 0.75 + 0.25 * Math.sin(ventGlowPhase);\n                grad.addColorStop(0, `rgba(255,87,34,${0.5 * eruptionIntensity * flicker})`);\n                grad.addColorStop(0.5, `rgba(255,87,34,${0.18 * eruptionIntensity * flicker})`);\n                grad.addColorStop(1, 'rgba(255,87,34,0)');\n                ctx.fillStyle = grad;\n                ctx.fillRect(0, glowTop, w, Math.max(0, bottomY - glowTop));\n            }\n            // Warm tint across the whole scene\n            ctx.fillStyle = `rgba(255,80,20,${0.09 * eruptionIntensity})`;\n            ctx.fillRect(0, 0, w, h);\n            ctx.restore();\n        }\n\n        // \ud83c\udf0b VOLCANIC TRENCH: eruption + heat shield HUD\n        function drawEruptionUI() {\n            if (mode !== 'play') return;\n            if (eruptionActive) {\n                const alpha = 0.75 + 0.25 * Math.sin(Date.now() * 0.006);\n                ctx.save();\n                ctx.globalAlpha = alpha;\n                ctx.font = 'bold 20px Fredoka';\n                ctx.fillStyle = '#FF7043';\n                ctx.textAlign = 'left';\n                ctx.shadowColor = 'rgba(0,0,0,0.8)';\n                ctx.shadowBlur = 8;\n                ctx.fillText(`\ud83c\udf0b Eruption \u00d72 \u2014 ${Math.ceil(eruptionTimer)}s`, 16, nightMode ? 86 : 58);\n                ctx.shadowBlur = 0;\n                ctx.restore();\n            }\n            if (heatShield) {\n                ctx.save();\n                ctx.font = 'bold 20px Fredoka';\n                ctx.fillStyle = '#4FC3F7';\n                ctx.textAlign = 'left';\n                ctx.shadowColor = 'rgba(0,0,0,0.8)';\n                ctx.shadowBlur = 8;\n                ctx.fillText(`\ud83d\udee1\ufe0f Heat Shield \u2014 ${Math.ceil(heatShieldTimer)}s`, 16, (nightMode ? 86 : 58) + (eruptionActive ? 28 : 0));\n                ctx.shadowBlur = 0;\n                ctx.restore();\n            }\n            // Shield bubble around the hook\n            if (heatShield && (gameState === STATE_DESCENDING || gameState === STATE_REELING)) {\n                const drawY = hookY - cameraY;\n                const pulse = 0.6 + 0.4 * Math.sin(Date.now() * 0.008);\n                ctx.save();\n                ctx.strokeStyle = `rgba(79,195,247,${0.5 * pulse})`;\n                ctx.lineWidth = 3;\n                ctx.beginPath();\n                ctx.arc(hookX, drawY, 34 + pulse * 4, 0, Math.PI * 2);\n                ctx.stroke();\n                ctx.restore();\n            }\n        }\n\n        // Draw rain\n        function drawRain() {\n            if (currentWeather !== 'rain') return;\n            ctx.strokeStyle = 'rgba(180,220,255,0.5)';\n            ctx.lineWidth = 1.5;\n            raindrops.forEach(r => {\n                ctx.beginPath();\n                ctx.moveTo(r.x, r.y);\n                ctx.lineTo(r.x - 2, r.y + 12);\n                ctx.stroke();\n            });\n        }\n\n        // Weather overlay\n        function drawWeatherOverlay() {\n            if (currentWeather === 'foggy') {\n                ctx.fillStyle = 'rgba(200,220,240,0.15)';\n                ctx.fillRect(0, 0, canvas.width, canvas.height);\n            }\n        }\n\n        // Combo meter UI\n        function drawComboMeter() {\n            if (comboCount > 0 && comboTimer > 0 && mode === 'play') {\n                const barWidth = 200;\n                const barX = canvas.width / 2 - barWidth / 2;\n                const barY = 70;\n                ctx.fillStyle = 'rgba(0,0,0,0.4)';\n                ctx.beginPath();\n                ctx.roundRect(barX, barY, barWidth, 24, 12);\n                ctx.fill();\n                const pct = comboTimer / COMBO_TIMEOUT;\n                const gradient = ctx.createLinearGradient(barX, 0, barX + barWidth * pct, 0);\n                gradient.addColorStop(0, '#FF4081');\n                gradient.addColorStop(1, '#FF80AB');\n                ctx.fillStyle = gradient;\n                ctx.beginPath();\n                ctx.roundRect(barX, barY, barWidth * pct, 24, 12);\n                ctx.fill();\n                ctx.font = 'bold 18px Fredoka';\n                ctx.fillStyle = 'white';\n                ctx.textAlign = 'center';\n                ctx.fillText(`x${comboCount} COMBO`, canvas.width / 2, barY + 18);\n            }\n        }\n\n        // Main game loop\n        showCastPrompt(mode === 'play');\n\n        function gameLoop(timestamp) {\n            timeNow = timestamp;\n            if (!lastTime) lastTime = timestamp;\n            let dt = (timestamp - lastTime) / 1000;\n            dt = Math.min(dt, 0.11);\n            lastTime = timestamp;\n\n            ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n            ctx.save();\n            applyScreenShake(dt);\n\n            drawBackground(dt);\n            drawBubbles(dt);\n            drawBoat();\n            drawObstacles();\n            drawBioGlow(timestamp);\n            drawFishes(dt, timestamp);\n            drawFishingLine();\n            drawHookTrail();\n            drawHook();\n            drawTreasureChests(timestamp);\n            drawMoonPearls(timestamp);\n            drawObsidianGems(timestamp);\n            drawLavaBubbles(timestamp);\n            drawEmberSparks();\n            drawCaughtFish();\n            drawSplash(dt);\n            drawParticles(dt);\n            drawRipples();\n            drawSonarRings();\n            drawRain();\n            drawWeatherOverlay();\n            if (mode === 'play' && !isMainMenuActive) {\n                ctx.save(); ctx.font='bold 19px Fredoka'; ctx.textAlign='left'; ctx.fillStyle='#ffd18e'; ctx.shadowColor='#24103e'; ctx.shadowBlur=6;\n                ctx.fillText(`\ud83c\udf83 Halloween \u2022 \ud83c\udf6c ${halloweenEvent.candy}`,16,92); ctx.restore();\n            }\n            drawEruptionOverlay();\n            drawNightOverlay();\n            drawNightModeUI();\n            drawEruptionUI();\n            drawComboMeter();\n            drawLuckyLureIndicator();\n            drawGoldenBootIndicator();\n            drawWorldCupConfetti();\n            drawGoalCelebration();\n            drawFloatingTexts(dt);\n\n            ctx.restore();\n\n            // Edit mode selection highlight\n            if (mode === 'edit' && editSelectedObj) {\n                const obj = editSelectedObj;\n                const dx = obj.x;\n                const dy = obj.y - cameraY;\n                const w = obj.type?.width || obj.width || 60;\n                const h = obj.type?.height || obj.height || 40;\n                ctx.strokeStyle = '#FFD54F';\n                ctx.lineWidth = 3;\n                ctx.setLineDash([6, 4]);\n                ctx.strokeRect(dx - w/2 - 5, dy - h/2 - 5, w + 10, h + 10);\n                ctx.setLineDash([]);\n            }\n\n            if (mode === 'play') {\n                update(dt, timestamp);\n            }\n\n            requestAnimationFrame(gameLoop);\n        }\n        requestAnimationFrame(gameLoop);\n    }\n    </script>\n</body>\n</html>", "game_config": "{\n  \"settings\": {\n    \"waterColor\": \"light_blue\",\n    \"fishSpawnRate\": 1,\n    \"obstacleDensity\": \"normal\",\n    \"hookSpeed\": \"normal\",\n    \"showDepthMeter\": true,\n    \"startingCoins\": 0,\n    \"eruptionsEnabled\": true,\n    \"eruptionEveryNCasts\": 3\n  },\n  \"upgrades\": {\n    \"coins\": 0,\n    \"rodLevel\": 1,\n    \"lineLevel\": 1,\n    \"reelLevel\": 1\n  }\n}", "game_design_document": "## Core Game Loop and Mechanics\n\n**Deep Blue Dragger** is a casual fishing arcade game that combines timing, skill, and progression. The core loop revolves around:\n\n1. **Casting**: Player taps the screen to cast their fishing line into the water\n2. **Guiding the Hook**: As the hook descends, player drags their finger to move the hook left/right, avoiding obstacles (boots, tin cans, seaweed) while targeting fish\n3. **Catching Fish**: When the hook touches a fish, it automatically attaches and the player must reel it in by tapping rapidly or holding\n4. **Earning Coins**: Successfully caught fish award coins based on rarity and size\n5. **Upgrading**: Between fishing sessions, players spend coins to upgrade their rod (cast deeper, reel faster) and bait (attract rarer fish)\n6. **Progression**: Unlock deeper fishing spots with better fish and eventually encounter legendary boss fish\n\n**Win Condition**: Catch all legendary boss fish across all five fishing spots\n\n**Engagement Drivers**:\n- Satisfying physics-based hook movement\n- Collection aspect (catching all fish types)\n- Progression through upgrades\n- Challenge of avoiding obstacles while targeting valuable fish\n- Boss fish encounters that require skill and upgraded equipment\n\n## Starting Scope\n\nFor the initial implementation, focus on creating a polished, playable core experience:\n\n**Must Have (First Iteration)**:\n- Single fishing spot (the first lake) with 3-4 common fish types\n- Basic obstacle types (boot, tin can)\n- Core fishing mechanics: cast, guide hook, catch fish, reel in\n- Simple coin system and basic upgrade menu (rod strength, line length)\n- Touch controls for mobile and mouse controls for desktop\n- Visual feedback for successful catches\n- Simple UI showing coins, depth meter, and current catch\n\n**Save for Later Iterations**:\n- Additional 4 fishing spots (unlock progression)\n- Legendary boss fish encounters\n- Bait upgrade system\n- Rare/epic fish varieties\n- Achievement system\n- Daily challenges\n- Sound effects and background music\n- Particle effects and polish animations\n\n## Game Perspective\n\n**Fixed-screen with vertical scrolling viewport**\n\nThe game uses a side-view perspective showing:\n- Top portion: Above-water view with the player's boat/dock\n- Main viewport: Underwater view that scrolls vertically as the hook descends\n- The camera follows the hook downward, revealing deeper water layers\n- Water surface remains visible at the top as a reference point\n\n## Game World and Level Design\n\n**First Fishing Spot: Sunny Shallows**\n\nThe initial level takes place in a bright, shallow lake environment:\n\n**Depth Zones**:\n- **0-20m**: Bright, clear water with sunlight rays, small common fish, occasional obstacles\n- **20-40m**: Slightly darker, more fish variety, increased obstacle density\n- **40-60m**: Deeper blue tones, larger fish appear, more challenging obstacle patterns\n\n**Layout**:\n- Water width: ~600px (allowing hook movement within safe boundaries)\n- Vertical scrolling depth: 60 meters maximum (with starter rod)\n- Obstacles spawn in semi-random patterns, creating \"lanes\" the player must navigate\n- Fish swim in horizontal patterns at various depths\n- Bottom of the fishing zone has a sandy floor with rocks and plants\n\n**Environmental Elements**:\n- Sunlight rays penetrating from surface (visual only)\n- Floating particles and bubbles\n- Seaweed patches that slow hook descent\n- Rocky outcrops that block movement\n\n**Fish Distribution**:\n- **Bluegill** (common): Small, slow-moving, worth 5 coins, found 0-30m\n- **Perch** (common): Medium size, moderate speed, worth 10 coins, found 10-40m\n- **Bass** (uncommon): Larger, faster, worth 25 coins, found 20-60m\n- **Golden Carp** (rare): Large, quick movements, worth 50 coins, found 40-60m\n\n**Obstacles**:\n- **Old Boot**: Stationary, blocks path, causes hook to bounce off\n- **Tin Can**: Slowly sinking, must avoid or hook gets tangled (lose the cast)\n- **Seaweed Patch**: Slows hook movement by 50% when passing through\n\n## Edit Mode Design\n\nPlayers can customize their fishing experience through an edit mode accessible from the main menu:\n\n**Editable Properties**:\n\n1. **Lake Appearance**:\n   - Water color (light blue, dark blue, green, murky)\n   - Time of day (dawn, day, dusk, night) - affects lighting and ambiance\n   - Weather effects (clear, light rain, foggy)\n\n2. **Fish Population**:\n   - Spawn rate multiplier (0.5x to 2x)\n   - Fish size variance (small, normal, large)\n   - Rare fish chance (0% to 20%)\n\n3. **Obstacle Density**:\n   - Obstacle spawn rate (none, low, normal, high)\n   - Obstacle types enabled/disabled individually\n\n4. **Difficulty Settings**:\n   - Hook movement speed (slow, normal, fast)\n   - Reel-in difficulty (easy, normal, hard)\n   - Starting coins (0, 50, 100, 500)\n\n5. **Visual Preferences**:\n   - Show depth meter (on/off)\n   - Show fish names (on/off)\n   - Particle effects intensity (low, normal, high)\n\n**Constraints**:\n- Water color must maintain sufficient contrast for visibility\n- Fish spawn rate cannot exceed 3x to prevent performance issues\n- Obstacle density capped to ensure playability\n- All settings have sensible defaults that create balanced gameplay\n\n## Control Design\n\n**Mobile (Touch) Controls**:\n- **Tap anywhere**: Cast fishing line (when at surface)\n- **Drag finger**: Move hook left/right while descending (hook follows finger horizontally)\n- **Tap rapidly or hold**: Reel in fish after catching (visual feedback shows reel progress)\n- **Tap \"Menu\" button**: Open upgrade shop (top-right corner)\n\n**Desktop (Mouse/Keyboard) Controls**:\n- **Click anywhere** or **Spacebar**: Cast fishing line\n- **Move mouse left/right** or **A/D keys**: Guide hook horizontally\n- **Click rapidly** or **hold Spacebar**: Reel in fish\n- **ESC key** or **click \"Menu\" button**: Open upgrade shop\n\n**Control Implementation Details**:\n- Hook movement is smooth and responsive, with slight momentum/inertia for feel\n- Horizontal movement is constrained to the water boundaries (with visual indicators)\n- When reeling in, a progress bar shows how much more tapping/holding is needed\n- Clear visual feedback when hook touches fish (hook \"latches on\", fish struggles)\n- Prevent accidental casts by requiring deliberate tap/click when ready\n\n## Art Style and Visual Theme\n\n**Overall Style**: Bright, cheerful cartoon art with a hand-drawn quality\n\n**Color Palette**:\n- **Water**: Gradient from light cyan (#87CEEB) at surface to deep blue (#1E3A5F) at depth\n- **Fish**: Vibrant, saturated colors (oranges, yellows, blues, greens) with simple patterns\n- **Obstacles**: Muted browns and grays to contrast with colorful fish\n- **UI**: Clean white/light blue panels with rounded corners, friendly sans-serif font\n\n**Visual Characteristics**:\n- Soft outlines on all objects\n- Gentle animations (fish swimming, bubbles rising, seaweed swaying)\n- Particle effects for splashes, bubbles, and sparkles on rare catches\n- Simple but expressive fish designs with visible eyes and fins\n- Clear visual hierarchy: fish pop against background, obstacles are distinct\n\n**Asset Approach**:\n- Generate image assets for: fish varieties, obstacles, boat/dock, hook, background elements\n- Use CSS/Canvas for: UI elements, progress bars, depth meter, coin counter, water gradient overlay\n- Render with code: fishing line (simple line drawing), bubbles (circles), particle effects\n\n**Consistency Notes**:\n- All fish should share similar cartoon style with visible scales/patterns\n- Obstacles should look weathered and old\n- Maintain consistent lighting direction (from above)\n- Use same outline thickness across all generated assets", "persistent_storage_config": {"leaderboard": {"sort_ascending": false}}, "game_metadata": {"game_id": "01KT7GRVF58JVY1NTZ5JQ0Z5TR", "title": "Deep Blue Dragger", "description": "Cast your line, dodge junk, and reel in colorful fish to upgrade your gear and catch legendary bosses!", "thumbnail_url": "https://d2oir5eh8rty2e.cloudfront.net/assets/images/thumbnail_66f98686-64b1-4c6c-84fc-65b9b7df74a7.webp", "created_at": 1780516433, "updated_at": 1793425227794.7656, "art_style": null}, "task_plan": null}