PageSourceSearch

https://pupwife.com/scripts/effects/cursorTrails.js

js pupwife.com collected 2026-09-30 15:05:35 UTC 138,578 bytes, 4,096 lines download raw bytes

1// ====================================
2// CURSOR TRAILS SYSTEM
3// Multiple cursor trail effects
4// ====================================
5
6(function() {
7    'use strict';
8
9    function emitDevLog() {
10        if (typeof window !== 'undefined' && typeof window.devLog === 'function') {
11            window.devLog.apply(window, arguments);
12        }
13    }
14    function emitDevWarn() {
15        if (typeof window !== 'undefined' && typeof window.devWarn === 'function') {
16            window.devWarn.apply(window, arguments);
17        }
18    }
19
20    const TRAIL_TYPES = {
21        none: 'none',
22        sparkles: 'sparkles',
23        fairy: 'fairy',
24        rainbow: 'rainbow',
25        bubbles: 'bubbles',
26        hearts: 'hearts',
27        stars: 'stars',
28        fire: 'fire',
29        snow: 'snow',
30        leaves: 'leaves',
31        // NEW TRAILS
32        lightning: 'lightning',
33        cherryBlossoms: 'cherryBlossoms',
34        fireflies: 'fireflies',
35        butterflies: 'butterflies',
36        comets: 'comets',
37        magicRunes: 'magicRunes',
38        crystals: 'crystals',
39        energyOrbs: 'energyOrbs',
40        matrix: 'matrix',
41        neonLines: 'neonLines',
42        glitch: 'glitch',
43        musicNotes: 'musicNotes',
44        confetti: 'confetti',
45        paintSplatter: 'paintSplatter',
46        smoke: 'smoke',
47        ripples: 'ripples',
48        dnaHelix: 'dnaHelix',
49        circuit: 'circuit',
50        binary: 'binary',
51        pawPrints: 'pawPrints',
52        // Heart colors
53        orangeHearts: 'orangeHearts',
54        yellowHearts: 'yellowHearts',
55        blueHearts: 'blueHearts',
56        redHearts: 'redHearts',
57        purpleHearts: 'purpleHearts',
58        greenHearts: 'greenHearts',
59        whiteHearts: 'whiteHearts',
60        blackHearts: 'blackHearts',
61        rainbowHearts: 'rainbowHearts',
62        dandelion: 'dandelion',
63        rosePetals: 'rosePetals',
64        pixelated: 'pixelated',
65        galaxy: 'galaxy',
66        auroraWaves: 'auroraWaves',
67        geometric: 'geometric',
68        spiral: 'spiral',
69        inkBlots: 'inkBlots',
70        paintStrokes: 'paintStrokes',
71        laserBeams: 'laserBeams',
72        holograms: 'holograms',
73        clouds: 'clouds',
74        tornado: 'tornado',
75        emojis: 'emojis',
76        candy: 'candy',
77        electricSparks: 'electricSparks',
78        plasma: 'plasma',
79        vortex: 'vortex',
80        cosmicDust: 'cosmicDust',
81        // NEW PHYSICS & OCEAN TRAILS
82        bouncyBalls: 'bouncyBalls',
83        physicsBoxes: 'physicsBoxes',
84        jellyfish: 'jellyfish',
85        fish: 'fish',
86        acorns: 'acorns',
87        pinecones: 'pinecones',
88        raindrops: 'raindrops',
89        mist: 'mist',
90        fog: 'fog',
91        chalk: 'chalk',
92        pastels: 'pastels',
93        dataPackets: 'dataPackets',
94        soundWaves: 'soundWaves',
95        trafficLights: 'trafficLights',
96        bees: 'bees',
97        lensFlares: 'lensFlares',
98        prisms: 'prisms',
99        // Physics chaos
100        chainLinks: 'chainLinks',
101        dominoes: 'dominoes',
102        // Ocean family
103        seahorses: 'seahorses',
104        starfish: 'starfish',
105        coral: 'coral',
106        // Cozy nature
107        mushrooms: 'mushrooms',
108        feathers: 'feathers',
109        fruit: 'fruit',
110        // Fantasy
111        portals: 'portals',
112        manaOrbs: 'manaOrbs',
113        // Music & food
114        vinylRecords: 'vinylRecords',
115        coffeeBeans: 'coffeeBeans',
116        sprinkles: 'sprinkles',
117        // Casino & chat
118        dice: 'dice',
119        chatBubbles: 'chatBubbles',
120        spaghetti: 'spaghetti',
121        // Extended handlers (cursorTrailHandlers.js)
122        tardigrade: 'tardigrade',
123        seashells: 'seashells',
124        seaweed: 'seaweed',
125        pearlBubbles: 'pearlBubbles',
126        bioluminescence: 'bioluminescence',
127        snowballFight: 'snowballFight',
128        valentineCandy: 'valentineCandy',
129        ropeYarn: 'ropeYarn',
130        // Matter trails (cursorTrailsMatter.js)
131        marbleRun: 'marbleRun',
132        newtonsCradle: 'newtonsCradle',
133        stackingBlocks: 'stackingBlocks',
134        // Batch 3 (cursorTrailHandlers.js)
135        tetrominoes: 'tetrominoes',
136        pokerChips: 'pokerChips',
137        xpOrbs: 'xpOrbs',
138        pixelHearts: 'pixelHearts',
139        paperClips: 'paperClips',
140        stickyNotes: 'stickyNotes',
141        popcorn: 'popcorn',
142        gummyWoims: 'gummyWoims',
143        shootingStars: 'shootingStars',
144        snails: 'snails',
145        moths: 'moths',
146        bats: 'bats',
147        frogs: 'frogs',
148        woims: 'woims',
149        equalizerBars: 'equalizerBars',
150        glitterSparkles: 'glitterSparkles',
151        moonDust: 'moonDust'
152    };
153
154    const EXTENSION_SCRIPTS = [
155        '/scripts/effects/cursorTrailHandlers.js',
156        '/scripts/effects/cursorTrailsMatter.js'
157    ];
158    let extensionScriptsRequested = false;
159
160    function syncExtensionsForCurrentTrail() {
161        var type = currentTrailType;
162        if (type === 'none') return;
163        if (window.PupwifeCursorTrailHandlers) {
164            window.PupwifeCursorTrailHandlers.onTrailChange(type);
165        }
166        if (window.PupwifeCursorTrailsMatter && canvas) {
167            if (isMatterTrail(type)) {
168                window.PupwifeCursorTrailsMatter.onTrailChange(type, canvas.width, canvas.height);
169            } else {
170                window.PupwifeCursorTrailsMatter.destroy();
171            }
172        }
173    }
174
175    function requestTrailExtensions() {
176        if (extensionScriptsRequested) return;
177        extensionScriptsRequested = true;
178        EXTENSION_SCRIPTS.forEach(function (src) {
179            if (document.querySelector('script[src="' + src + '"]')) return;
180            var script = document.createElement('script');
181            script.src = src;
182            script.async = true;
183            script.onload = function () {
184                syncExtensionsForCurrentTrail();
185            };
186            document.head.appendChild(script);
187        });
188    }
189
190    function isHandlerTrail(type) {
191        return window.PupwifeCursorTrailHandlers &&
192            typeof window.PupwifeCursorTrailHandlers.isHandlerType === 'function' &&
193            window.PupwifeCursorTrailHandlers.isHandlerType(type);
194    }
195
196    function isMatterTrail(type) {
197        return window.PupwifeCursorTrailsMatter &&
198            typeof window.PupwifeCursorTrailsMatter.isMatterType === 'function' &&
199            window.PupwifeCursorTrailsMatter.isMatterType(type);
200    }
201
202    function getHandlerCtx() {
203        return {
204            particles: particles,
205            mouseVelX: mouseVelX,
206            mouseVelY: mouseVelY,
207            lastMouseX: lastMouseX,
208            lastMouseY: lastMouseY
209        };
210    }
211
212    const CHAT_BUBBLE_TEXTS = [
213        'lol', ':3c', 'xD', 'omg', 'uwu', 'brb', 'hai', 'yeet', 'mood', 'same',
214        'facts', 'rip', 'lmao', '???', '!!!', 'hehe', 'nice', 'wow', 'ok', 'ty'
215    ];
216
217    const FRUIT_COLORS = [
218        { kind: 'apple', color: '#e53935', stem: '#5d4037' },
219        { kind: 'orange', color: '#ff9800', stem: '#33691e' },
220        { kind: 'berry', color: '#9c27b0', stem: '#4a148c' },
221        { kind: 'grape', color: '#7b1fa2', stem: '#4a148c' },
222        { kind: 'lemon', color: '#ffeb3b', stem: '#827717' }
223    ];
224
225    const TRAIL_AMOUNT_LABELS = ['Minimum', 'Low', 'Normal', 'High', 'Extreme'];
226    const TRAIL_AMOUNT_PRESETS = [
227        { spawnChance: 0.15, maxParticles: 40, burst: 1, speedMin: 2.2 },
228        { spawnChance: 0.38, maxParticles: 85, burst: 1, speedMin: 1.6 },
229        { spawnChance: 0.68, maxParticles: 155, burst: 1, speedMin: 1 },
230        { spawnChance: 0.9, maxParticles: 245, burst: 2, speedMin: 0.75 },
231        { spawnChance: 1, maxParticles: 400, burst: 3, speedMin: 0.5 }
232    ];
233
234    let trailColorOverrides = { primary: '#c8a8ff', secondary: '#ffb3ba', tertiary: '#89cff0' };
235
236    function mergeTrailColors(raw) {
237        if (window.PupwifeTrailColorSupport && typeof window.PupwifeTrailColorSupport.mergeTrailColors === 'function') {
238            return window.PupwifeTrailColorSupport.mergeTrailColors(raw);
239        }
240        return Object.assign({}, trailColorOverrides, raw || {});
241    }
242
243    function supportsCurrentTrailColors() {
244        return window.PupwifeTrailColorSupport &&
245            typeof window.PupwifeTrailColorSupport.supportsTrailColors === 'function' &&
246            window.PupwifeTrailColorSupport.supportsTrailColors(currentTrailType);
247    }
248
249    function resolveTrailColor(slotIndex, fallback) {
250        if (!supportsCurrentTrailColors()) return fallback;
251        return window.PupwifeTrailColorSupport.pickColor(trailColorOverrides, slotIndex) || fallback;
252    }
253
254    function tintParticleIfSupported(p, slotIndex) {
255        if (!p || p.color === undefined || !supportsCurrentTrailColors()) return;
256        p.color = resolveTrailColor(slotIndex, p.color);
257    }
258
259    function readTrailColorsFromSettings() {
260        try {
261            const saved = localStorage.getItem('puppywifey_settings');
262            if (saved) {
263                const parsed = JSON.parse(saved);
264                if (parsed.trailColors) {
265                    trailColorOverrides = mergeTrailColors(parsed.trailColors);
266                }
267            }
268        } catch (e) { /* ignore */ }
269    }
270
271    let currentTrailType = 'none';
272    let currentTrailAmount = 2;
273    let particles = [];
274    let spaghettiPath = [];
275    const SPAGHETTI_MAX_POINTS = 90;
276    const SPAGHETTI_COLORS = ['#f5d76e', '#f4d03f', '#e8c547', '#d4a017', '#c9982a'];
277    let animationFrameId = null;
278    let canvas = null;
279    let ctx = null;
280    let lastMouseX = 0;
281    let lastMouseY = 0;
282    let mouseVelX = 0;
283    let mouseVelY = 0;
284    
285    // Cached images for trails
286    let pawPrintImage = null;
287    let pawPrintImageLoaded = false;
288
289    // ====================================
290    // INITIALIZATION
291    // ====================================
292
293    function init() {
294        // Create canvas for trails
295        canvas = document.createElement('canvas');
296        canvas.id = 'cursor-trail-canvas';
297        canvas.style.cssText = `
298            position: fixed;
299            top: 0;
300            left: 0;
301            width: 100vw;
302            height: 100vh;
303            pointer-events: none;
304            z-index: 9999;
305        `;
306        document.body.appendChild(canvas);
307
308        ctx = canvas.getContext('2d');
309        resizeCanvas();
310
311        // Mouse tracking
312        document.addEventListener('mousemove', handleMouseMove);
313        window.addEventListener('resize', resizeCanvas);
314
315        readTrailAmountFromSettings();
316        readTrailColorsFromSettings();
317        document.addEventListener('pupwife:setting-changed', handleSettingChanged);
318        requestTrailExtensions();
319
320        emitDevLog('✨ Cursor trails initialized!');
321    }
322
323    function normalizeTrailAmount(value) {
324        const n = parseInt(value, 10);
325        if (Number.isNaN(n)) return 2;
326        return Math.max(0, Math.min(4, n));
327    }
328
329    function readTrailAmountFromSettings() {
330        try {
331            const saved = localStorage.getItem('puppywifey_settings');
332            if (saved) {
333                const parsed = JSON.parse(saved);
334                if (parsed.trailAmount !== undefined) {
335                    setTrailAmount(parsed.trailAmount);
336                }
337            }
338        } catch (e) { /* ignore */ }
339    }
340
341    function handleSettingChanged(e) {
342        if (!e || !e.detail) return;
343        if (e.detail.key === 'trailAmount') {
344            setTrailAmount(e.detail.value);
345        } else if (e.detail.key === 'trailColors') {
346            setTrailColors(e.detail.value);
347        }
348    }
349
350    function resizeCanvas() {
351        if (!canvas) return;
352        canvas.width = window.innerWidth;
353        canvas.height = window.innerHeight;
354    }
355
356    function handleMouseMove(e) {
357        const newX = e.clientX;
358        const newY = e.clientY;
359
360        // Calculate velocity
361        mouseVelX = newX - lastMouseX;
362        mouseVelY = newY - lastMouseY;
363
364        lastMouseX = newX;
365        lastMouseY = newY;
366
367        // Spawn particles based on trail type
368        if (currentTrailType !== 'none') {
369            spawnParticles(newX, newY);
370        }
371    }
372
373    // ====================================
374    // PARTICLE SPAWNING
375    // ====================================
376
377    function spawnParticles(x, y) {
378        const preset = TRAIL_AMOUNT_PRESETS[currentTrailAmount] || TRAIL_AMOUNT_PRESETS[2];
379        const speed = Math.sqrt(mouseVelX * mouseVelX + mouseVelY * mouseVelY);
380
381        if (speed < preset.speedMin) return;
382        if (particles.length >= preset.maxParticles) return;
383        if (Math.random() > preset.spawnChance) return;
384
385        for (let b = 0; b < preset.burst; b++) {
386            if (b > 0 && Math.random() > 0.55) continue;
387            const sx = x + (b > 0 ? (Math.random() - 0.5) * 12 : 0);
388            const sy = y + (b > 0 ? (Math.random() - 0.5) * 12 : 0);
389            spawnTrailParticle(sx, sy);
390        }
391    }
392
393    function spawnTrailParticle(x, y) {
394        if (isMatterTrail(currentTrailType)) {
395            if (window.PupwifeCursorTrailsMatter) {
396                window.PupwifeCursorTrailsMatter.spawn(currentTrailType, x, y, {
397                    vx: mouseVelX,
398                    vy: mouseVelY
399                });
400            }
401            return;
402        }
403        if (isHandlerTrail(currentTrailType)) {
404            if (window.PupwifeCursorTrailHandlers) {
405                window.PupwifeCursorTrailHandlers.spawn(currentTrailType, x, y, getHandlerCtx());
406            }
407            return;
408        }
409
410        const lenBefore = particles.length;
411
412        switch (currentTrailType) {
413            case 'sparkles': spawnSparkle(x, y); break;
414            case 'fairy': spawnFairy(x, y); break;
415            case 'rainbow': spawnRainbow(x, y); break;
416            case 'bubbles': spawnBubble(x, y); break;
417            case 'hearts': spawnHeart(x, y); break;
418            case 'stars': spawnStar(x, y); break;
419            case 'fire': spawnFire(x, y); break;
420            case 'snow': spawnSnow(x, y); break;
421            case 'leaves': spawnLeaf(x, y); break;
422            // NEW TRAILS
423            case 'lightning': spawnLightning(x, y); break;
424            case 'cherryBlossoms': spawnCherryBlossom(x, y); break;
425            case 'fireflies': spawnFirefly(x, y); break;
426            case 'butterflies': spawnButterfly(x, y); break;
427            case 'comets': spawnComet(x, y); break;
428            case 'magicRunes': spawnMagicRune(x, y); break;
429            case 'crystals': spawnCrystal(x, y); break;
430            case 'energyOrbs': spawnEnergyOrb(x, y); break;
431            case 'matrix': spawnMatrix(x, y); break;
432            case 'neonLines': spawnNeonLine(x, y); break;
433            case 'glitch': spawnGlitch(x, y); break;
434            case 'musicNotes': spawnMusicNote(x, y); break;
435            case 'confetti': spawnConfetti(x, y); break;
436            case 'paintSplatter': spawnPaintSplatter(x, y); break;
437            case 'smoke': spawnSmoke(x, y); break;
438            case 'ripples': spawnRipple(x, y); break;
439            case 'dnaHelix': spawnDNA(x, y); break;
440            case 'circuit': spawnCircuit(x, y); break;
441            case 'binary': spawnBinary(x, y); break;
442            case 'pawPrints': spawnPawPrint(x, y); break;
443            // Heart colors
444            case 'orangeHearts': spawnColoredHeart(x, y, '🧡'); break;
445            case 'yellowHearts': spawnColoredHeart(x, y, '💛'); break;
446            case 'blueHearts': spawnColoredHeart(x, y, '💙'); break;
447            case 'redHearts': spawnColoredHeart(x, y, '❤️'); break;
448            case 'purpleHearts': spawnColoredHeart(x, y, '💜'); break;
449            case 'greenHearts': spawnColoredHeart(x, y, '💚'); break;
450            case 'whiteHearts': spawnColoredHeart(x, y, '🤍'); break;
451            case 'blackHearts': spawnColoredHeart(x, y, '🖤'); break;
452            case 'rainbowHearts': spawnRainbowHeart(x, y); break;
453            case 'dandelion': spawnDandelionSeed(x, y); break;
454            case 'rosePetals': spawnRosePetal(x, y); break;
455            case 'pixelated': spawnPixel(x, y); break;
456            case 'galaxy': spawnGalaxy(x, y); break;
457            case 'auroraWaves': spawnAuroraWave(x, y); break;
458            case 'geometric': spawnGeometric(x, y); break;
459            case 'spiral': spawnSpiral(x, y); break;
460            case 'inkBlots': spawnInkBlot(x, y); break;
461            case 'paintStrokes': spawnPaintStroke(x, y); break;
462            case 'laserBeams': spawnLaser(x, y); break;
463            case 'holograms': spawnHologram(x, y); break;
464            case 'clouds': spawnCloud(x, y); break;
465            case 'tornado': spawnTornado(x, y); break;
466            case 'emojis': spawnEmoji(x, y); break;
467            case 'candy': spawnCandy(x, y); break;
468            case 'electricSparks': spawnElectricSpark(x, y); break;
469            case 'plasma': spawnPlasma(x, y); break;
470            case 'vortex': spawnVortex(x, y); break;
471            case 'cosmicDust': spawnCosmicDust(x, y); break;
472            // NEW PHYSICS & OCEAN TRAILS
473            case 'bouncyBalls': spawnBouncyBall(x, y); break;
474            case 'physicsBoxes': spawnPhysicsBox(x, y); break;
475            case 'jellyfish': spawnJellyfish(x, y); break;
476            case 'fish': spawnFish(x, y); break;
477            case 'acorns': spawnAcorn(x, y); break;
478            case 'pinecones': spawnPinecone(x, y); break;
479            case 'raindrops': spawnRaindrop(x, y); break;
480            case 'mist': spawnMist(x, y); break;
481            case 'fog': spawnFog(x, y); break;
482            case 'chalk': spawnChalk(x, y); break;
483            case 'pastels': spawnPastel(x, y); break;
484            case 'dataPackets': spawnDataPacket(x, y); break;
485            case 'soundWaves': spawnSoundWave(x, y); break;
486            case 'trafficLights': spawnTrafficLight(x, y); break;
487            case 'bees': spawnBee(x, y); break;
488            case 'lensFlares': spawnLensFlare(x, y); break;
489            case 'prisms': spawnPrism(x, y); break;
490            case 'chainLinks': spawnChainLink(x, y); break;
491            case 'dominoes': spawnDomino(x, y); break;
492            case 'seahorses': spawnSeahorse(x, y); break;
493            case 'starfish': spawnStarfish(x, y); break;
494            case 'coral': spawnCoral(x, y); break;
495            case 'mushrooms': spawnMushroom(x, y); break;
496            case 'feathers': spawnFeather(x, y); break;
497            case 'fruit': spawnFruit(x, y); break;
498            case 'portals': spawnPortal(x, y); break;
499            case 'manaOrbs': spawnManaOrb(x, y); break;
500            case 'vinylRecords': spawnVinylRecord(x, y); break;
501            case 'coffeeBeans': spawnCoffeeBean(x, y); break;
502            case 'sprinkles': spawnSprinkle(x, y); break;
503            case 'dice': spawnDice(x, y); break;
504            case 'chatBubbles': spawnChatBubble(x, y); break;
505            case 'spaghetti': spawnSpaghetti(x, y); break;
506        }
507
508        for (let i = lenBefore; i < particles.length; i++) {
509            tintParticleIfSupported(particles[i], i - lenBefore);
510        }
511    }
512
513    function findLastParticleOfType(type) {
514        for (let i = particles.length - 1; i >= 0; i--) {
515            if (particles[i].type === type && particles[i].life > 0.15) {
516                return particles[i];
517            }
518        }
519        return null;
520    }
521
522    // ====================================
523    // PARTICLE TYPES
524    // ====================================
525
526    function spawnSparkle(x, y) {
527        particles.push({
528            x: x + (Math.random() - 0.5) * 10,
529            y: y + (Math.random() - 0.5) * 10,
530            vx: (Math.random() - 0.5) * 2,
531            vy: (Math.random() - 0.5) * 2,
532            life: 1,
533            maxLife: 0.5 + Math.random() * 0.5,
534            size: 2 + Math.random() * 4,
535            color: `hsl(${Math.random() * 360}, 100%, 70%)`,
536            type: 'sparkle'
537        });
538    }
539
540    function spawnFairy(x, y) {
541        particles.push({
542            x: x + (Math.random() - 0.5) * 15,
543            y: y + (Math.random() - 0.5) * 15,
544            vx: (Math.random() - 0.5) * 1,
545            vy: -1 - Math.random() * 2,
546            life: 1,
547            maxLife: 1 + Math.random() * 1,
548            size: 3 + Math.random() * 3,
549            color: `hsl(${280 + Math.random() * 60}, 100%, 80%)`,
550            glow: 15,
551            type: 'fairy'
552        });
553    }
554
555    function spawnRainbow(x, y) {
556        const hue = (Date.now() / 10) % 360;
557        particles.push({
558            x: x + (Math.random() - 0.5) * 8,
559            y: y + (Math.random() - 0.5) * 8,
560            vx: (Math.random() - 0.5) * 1.5,
561            vy: (Math.random() - 0.5) * 1.5,
562            life: 1,
563            maxLife: 0.8 + Math.random() * 0.4,
564            size: 4 + Math.random() * 4,
565            color: `hsl(${hue}, 100%, 60%)`,
566            type: 'rainbow'
567        });
568    }
569
570    function spawnBubble(x, y) {
571        particles.push({
572            x: x + (Math.random() - 0.5) * 12,
573            y: y + (Math.random() - 0.5) * 12,
574            vx: (Math.random() - 0.5) * 0.5,
575            vy: -0.5 - Math.random() * 1.5,
576            life: 1,
577            maxLife: 1.5 + Math.random() * 1,
578            size: 5 + Math.random() * 10,
579            rotation: Math.random() * Math.PI * 2,
580            rotationSpeed: (Math.random() - 0.5) * 0.1,
581            type: 'bubble'
582        });
583    }
584
585    function spawnHeart(x, y) {
586        if (Math.random() > 0.7) { // Spawn less frequently
587            particles.push({
588                x: x + (Math.random() - 0.5) * 10,
589                y: y + (Math.random() - 0.5) * 10,
590                vx: (Math.random() - 0.5) * 0.8,
591                vy: -0.5 - Math.random() * 1,
592                life: 1,
593                maxLife: 1.2 + Math.random() * 0.8,
594                size: 10 + Math.random() * 8,
595                rotation: (Math.random() - 0.5) * 0.5,
596                rotationSpeed: (Math.random() - 0.5) * 0.05,
597                type: 'heart'
598            });
599        }
600    }
601
602    function spawnStar(x, y) {
603        particles.push({
604            x: x + (Math.random() - 0.5) * 12,
605            y: y + (Math.random() - 0.5) * 12,
606            vx: (Math.random() - 0.5) * 2,
607            vy: (Math.random() - 0.5) * 2,
608            life: 1,
609            maxLife: 0.6 + Math.random() * 0.6,
610            size: 8 + Math.random() * 8,
611            rotation: Math.random() * Math.PI * 2,
612            rotationSpeed: (Math.random() - 0.5) * 0.2,
613            points: 5,
614            type: 'star'
615        });
616    }
617
618    function spawnFire(x, y) {
619        particles.push({
620            x: x + (Math.random() - 0.5) * 10,
621            y: y + Math.random() * 10,
622            vx: (Math.random() - 0.5) * 1,
623            vy: -2 - Math.random() * 3,
624            life: 1,
625            maxLife: 0.4 + Math.random() * 0.4,
626            size: 8 + Math.random() * 12,
627            color: Math.random() > 0.5 ? '#ff6b00' : '#ff0000',
628            type: 'fire'
629        });
630    }
631
632    function spawnSnow(x, y) {
633        particles.push({
634            x: x + (Math.random() - 0.5) * 15,
635            y: y + (Math.random() - 0.5) * 15,
636            vx: (Math.random() - 0.5) * 0.5,
637            vy: 0.5 + Math.random() * 1.5,
638            life: 1,
639            maxLife: 1.5 + Math.random() * 1,
640            size: 3 + Math.random() * 5,
641            rotation: Math.random() * Math.PI * 2,
642            rotationSpeed: (Math.random() - 0.5) * 0.05,
643            type: 'snow'
644        });
645    }
646
647    function spawnLeaf(x, y) {
648        if (Math.random() > 0.6) { // Spawn less frequently
649            particles.push({
650                x: x + (Math.random() - 0.5) * 15,
651                y: y + (Math.random() - 0.5) * 15,
652                vx: (Math.random() - 0.5) * 2,
653                vy: 1 + Math.random() * 2,
654                life: 1,
655                maxLife: 2 + Math.random() * 1,
656                size: 8 + Math.random() * 8,
657                rotation: Math.random() * Math.PI * 2,
658                rotationSpeed: (Math.random() - 0.5) * 0.15,
659                color: ['#ff6b00', '#ff8c00', '#ffa500', '#8b4513', '#a0522d'][Math.floor(Math.random() * 5)],
660                type: 'leaf'
661            });
662        }
663    }
664
665    // ====================================
666    // NEW PARTICLE TYPES (40 MORE!)
667    // ====================================
668
669    function spawnLightning(x, y) {
670        particles.push({
671            x: x + (Math.random() - 0.5) * 30,
672            y: y + (Math.random() - 0.5) * 30,
673            vx: 0,
674            vy: 0,
675            life: 1,
676            maxLife: 0.2,
677            segments: Array.from({length: 5}, () => ({
678                x: (Math.random() - 0.5) * 20,
679                y: Math.random() * 30
680            })),
681            color: ['#fff', '#c0f', '#0ff'][Math.floor(Math.random() * 3)],
682            type: 'lightning'
683        });
684    }
685
686    function spawnCherryBlossom(x, y) {
687        particles.push({
688            x: x + (Math.random() - 0.5) * 10,
689            y: y + (Math.random() - 0.5) * 10,
690            vx: (Math.random() - 0.5) * 1.5,
691            vy: 0.5 + Math.random() * 1,
692            life: 1,
693            maxLife: 2 + Math.random(),
694            size: 6 + Math.random() * 4,
695            rotation: Math.random() * Math.PI * 2,
696            rotationSpeed: (Math.random() - 0.5) * 0.1,
697            wobble: Math.random() * Math.PI * 2,
698            type: 'cherryBlossom'
699        });
700    }
701
702    function spawnFirefly(x, y) {
703        particles.push({
704            x: x + (Math.random() - 0.5) * 20,
705            y: y + (Math.random() - 0.5) * 20,
706            vx: (Math.random() - 0.5) * 0.5,
707            vy: (Math.random() - 0.5) * 0.5,
708            life: 1,
709            maxLife: 2 + Math.random() * 2,
710            size: 3 + Math.random() * 2,
711            glow: 20,
712            pulse: Math.random() * Math.PI * 2,
713            color: ['#ffeb3b', '#76ff03'][Math.floor(Math.random() * 2)],
714            type: 'firefly'
715        });
716    }
717
718    function spawnButterfly(x, y) {
719        if (Math.random() > 0.7) {
720            particles.push({
721                x: x + (Math.random() - 0.5) * 10,
722                y: y + (Math.random() - 0.5) * 10,
723                vx: (Math.random() - 0.5) * 2,
724                vy: -1 - Math.random(),
725                life: 1,
726                maxLife: 3 + Math.random() * 2,
727                size: 10 + Math.random() * 5,
728                wingFlap: 0,
729                color: ['#ff69b4', '#9c27b0', '#00bcd4', '#ffeb3b'][Math.floor(Math.random() * 4)],
730                type: 'butterfly'
731            });
732        }
733    }
734
735    function spawnComet(x, y) {
736        const angle = Math.random() * Math.PI * 2;
737        particles.push({
738            x: x,
739            y: y,
740            vx: Math.cos(angle) * 5,
741            vy: Math.sin(angle) * 5,
742            life: 1,
743            maxLife: 0.6,
744            size: 4 + Math.random() * 3,
745            tail: [],
746            color: ['#fff', '#ff0', '#0ff'][Math.floor(Math.random() * 3)],
747            type: 'comet'
748        });
749    }
750
751    function spawnMagicRune(x, y) {
752        if (Math.random() > 0.5) {
753            particles.push({
754                x: x + (Math.random() - 0.5) * 10,
755                y: y + (Math.random() - 0.5) * 10,
756                vx: 0,
757                vy: -0.5 - Math.random() * 0.5,
758                life: 1,
759                maxLife: 2,
760                size: 15 + Math.random() * 10,
761                rotation: Math.random() * Math.PI * 2,
762                rotationSpeed: (Math.random() - 0.5) * 0.1,
763                rune: ['ᚠ', 'ᚢ', 'ᚦ', 'ᚨ', 'ᚱ', 'ᚲ'][Math.floor(Math.random() * 6)],
764                color: `hsl(${270 + Math.random() * 60}, 100%, 70%)`,
765                type: 'magicRune'
766            });
767        }
768    }
769
770    function spawnCrystal(x, y) {
771        particles.push({
772            x: x + (Math.random() - 0.5) * 10,
773            y: y + (Math.random() - 0.5) * 10,
774            vx: (Math.random() - 0.5) * 1,
775            vy: (Math.random() - 0.5) * 1,
776            life: 1,
777            maxLife: 1.5,
778            size: 8 + Math.random() * 6,
779            rotation: Math.random() * Math.PI * 2,
780            rotationSpeed: (Math.random() - 0.5) * 0.15,
781            sides: Math.floor(3 + Math.random() * 3),
782            color: `hsl(${Math.random() * 360}, 100%, 70%)`,
783            type: 'crystal'
784        });
785    }
786
787    function spawnEnergyOrb(x, y) {
788        particles.push({
789            x: x + (Math.random() - 0.5) * 15,
790            y: y + (Math.random() - 0.5) * 15,
791            vx: (Math.random() - 0.5) * 2,
792            vy: (Math.random() - 0.5) * 2,
793            life: 1,
794            maxLife: 1 + Math.random(),
795            size: 8 + Math.random() * 8,
796            glow: 25,
797            pulse: Math.random() * Math.PI * 2,
798            color: `hsl(${180 + Math.random() * 60}, 100%, 60%)`,
799            type: 'energyOrb'
800        });
801    }
802
803    function spawnMatrix(x, y) {
804        particles.push({
805            x: x + (Math.random() - 0.5) * 20,
806            y: y - 20,
807            vx: 0,
808            vy: 3 + Math.random() * 2,
809            life: 1,
810            maxLife: 1,
811            char: String.fromCharCode(0x30A0 + Math.random() * 96),
812            size: 12 + Math.random() * 6,
813            type: 'matrix'
814        });
815    }
816
817    function spawnNeonLine(x, y) {
818        const angle = Math.atan2(mouseVelY, mouseVelX);
819        particles.push({
820            x: x,
821            y: y,
822            vx: 0,
823            vy: 0,
824            angle: angle,
825            length: 20 + Math.random() * 30,
826            life: 1,
827            maxLife: 0.4,
828            color: `hsl(${Math.random() * 360}, 100%, 60%)`,
829            glow: 15,
830            type: 'neonLine'
831        });
832    }
833
834    function spawnGlitch(x, y) {
835        particles.push({
836            x: x + (Math.random() - 0.5) * 30,
837            y: y + (Math.random() - 0.5) * 30,
838            vx: (Math.random() - 0.5) * 5,
839            vy: (Math.random() - 0.5) * 5,
840            life: 1,
841            maxLife: 0.3,
842            width: 10 + Math.random() * 30,
843            height: 2 + Math.random() * 10,
844            color: ['#f00', '#0f0', '#00f', '#f0f', '#0ff'][Math.floor(Math.random() * 5)],
845            type: 'glitch'
846        });
847    }
848
849    function spawnMusicNote(x, y) {
850        if (Math.random() > 0.6) {
851            particles.push({
852                x: x + (Math.random() - 0.5) * 10,
853                y: y + (Math.random() - 0.5) * 10,
854                vx: (Math.random() - 0.5) * 0.5,
855                vy: -1 - Math.random(),
856                life: 1,
857                maxLife: 2,
858                size: 12 + Math.random() * 6,
859                note: ['♪', '♫', '♬', '♩'][Math.floor(Math.random() * 4)],
860                color: `hsl(${Math.random() * 360}, 70%, 60%)`,
861                type: 'musicNote'
862            });
863        }
864    }
865
866    function spawnConfetti(x, y) {
867        particles.push({
868            x: x + (Math.random() - 0.5) * 10,
869            y: y + (Math.random() - 0.5) * 10,
870            vx: (Math.random() - 0.5) * 3,
871            vy: -2 - Math.random() * 2,
872            life: 1,
873            maxLife: 1.5,
874            size: 4 + Math.random() * 6,
875            rotation: Math.random() * Math.PI * 2,
876            rotationSpeed: (Math.random() - 0.5) * 0.3,
877            shape: ['rect', 'circle'][Math.floor(Math.random() * 2)],
878            color: `hsl(${Math.random() * 360}, 100%, 60%)`,
879            type: 'confetti'
880        });
881    }
882
883    function spawnPaintSplatter(x, y) {
884        particles.push({
885            x: x + (Math.random() - 0.5) * 15,
886            y: y + (Math.random() - 0.5) * 15,
887            vx: (Math.random() - 0.5) * 2,
888            vy: (Math.random() - 0.5) * 2,
889            life: 1,
890            maxLife: 1,
891            size: 10 + Math.random() * 15,
892            splats: Array.from({length: 5}, () => ({
893                angle: Math.random() * Math.PI * 2,
894                length: Math.random() * 10
895            })),
896            color: `hsl(${Math.random() * 360}, 80%, 55%)`,
897            type: 'paintSplatter'
898        });
899    }
900
901    function spawnSmoke(x, y) {
902        particles.push({
903            x: x + (Math.random() - 0.5) * 10,
904            y: y + (Math.random() - 0.5) * 10,
905            vx: (Math.random() - 0.5) * 0.5,
906            vy: -0.5 - Math.random() * 0.5,
907            life: 1,
908           
908 maxLife: 2,
909            size: 15 + Math.random() * 20,
910            expansion: 1,
911            type: 'smoke'
912        });
913    }
914
915    function spawnRipple(x, y) {
916        if (Math.random() > 0.5) { // Made more frequent
917            particles.push({
918                x: x,
919                y: y,
920                vx: 0,
921                vy: 0,
922                life: 1,
923                maxLife: 1,
924                size: 5,
925                maxSize: 50 + Math.random() * 50,
926                type: 'ripple'
927            });
928        }
929    }
930
931    function spawnDNA(x, y) {
932        particles.push({
933            x: x + (Math.random() - 0.5) * 10,
934            y: y + (Math.random() - 0.5) * 10,
935            vx: 0,
936            vy: -1,
937            life: 1,
938            maxLife: 2,
939            size: 8,
940            twist: Math.random() * Math.PI * 2,
941            type: 'dna'
942        });
943    }
944
945    function spawnCircuit(x, y) {
946        particles.push({
947            x: x + (Math.random() - 0.5) * 10,
948            y: y + (Math.random() - 0.5) * 10,
949            vx: (Math.random() - 0.5) * 1,
950            vy: (Math.random() - 0.5) * 1,
951            life: 1,
952            maxLife: 1.5,
953            size: 8 + Math.random() * 8,
954            lines: Array.from({length: 4}, () => ({
955                angle: Math.random() * Math.PI * 2,
956                length: 5 + Math.random() * 10
957            })),
958            type: 'circuit'
959        });
960    }
961
962    function spawnBinary(x, y) {
963        particles.push({
964            x: x + (Math.random() - 0.5) * 20,
965            y: y - 10,
966            vx: 0,
967            vy: 2 + Math.random(),
968            life: 1,
969            maxLife: 1.5,
970            digit: Math.random() > 0.5 ? '1' : '0',
971            size: 10 + Math.random() * 4,
972            type: 'binary'
973        });
974    }
975
976    function spawnPawPrint(x, y) {
977        if (Math.random() > 0.7) { // 30% spawn rate - less frequent to avoid solid lines
978            // Load paw print image if not already loaded
979            if (!pawPrintImage) {
980                pawPrintImage = new Image();
981                pawPrintImage.onload = () => {
982                    pawPrintImageLoaded = true;
983                    emitDevLog('✅ Paw print image loaded successfully');
984                };
985                pawPrintImage.onerror = () => {
986                    emitDevWarn('❌ Failed to load paw print image from /images/paw.png, using fallback');
987                    pawPrintImageLoaded = false;
988                };
989                pawPrintImage.src = '/images/paw.png';
990            }
991            
992            particles.push({
993                x: x,
994                y: y,
995                vx: 0,
996                vy: 0,
997                life: 1,
998                maxLife: 2,
999                size: 15 + Math.random() * 5,
1000                rotation: Math.random() * Math.PI * 2,
1001                color: ['#ff69b4', '#9c27b0', '#00bcd4'][Math.floor(Math.random() * 3)],
1002                type: 'pawPrint'
1003            });
1004        }
1005    }
1006
1007    // ====================================
1008    // COLORED HEART TRAILS
1009    // ====================================
1010
1011    function spawnColoredHeart(x, y, emoji) {
1012        if (Math.random() > 0.6) { // Spawn less frequently
1013            particles.push({
1014                x: x + (Math.random() - 0.5) * 10,
1015                y: y + (Math.random() - 0.5) * 10,
1016                vx: (Math.random() - 0.5) * 0.8,
1017                vy: -0.5 - Math.random() * 1,
1018                life: 1,
1019                maxLife: 1.2 + Math.random() * 0.8,
1020                size: 16 + Math.random() * 8,
1021                rotation: (Math.random() - 0.5) * 0.5,
1022                rotationSpeed: (Math.random() - 0.5) * 0.05,
1023                emoji: emoji,
1024                type: 'coloredHeart'
1025            });
1026        }
1027    }
1028
1029    function spawnRainbowHeart(x, y) {
1030        if (Math.random() > 0.6) { // Spawn less frequently
1031            const hearts = ['❤️', '🧡', '💛', '💚', '💙', '💜'];
1032            const emoji = hearts[Math.floor(Math.random() * hearts.length)];
1033            particles.push({
1034                x: x + (Math.random() - 0.5) * 10,
1035                y: y + (Math.random() - 0.5) * 10,
1036                vx: (Math.random() - 0.5) * 0.8,
1037                vy: -0.5 - Math.random() * 1,
1038                life: 1,
1039                maxLife: 1.2 + Math.random() * 0.8,
1040                size: 16 + Math.random() * 8,
1041                rotation: (Math.random() - 0.5) * 0.5,
1042                rotationSpeed: (Math.random() - 0.5) * 0.05,
1043                emoji: emoji,
1044                type: 'coloredHeart'
1045            });
1046        }
1047    }
1048
1049    function spawnDandelionSeed(x, y) {
1050        particles.push({
1051            x: x + (Math.random() - 0.5) * 10,
1052            y: y + (Math.random() - 0.5) * 10,
1053            vx: (Math.random() - 0.5) * 0.5,
1054            vy: -0.3 - Math.random() * 0.5,
1055            life: 1,
1056            maxLife: 3 + Math.random() * 2,
1057            size: 8 + Math.random() * 4,
1058            rotation: Math.random() * Math.PI * 2,
1059            rotationSpeed: (Math.random() - 0.5) * 0.05,
1060            type: 'dandelion'
1061        });
1062    }
1063
1064    function spawnRosePetal(x, y) {
1065        particles.push({
1066            x: x + (Math.random() - 0.5) * 10,
1067            y: y + (Math.random() - 0.5) * 10,
1068            vx: (Math.random() - 0.5) * 1,
1069            vy: 0.5 + Math.random(),
1070            life: 1,
1071            maxLife: 2 + Math.random(),
1072            size: 8 + Math.random() * 6,
1073            rotation: Math.random() * Math.PI * 2,
1074            rotationSpeed: (Math.random() - 0.5) * 0.1,
1075            wobble: Math.random() * Math.PI * 2,
1076            color: ['#ff1744', '#f50057', '#ff69b4', '#ff80ab'][Math.floor(Math.random() * 4)],
1077            type: 'rosePetal'
1078        });
1079    }
1080
1081    function spawnPixel(x, y) {
1082        particles.push({
1083            x: Math.floor(x / 8) * 8,
1084            y: Math.floor(y / 8) * 8,
1085            vx: (Math.random() - 0.5) * 2,
1086            vy: (Math.random() - 0.5) * 2,
1087            life: 1,
1088            maxLife: 0.8,
1089            size: 8,
1090            color: `hsl(${Math.random() * 360}, 80%, 60%)`,
1091            type: 'pixelated'
1092        });
1093    }
1094
1095    function spawnGalaxy(x, y) {
1096        particles.push({
1097            x: x + (Math.random() - 0.5) * 20,
1098            y: y + (Math.random() - 0.5) * 20,
1099            vx: (Math.random() - 0.5) * 0.5,
1100            vy: (Math.random() - 0.5) * 0.5,
1101            life: 1,
1102            maxLife: 2 + Math.random(),
1103            size: 3 + Math.random() * 5,
1104            orbitAngle: Math.random() * Math.PI * 2,
1105            orbitSpeed: (Math.random() - 0.5) * 0.1,
1106            color: ['#fff', '#c9f', '#9cf', '#fc9'][Math.floor(Math.random() * 4)],
1107            type: 'galaxy'
1108        });
1109    }
1110
1111    function spawnAuroraWave(x, y) {
1112        particles.push({
1113            x: x,
1114            y: y + (Math.random() - 0.5) * 20,
1115            vx: (Math.random() - 0.5) * 2,
1116            vy: -0.5,
1117            life: 1,
1118            maxLife: 2,
1119            width: 30 + Math.random() * 40,
1120            height: 20 + Math.random() * 20,
1121            wave: Math.random() * Math.PI * 2,
1122            color: `hsl(${150 + Math.random() * 80}, 80%, 60%)`,
1123            type: 'auroraWave'
1124        });
1125    }
1126
1127    function spawnGeometric(x, y) {
1128        particles.push({
1129            x: x + (Math.random() - 0.5) * 10,
1130            y: y + (Math.random() - 0.5) * 10,
1131            vx: (Math.random() - 0.5) * 1.5,
1132            vy: (Math.random() - 0.5) * 1.5,
1133            life: 1,
1134            maxLife: 1.5,
1135            size: 10 + Math.random() * 10,
1136            rotation: Math.random() * Math.PI * 2,
1137            rotationSpeed: (Math.random() - 0.5) * 0.2,
1138            sides: Math.floor(3 + Math.random() * 5),
1139            color: `hsl(${Math.random() * 360}, 70%, 60%)`,
1140            type: 'geometric'
1141        });
1142    }
1143
1144    function spawnSpiral(x, y) {
1145        particles.push({
1146            x: x,
1147            y: y,
1148            centerX: x,
1149            centerY: y,
1150            vx: 0,
1151            vy: 0,
1152            angle: 0,
1153            radius: 0,
1154            angleSpeed: 0.3,
1155            radiusSpeed: 2,
1156            life: 1,
1157            maxLife: 1.5,
1158            color: `hsl(${Math.random() * 360}, 80%, 60%)`,
1159            type: 'spiral'
1160        });
1161    }
1162
1163    function spawnInkBlot(x, y) {
1164        particles.push({
1165            x: x + (Math.random() - 0.5) * 10,
1166            y: y + (Math.random() - 0.5) * 10,
1167            vx: (Math.random() - 0.5) * 1,
1168            vy: (Math.random() - 0.5) * 1,
1169            life: 1,
1170            maxLife: 1.5,
1171            size: 15 + Math.random() * 15,
1172            expansion: 1,
1173            blobs: Array.from({length: 3}, () => ({
1174                x: (Math.random() - 0.5) * 10,
1175                y: (Math.random() - 0.5) * 10,
1176                size: 5 + Math.random() * 10
1177            })),
1178            color: ['#000', '#1a237e', '#4a148c'][Math.floor(Math.random() * 3)],
1179            type: 'inkBlot'
1180        });
1181    }
1182
1183    function spawnPaintStroke(x, y) {
1184        const angle = Math.atan2(mouseVelY, mouseVelX);
1185        particles.push({
1186            x: x,
1187            y: y,
1188            vx: 0,
1189            vy: 0,
1190            angle: angle,
1191            length: 20 + Math.random() * 30,
1192            width: 5 + Math.random() * 10,
1193            life: 1,
1194            maxLife: 0.8,
1195            color: `hsl(${Math.random() * 360}, 70%, 55%)`,
1196            type: 'paintStroke'
1197        });
1198    }
1199
1200    function spawnLaser(x, y) {
1201        const angle = Math.atan2(mouseVelY, mouseVelX);
1202        particles.push({
1203            x: x,
1204            y: y,
1205            vx: 0,
1206            vy: 0,
1207            angle: angle,
1208            length: 40 + Math.random() * 60,
1209            life: 1,
1210            maxLife: 0.3,
1211            color: ['#f00', '#0f0', '#00f', '#f0f', '#0ff'][Math.floor(Math.random() * 5)],
1212            glow: 20,
1213            type: 'laser'
1214        });
1215    }
1216
1217    function spawnHologram(x, y) {
1218        particles.push({
1219            x: x + (Math.random() - 0.5) * 15,
1220            y: y + (Math.random() - 0.5) * 15,
1221            vx: 0,
1222            vy: -0.5,
1223            life: 1,
1224            maxLife: 1.5,
1225            size: 10 + Math.random() * 10,
1226            scanLines: 3,
1227            color: `hsl(${180 + Math.random() * 40}, 100%, 60%)`,
1228            type: 'hologram'
1229        });
1230    }
1231
1232    function spawnCloud(x, y) {
1233        particles.push({
1234            x: x + (Math.random() - 0.5) * 20,
1235            y: y + (Math.random() - 0.5) * 20,
1236            vx: 0.5 + Math.random() * 0.5,
1237            vy: -0.2,
1238            life: 1,
1239            maxLife: 3 + Math.random() * 2,
1240            size: 20 + Math.random() * 20,
1241            puffs: Array.from({length: 3}, () => ({
1242                x: (Math.random() - 0.5) * 15,
1243                y: (Math.random() - 0.5) * 8,
1244                size: 8 + Math.random() * 12
1245            })),
1246            type: 'cloud'
1247        });
1248    }
1249
1250    function spawnTornado(x, y) {
1251        particles.push({
1252            x: x,
1253            y: y,
1254            centerX: x + (Math.random() - 0.5) * 10,
1255            centerY: y,
1256            vx: 0,
1257            vy: -2,
1258            angle: Math.random() * Math.PI * 2,
1259            radius: 0,
1260            angleSpeed: 0.4,
1261            radiusSpeed: 1.5,
1262            life: 1,
1263            maxLife: 1.5,
1264            color: `rgba(150, 150, 150, ${0.5 + Math.random() * 0.3})`,
1265            type: 'tornado'
1266        });
1267    }
1268
1269    function spawnEmoji(x, y) {
1270        if (Math.random() > 0.6) {
1271            particles.push({
1272                x: x + (Math.random() - 0.5) * 10,
1273                y: y + (Math.random() - 0.5) * 10,
1274                vx: (Math.random() - 0.5) * 1,
1275                vy: -0.5 - Math.random() * 0.5,
1276                life: 1,
1277                maxLife: 2,
1278                size: 16 + Math.random() * 8,
1279                emoji: ['✨', '💖', '🌟', '🎀', '🦋', '🌸', '💫', '🎨'][Math.floor(Math.random() * 8)],
1280                rotation: (Math.random() - 0.5) * 0.5,
1281                rotationSpeed: (Math.random() - 0.5) * 0.05,
1282                type: 'emoji'
1283            });
1284        }
1285    }
1286
1287    function spawnCandy(x, y) {
1288        if (Math.random() > 0.7) {
1289            particles.push({
1290                x: x + (Math.random() - 0.5) * 10,
1291                y: y + (Math.random() - 0.5) * 10,
1292                vx: (Math.random() - 0.5) * 1,
1293                vy: (Math.random() - 0.5) * 1,
1294                life: 1,
1295                maxLife: 2,
1296                size: 10 + Math.random() * 8,
1297                rotation: Math.random() * Math.PI * 2,
1298                rotationSpeed: (Math.random() - 0.5) * 0.15,
1299                candyType: Math.floor(Math.random() * 3), // lollipop, wrapped candy, heart
1300                color: `hsl(${Math.random() * 360}, 100%, 65%)`,
1301                type: 'candy'
1302            });
1303        }
1304    }
1305
1306    function spawnElectricSpark(x, y) {
1307        particles.push({
1308            x: x + (Math.random() - 0.5) * 15,
1309            y: y + (Math.random() - 0.5) * 15,
1310            vx: (Math.random() - 0.5) * 3,
1311            vy: (Math.random() - 0.5) * 3,
1312            life: 1,
1313            maxLife: 0.4,
1314            size: 3 + Math.random() * 4,
1315            branches: Array.from({length: 3}, () => ({
1316                angle: Math.random() * Math.PI * 2,
1317                length: 5 + Math.random() * 10
1318            })),
1319            color: ['#fff', '#ff0', '#0ff'][Math.floor(Math.random() * 3)],
1320            glow: 15,
1321            type: 'electricSpark'
1322        });
1323    }
1324
1325    function spawnPlasma(x, y) {
1326        particles.push({
1327            x: x + (Math.random() - 0.5) * 15,
1328            y: y + (Math.random() - 0.5) * 15,
1329            vx: (Math.random() - 0.5) * 1,
1330            vy: (Math.random() - 0.5) * 1,
1331            life: 1,
1332            maxLife: 1.5,
1333            size: 12 + Math.random() * 12,
1334            glow: 30,
1335            pulse: Math.random() * Math.PI * 2,
1336            color: `hsl(${280 + Math.random() * 40}, 100%, 60%)`,
1337            type: 'plasma'
1338        });
1339    }
1340
1341    function spawnVortex(x, y) {
1342        particles.push({
1343            x: x,
1344            y: y,
1345            centerX: x,
1346            centerY: y,
1347            vx: 0,
1348            vy: 0,
1349            angle: Math.random() * Math.PI * 2,
1350            radius: 5,
1351            angleSpeed: 0.5,
1352            radiusSpeed: -0.5,
1353            life: 1,
1354            maxLife: 1.5,
1355            size: 3,
1356            color: `hsl(${Math.random() * 360}, 80%, 60%)`,
1357            type: 'vortex'
1358        });
1359    }
1360
1361    function spawnCosmicDust(x, y) {
1362        particles.push({
1363            x: x + (Math.random() - 0.5) * 25,
1364            y: y + (Math.random() - 0.5) * 25,
1365            vx: (Math.random() - 0.5) * 0.5,
1366            vy: (Math.random() - 0.5) * 0.5,
1367            life: 1,
1368            maxLife: 2 + Math.random() * 2,
1369            size: 2 + Math.random() * 3,
1370            twinkle: Math.random() * Math.PI * 2,
1371            color: ['#fff', '#ffd', '#dff', '#fdf'][Math.floor(Math.random() * 4)],
1372            type: 'cosmicDust'
1373        });
1374    }
1375
1376    // ====================================
1377    // NEW PHYSICS & OCEAN TRAILS
1378    // ====================================
1379
1380    function spawnBouncyBall(x, y) {
1381        particles.push({
1382            x: x + (Math.random() - 0.5) * 10,
1383            y: y + (Math.random() - 0.5) * 10,
1384            vx: (Math.random() - 0.5) * 3,
1385            vy: (Math.random() - 0.5) * 3,
1386            life: 1,
1387            maxLife: 3 + Math.random() * 2,
1388            size: 8 + Math.random() * 8,
1389            color: `hsl(${Math.random() * 360}, 80%, 60%)`,
1390            bounce: 0.7, // Bounce coefficient
1391            gravity: 0.3,
1392            type: 'bouncyBall'
1393        });
1394    }
1395
1396    function spawnPhysicsBox(x, y) {
1397        particles.push({
1398            x: x + (Math.random() - 0.5) * 10,
1399            y: y + (Math.random() - 0.5) * 10,
1400            vx: (Math.random() - 0.5) * 2,
1401            vy: (Math.random() - 0.5) * 2,
1402            life: 1,
1403            maxLife: 3 + Math.random() * 2,
1404            size: 10 + Math.random() * 10,
1405            rotation: Math.random() * Math.PI * 2,
1406            rotationSpeed: (Math.random() - 0.5) * 0.2,
1407            bounce: 0.6,
1408            gravity: 0.25,
1409            color: `hsl(${Math.random() * 360}, 70%, 55%)`,
1410            type: 'physicsBox'
1411        });
1412    }
1413
1414    function spawnJellyfish(x, y) {
1415        if (Math.random() > 0.5) { // Spawn less frequently
1416            const gifPath = Math.random() > 0.5 ? '/i
1416mages/fishes/jellyfish.gif' : '/images/fishes/jellyfishfast.gif';
1417            const img = new Image();
1418            img.src = gifPath;
1419            
1420            particles.push({
1421                x: x + (Math.random() - 0.5) * 15,
1422                y: y + (Math.random() - 0.5) * 15,
1423                vx: (Math.random() - 0.5) * 0.5,
1424                vy: -0.3 - Math.random() * 0.5,
1425                life: 1,
1426                maxLife: 4 + Math.random() * 2,
1427                size: 30 + Math.random() * 20,
1428                pulse: Math.random() * Math.PI * 2,
1429                pulseSpeed: 0.8 + Math.random() * 0.4,
1430                image: img,
1431                gifPath: gifPath,
1432                hue: Math.random() * 360,
1433                type: 'jellyfish'
1434            });
1435        }
1436    }
1437
1438    function spawnFish(x, y) {
1439        if (Math.random() > 0.6) {
1440            particles.push({
1441                x: x + (Math.random() - 0.5) * 10,
1442                y: y + (Math.random() - 0.5) * 10,
1443                vx: (Math.random() - 0.5) * 2,
1444                vy: (Math.random() - 0.5) * 1,
1445                life: 1,
1446                maxLife: 2 + Math.random() * 2,
1447                size: 12 + Math.random() * 8,
1448                tailWag: Math.random() * Math.PI * 2,
1449                direction: Math.random() > 0.5 ? 1 : -1,
1450                color: `hsl(${180 + Math.random() * 60}, 70%, 50%)`,
1451                type: 'fish'
1452            });
1453        }
1454    }
1455
1456    function spawnAcorn(x, y) {
1457        if (Math.random() > 0.6) {
1458            particles.push({
1459                x: x + (Math.random() - 0.5) * 10,
1460                y: y + (Math.random() - 0.5) * 10,
1461                vx: (Math.random() - 0.5) * 1,
1462                vy: 0.5 + Math.random() * 1.5,
1463                life: 1,
1464                maxLife: 2 + Math.random() * 1,
1465                size: 8 + Math.random() * 6,
1466                rotation: Math.random() * Math.PI * 2,
1467                rotationSpeed: (Math.random() - 0.5) * 0.1,
1468                gravity: 0.2,
1469                type: 'acorn'
1470            });
1471        }
1472    }
1473
1474    function spawnPinecone(x, y) {
1475        if (Math.random() > 0.6) {
1476            particles.push({
1477                x: x + (Math.random() - 0.5) * 10,
1478                y: y + (Math.random() - 0.5) * 10,
1479                vx: (Math.random() - 0.5) * 1,
1480                vy: 0.5 + Math.random() * 1.5,
1481                life: 1,
1482                maxLife: 2.5 + Math.random() * 1,
1483                size: 10 + Math.random() * 8,
1484                rotation: Math.random() * Math.PI * 2,
1485                rotationSpeed: (Math.random() - 0.5) * 0.15,
1486                gravity: 0.2,
1487                type: 'pinecone'
1488            });
1489        }
1490    }
1491
1492    function spawnRaindrop(x, y) {
1493        particles.push({
1494            x: x + (Math.random() - 0.5) * 5,
1495            y: y - 5,
1496            vx: (Math.random() - 0.5) * 0.3,
1497            vy: 2 + Math.random() * 2,
1498            life: 1,
1499            maxLife: 1.5,
1500            size: 2 + Math.random() * 3,
1501            splashY: null, // Will be set when hitting bottom
1502            type: 'raindrop'
1503        });
1504    }
1505
1506    function spawnMist(x, y) {
1507        particles.push({
1508            x: x + (Math.random() - 0.5) * 20,
1509            y: y + (Math.random() - 0.5) * 20,
1510            vx: (Math.random() - 0.5) * 0.3,
1511            vy: -0.2 - Math.random() * 0.3,
1512            life: 1,
1513            maxLife: 3 + Math.random() * 2,
1514            size: 20 + Math.random() * 30,
1515            expansion: 1,
1516            opacity: 0.3 + Math.random() * 0.2,
1517            type: 'mist'
1518        });
1519    }
1520
1521    function spawnFog(x, y) {
1522        particles.push({
1523            x: x + (Math.random() - 0.5) * 25,
1524            y: y + (Math.random() - 0.5) * 25,
1525            vx: (Math.random() - 0.5) * 0.2,
1526            vy: -0.1 - Math.random() * 0.2,
1527            life: 1,
1528            maxLife: 4 + Math.random() * 2,
1529            size: 30 + Math.random() * 40,
1530            expansion: 1,
1531            opacity: 0.2 + Math.random() * 0.15,
1532            type: 'fog'
1533        });
1534    }
1535
1536    function spawnChalk(x, y) {
1537        particles.push({
1538            x: x + (Math.random() - 0.5) * 8,
1539            y: y + (Math.random() - 0.5) * 8,
1540            vx: (Math.random() - 0.5) * 1.5,
1541            vy: (Math.random() - 0.5) * 1.5,
1542            life: 1,
1543            maxLife: 1.5 + Math.random() * 0.5,
1544            size: 3 + Math.random() * 4,
1545            smudge: 0,
1546            color: ['#fff', '#f0f0f0', '#e0e0e0', '#d0d0d0'][Math.floor(Math.random() * 4)],
1547            type: 'chalk'
1548        });
1549    }
1550
1551    function spawnPastel(x, y) {
1552        particles.push({
1553            x: x + (Math.random() - 0.5) * 10,
1554            y: y + (Math.random() - 0.5) * 10,
1555            vx: (Math.random() - 0.5) * 1,
1556            vy: (Math.random() - 0.5) * 1,
1557            life: 1,
1558            maxLife: 2 + Math.random(),
1559            size: 8 + Math.random() * 10,
1560            blend: 0,
1561            color: `hsl(${Math.random() * 360}, 40%, 75%)`,
1562            type: 'pastel'
1563        });
1564    }
1565
1566    function spawnDataPacket(x, y) {
1567        particles.push({
1568            x: x + (Math.random() - 0.5) * 15,
1569            y: y + (Math.random() - 0.5) * 15,
1570            vx: (Math.random() - 0.5) * 2,
1571            vy: (Math.random() - 0.5) * 2,
1572            life: 1,
1573            maxLife: 1.5 + Math.random(),
1574            size: 6 + Math.random() * 6,
1575            data: Math.random().toString(36).substring(2, 5).toUpperCase(),
1576            path: [],
1577            color: `hsl(${120 + Math.random() * 60}, 80%, 50%)`,
1578            type: 'dataPacket'
1579        });
1580    }
1581
1582    function spawnSoundWave(x, y) {
1583        particles.push({
1584            x: x,
1585            y: y,
1586            vx: 0,
1587            vy: 0,
1588            life: 1,
1589            maxLife: 1.5,
1590            size: 5,
1591            maxSize: 40 + Math.random() * 30,
1592            frequency: 0.1 + Math.random() * 0.1,
1593            phase: Math.random() * Math.PI * 2,
1594            color: `hsl(${200 + Math.random() * 60}, 70%, 60%)`,
1595            type: 'soundWave'
1596        });
1597    }
1598
1599    function spawnTrafficLight(x, y) {
1600        if (Math.random() > 0.7) {
1601            particles.push({
1602                x: x + (Math.random() - 0.5) * 10,
1603                y: y + (Math.random() - 0.5) * 10,
1604                vx: (Math.random() - 0.5) * 0.5,
1605                vy: -0.5 - Math.random() * 0.5,
1606                life: 1,
1607                maxLife: 3,
1608                size: 12 + Math.random() * 6,
1609                state: Math.floor(Math.random() * 3), // 0=red, 1=yellow, 2=green
1610                cycleTime: 0,
1611                type: 'trafficLight'
1612            });
1613        }
1614    }
1615
1616    function spawnBee(x, y) {
1617        if (Math.random() > 0.6) {
1618            particles.push({
1619                x: x + (Math.random() - 0.5) * 10,
1620                y: y + (Math.random() - 0.5) * 10,
1621                vx: (Math.random() - 0.5) * 2,
1622                vy: (Math.random() - 0.5) * 2,
1623                life: 1,
1624                maxLife: 2.5 + Math.random() * 1.5,
1625                size: 8 + Math.random() * 6,
1626                wingFlap: Math.random() * Math.PI * 2,
1627                zigzag: 0,
1628                color: '#ffeb3b',
1629                type: 'bee'
1630            });
1631        }
1632    }
1633
1634    function spawnLensFlare(x, y) {
1635        particles.push({
1636            x: x,
1637            y: y,
1638            vx: 0,
1639            vy: 0,
1640            life: 1,
1641            maxLife: 0.8,
1642            size: 20 + Math.random() * 30,
1643            angle: Math.random() * Math.PI * 2,
1644            rotation: Math.random() * Math.PI * 2,
1645            rotationSpeed: (Math.random() - 0.5) * 0.3,
1646            color: `rgba(255, 255, 255, ${0.6 + Math.random() * 0.3})`,
1647            type: 'lensFlare'
1648        });
1649    }
1650
1651    function spawnPrism(x, y) {
1652        particles.push({
1653            x: x + (Math.random() - 0.5) * 10,
1654            y: y + (Math.random() - 0.5) * 10,
1655            vx: (Math.random() - 0.5) * 1,
1656            vy: (Math.random() - 0.5) * 1,
1657            life: 1,
1658            maxLife: 2 + Math.random(),
1659            size: 12 + Math.random() * 10,
1660            rotation: Math.random() * Math.PI * 2,
1661            rotationSpeed: (Math.random() - 0.5) * 0.1,
1662            lightAngle: Math.random() * Math.PI * 2,
1663            type: 'prism'
1664        });
1665    }
1666
1667    // ====================================
1668    // NEW TRAILS — physics, ocean, nature, fantasy, food, casino, chat
1669    // ====================================
1670
1671    function spawnChainLink(x, y) {
1672        if (Math.random() > 0.45) return;
1673        const parent = findLastParticleOfType('chainLink');
1674        particles.push({
1675            x: x + (Math.random() - 0.5) * 8,
1676            y: y + (Math.random() - 0.5) * 8,
1677            vx: (Math.random() - 0.5) * 2 + mouseVelX * 0.1,
1678            vy: (Math.random() - 0.5) * 2 + mouseVelY * 0.1,
1679            life: 1,
1680            maxLife: 3.5 + Math.random() * 2,
1681            size: 7 + Math.random() * 5,
1682            rotation: Math.random() * Math.PI * 2,
1683            rotationSpeed: (Math.random() - 0.5) * 0.15,
1684            bounce: 0.55,
1685            gravity: 0.28,
1686            parent: parent,
1687            linkLength: 14 + Math.random() * 6,
1688            color: `hsl(${35 + Math.random() * 25}, 50%, ${42 + Math.random() * 18}%)`,
1689            type: 'chainLink'
1690        });
1691    }
1692
1693    function spawnDomino(x, y) {
1694        if (Math.random() > 0.5) return;
1695        particles.push({
1696            x: x + (Math.random() - 0.5) * 12,
1697            y: y + (Math.random() - 0.5) * 12,
1698            vx: 0,
1699            vy: 0,
1700            life: 1,
1701            maxLife: 4 + Math.random() * 2,
1702            width: 6 + Math.random() * 4,
1703            height: 18 + Math.random() * 10,
1704            size: 12,
1705            rotation: 0,
1706            rotationSpeed: 0,
1707            fallen: false,
1708            bounce: 0.45,
1709            gravity: 0.32,
1710            dots: Math.floor(Math.random() * 6) + 1,
1711            color: '#f5f5f5',
1712            type: 'domino'
1713        });
1714    }
1715
1716    function spawnSeahorse(x, y) {
1717        if (Math.random() > 0.55) return;
1718        particles.push({
1719            x: x + (Math.random() - 0.5) * 14,
1720            y: y + (Math.random() - 0.5) * 14,
1721            vx: (Math.random() - 0.5) * 1.2,
1722            vy: -0.2 - Math.random() * 0.4,
1723            life: 1,
1724            maxLife: 3 + Math.random() * 2,
1725            size: 14 + Math.random() * 10,
1726            swimPhase: Math.random() * Math.PI * 2,
1727            direction: Math.random() > 0.5 ? 1 : -1,
1728            color: `hsl(${160 + Math.random() * 50}, ${55 + Math.random() * 25}%, ${45 + Math.random() * 20}%)`,
1729            type: 'seahorse'
1730        });
1731    }
1732
1733    function spawnStarfish(x, y) {
1734        if (Math.random() > 0.5) return;
1735        particles.push({
1736            x: x + (Math.random() - 0.5) * 16,
1737            y: y + (Math.random() - 0.5) * 16,
1738            vx: (Math.random() - 0.5) * 0.6,
1739            vy: 0.15 + Math.random() * 0.35,
1740            life: 1,
1741            maxLife: 3.5 + Math.random() * 2,
1742            size: 12 + Math.random() * 14,
1743            rotation: Math.random() * Math.PI * 2,
1744            rotationSpeed: (Math.random() - 0.5) * 0.06,
1745            arms: 5,
1746            color: `hsl(${10 + Math.random() * 30}, ${70 + Math.random() * 20}%, ${50 + Math.random() * 15}%)`,
1747            type: 'starfish'
1748        });
1749    }
1750
1751    function spawnCoral(x, y) {
1752        if (Math.random() > 0.5) return;
1753        const branches = 3 + Math.floor(Math.random() * 4);
1754        const branchData = [];
1755        for (let i = 0; i < branches; i++) {
1756            branchData.push({
1757                angle: (i / branches) * Math.PI - Math.PI / 2 + (Math.random() - 0.5) * 0.4,
1758                len: 0.6 + Math.random() * 0.5
1759            });
1760        }
1761        particles.push({
1762            x: x + (Math.random() - 0.5) * 14,
1763            y: y + (Math.random() - 0.5) * 14,
1764            vx: (Math.random() - 0.5) * 0.5,
1765            vy: -0.1 - Math.random() * 0.3,
1766            life: 1,
1767            maxLife: 3 + Math.random() * 2,
1768            size: 10 + Math.random() * 12,
1769            rotation: Math.random() * Math.PI * 2,
1770            rotationSpeed: (Math.random() - 0.5) * 0.08,
1771            branches: branches,
1772            branchData: branchData,
1773            color: `hsl(${330 + Math.random() * 40}, ${60 + Math.random() * 25}%, ${48 + Math.random() * 18}%)`,
1774            type: 'coral'
1775        });
1776    }
1777
1778    function spawnMushroom(x, y) {
1779        if (Math.random() > 0.55) return;
1780        const capHue = Math.random() > 0.5 ? 0 + Math.random() * 20 : 270 + Math.random() * 40;
1781        particles.push({
1782            x: x + (Math.random() - 0.5) * 10,
1783            y: y + (Math.random() - 0.5) * 10,
1784            vx: (Math.random() - 0.5) * 0.8,
1785            vy: 0.2 + Math.random() * 0.6,
1786            life: 1,
1787            maxLife: 2.5 + Math.random() * 1.5,
1788            size: 6 + Math.random() * 8,
1789            grow: 0.4,
1790            spots: Math.random() > 0.4,
1791            capColor: `hsl(${capHue}, ${65 + Math.random() * 25}%, ${48 + Math.random() * 15}%)`,
1792            stemColor: '#f5f0e6',
1793            type: 'mushroom'
1794        });
1795    }
1796
1797    function spawnFeather(x, y) {
1798        if (Math.random() > 0.5) return;
1799        particles.push({
1800            x: x + (Math.random() - 0.5) * 18,
1801            y: y + (Math.random() - 0.5) * 18,
1802            vx: (Math.random() - 0.5) * 1.5,
1803            vy: -0.3 - Math.random() * 0.8,
1804            life: 1,
1805            maxLife: 3 + Math.random() * 2,
1806            size: 14 + Math.random() * 12,
1807            rotation: Math.random() * Math.PI * 2,
1808            rotationSpeed: (Math.random() - 0.5) * 0.12,
1809            flutter: Math.random() * Math.PI * 2,
1810            color: `hsl(${0}, 0%, ${78 + Math.random() * 18}%)`,
1811            type: 'feather'
1812        });
1813    }
1814
1815    function spawnFruit(x, y) {
1816        if (Math.random() > 0.5) return;
1817        const fruit = FRUIT_COLORS[Math.floor(Math.random() * FRUIT_COLORS.length)];
1818        particles.push({
1819            x: x + (Math.random() - 0.5) * 10,
1820            y: y + (Math.random() - 0.5) * 10,
1821            vx: (Math.random() - 0.5) * 2.5,
1822            vy: (Math.random() - 0.5) * 2,
1823            life: 1,
1824            maxLife: 3 + Math.random() * 2,
1825            size: 8 + Math.random() * 7,
1826            rotation: Math.random() * Math.PI * 2,
1827            rotationSpeed: (Math.random() - 0.5) * 0.15,
1828            bounce: 0.65,
1829            gravity: 0.3,
1830            fruitKind: fruit.kind,
1831            color: fruit.color,
1832            stemColor: fruit.stem,
1833            type: 'fruit'
1834        });
1835    }
1836
1837    function spawnPortal(x, y) {
1838        if (Math.random() > 0.6) return;
1839        particles.push({
1840            x: x + (Math.random() - 0.5) * 8,
1841            y: y + (Math.random() - 0.5) * 8,
1842            vx: (Math.random() - 0.5) * 0.3,
1843            vy: (Math.random() - 0.5) * 0.3,
1844            life: 1,
1845            maxLife: 1.8 + Math.random() * 1,
1846            size: 12 + Math.random() * 16,
1847            rotation: Math.random() * Math.PI * 2,
1848            rotationSpeed: 0.12 + Math.random() * 0.15,
1849            pulse: Math.random() * Math.PI * 2,
1850            innerHue: 260 + Math.random() * 60,
1851            type: 'portal'
1852        });
1853    }
1854
1855    function spawnManaOrb(x, y) {
1856        if (Math.random() > 0.55) return;
1857        const orbitRadius = 20 + Math.random() * 25;
1858        const angle = Math.random() * Math.PI * 2;
1859        particles.push({
1860            x: x + Math.cos(angle) * orbitRadius,
1861            y: y + Math.sin(angle) * orbitRadius,
1862            centerX: x,
1863            centerY: y,
1864            orbitRadius: orbitRadius,
1865            orbitAngle: angle,
1866            orbitSpeed: 0.08 + Math.random() * 0.1,
1867            vx: 0,
1868            vy: 0,
1869            life: 1,
1870            maxLife: 2.5 + Math.random() * 1.5,
1871            size: 5 + Math.random() * 6,
1872            pulse: Math.random() * Math.PI * 2,
1873            color: `hsl(${200 + Math.random() * 80}, 85%, 65%)`,
1874            type: 'manaOrb'
1875        });
1876    }
1877
1878    function spawnVinylRecord(x, y) {
1879        if (Math.random() > 0.5) return;
1880        particles.push({
1881            x: x + (Math.random() - 0.5) * 12,
1882            y: y + (Math.random() - 0.5) * 12,
1883            vx: (Math.random() - 0.5) * 1.5,
1884            vy: 0.3 + Math.random() * 0.8,
1885            life: 1,
1886            maxLife: 3.5 + Math.random() * 2,
1887            size: 14 + Math.random() * 12,
1888            rotation: Math.random() * Math.PI * 2,
1889            rotationSpeed: 0.15 + Math.random() * 0.2,
1890            labelHue: Math.random() * 360,
1891            type: 'vinylRecord'
1892        });
1893    }
1894
1895    function spawnCoffeeBean(x, y) {
1896        if (Math.random() > 0.45) return;
1897        particles.push({
1898            x: x + (Math.random() - 0.5) * 14,
1899            y: y + (Math.random() - 0.5) * 14,
1900            vx: (Math.random() - 0.5) * 3,
1901            vy: (Math.random() - 0.5) * 2,
1902            life: 1,
1903            maxLife: 2.5 + Math.random() * 1.5,
1904            size: 5 + Math.random() * 5,
1905            rotation: Math.random() * Math.PI * 2,
1906            rotationSpeed: (Math.random() - 0.5) * 0.2,
1907            bounce: 0.5,
1908            gravity: 0.35,
1909            color: '#3e2723',
1910            type: 'coffeeBean'
1911        });
1912    }
1913
1914    function spawnSprinkle(x, y) {
1915        if (Math.random() > 0.35) return;
1916        particles.push({
1917            x: x + (Math.random() - 0.5) * 16,
1918            y: y + (Math.random() - 0.5) * 16,
1919            vx: (Math.random() - 0.5) * 2.5,
1920            vy: (Math.random() - 0.5) * 2,
1921            life: 1,
1922            maxLife: 2 + Math.random() * 1.5,
1923            size: 3 + Math.random() * 4,
1924            length: 6 + Math.random() * 8,
1925            rotation: Math.random() * Math.PI * 2,
1926            rotationSpeed: (Math.random() - 0.5) * 0.25,
1927            bounce: 0.55,
1928            gravity: 0.35,
1929            color: `hsl(${Math.random() * 360}, 90%, 60%)`,
1930            type: 'sprinkle'
1931        });
1932    }
1933
1934    function spawnDice(x, y) {
1935        if (Math.random() > 0.5) return;
1936        const face = Math.floor(Math.random() * 6) + 1;
1937        particles.push({
1938            x: x + (Math.random() - 0.5) * 12,
1939            y: y + (Math.random() - 0.5) * 12,
1940            vx: (Math.random() - 0.5) * 3,
1941            vy: (Math.random() - 0.5) * 2,
1942            life: 1,
1943            maxLife: 3 + Math.random() * 2,
1944            size: 10 + Math.random() * 8,
1945            rotation: Math.random() * Math.PI * 2,
1946            rotationSpeed: (Math.random() - 0.5) * 0.25,
1947            bounce: 0.6,
1948            gravity: 0.32,
1949            face: face,
1950            color: '#fffef8',
1951            pipColor: '#c62828',
1952            type: 'dice'
1953        });
1954    }
1955
1956    function spawnChatBubble(x, y) {
1957        if (Math.random() > 0.45) return;
1958        const text = CHAT_BUBBLE_TEXTS[Math.floor(Math.random() * CHAT_BUBBLE_TEXTS.length)];
1959        particles.push({
1960            x: x + (Math.random() - 0.5) * 20,
1961            y: y + (Math.random() - 0.5) * 20,
1962            vx: (Math.random() - 0.5) * 0.8,
1963            vy: -0.6 - Math.random() * 0.8,
1964            life: 1,
1965            maxLife: 2.5 + Math.random() * 1.5,
1966            size: 10 + text.length * 2.5,
1967            text: text,
1968            wobble: Math.random() * Math.PI * 2,
1969            bubbleHue: Math.random() > 0.5 ? 280 + Math.random() * 40 : 190 + Math.random() * 40,
1970            type: 'chatBubble'
1971        });
1972    }
1973
1974    function hexToRgba(hex, alpha) {
1975        const r = parseInt(hex.slice(1, 3), 16);
1976        const g = parseInt(hex.slice(3, 5), 16);
1977        const b = parseInt(hex.slice(5, 7), 16);
1978        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
1979    }
1980
1981    function spawnSpaghetti(x, y) {
1982        const last = spaghettiPath[spaghettiPath.length - 1];
1983        if (last) {
1984            const dx = x - last.x;
1985            const dy = y - last.y;
1986            if (dx * dx + dy * dy < 20) return;
1987        }
1988
1989        spaghettiPath.push({
1990            x: x,
1991            y: y,
1992            life: 1,
1993            maxLife: 2.8 + Math.random() * 1.8,
1994            thickness: 3.5 + Math.random() * 3.5,
1995            wobble: Math.random() * Math.PI * 2,
1996            wobbleSpeed: 0.06 + Math.random() * 0.08,
1997            color: SPAGHETTI_COLORS[Math.floor(Math.random() * SPAGHETTI_COLORS.length)]
1998        });
1999
2000        if (spaghettiPath.length > SPAGHETTI_MAX_POINTS) {
2001            spaghettiPath.shift();
2002        }
2003
2004        if (Math.random() > 0.78) {
2005            particles.push({
2006                x: x + (Math.random() - 0.5) * 8,
2007                y: y + (Math.random() - 0.5) * 8,
2008                vx: (Math.random() - 0.5) * 0.6,
2009                vy: 0.15 + Math.random() * 0.5,
2010                life: 1,
2011                maxLife: 1.5 + Math.random() * 1.2,
2012                size: 3 + Math.random() * 5,
2013                type: 'spaghettiSauce'
2014            });
2015        }
2016    }
2017
2018    function updateAndDrawSpaghetti(deltaTime) {
2019        for (let i = spaghettiPath.length - 1; i >= 0; i--) {
2020            const pt = spaghettiPath[i];
2021            pt.life -= (deltaTime * 0.016) / pt.maxLife;
2022            pt.wobble += pt.wobbleSpeed * deltaTime;
2023            if (pt.life <= 0) {
2024                spaghettiPath.splice(i, 1);
2025            }
2026        }
2027
2028        if (spaghettiPath.length < 2) return;
2029
2030        ctx.save();
2031        ctx.lineCap = 'round';
2032        ctx.lineJoin = 'round';
2033
2034        for (let i = 1; i < spaghettiPath.length; i++) {
2035            const a = spaghettiPath[i - 1];
2036            const b = spaghettiPath[i];
2037            const alpha = Math.min(a.life, b.life);
2038            if (alpha <= 0) continue;
2039
2040            const mx = (a.x + b.x) / 2;
2041            const my = (a.y + b.y) / 2;
2042            const dx = b.x - a.x;
2043            const dy = b.y - a.y;
2044            const len = Math.sqrt(dx * dx + dy * dy) || 1;
2045            const nx = -dy / len;
2046            const ny = dx / len;
2047            const wobbleOff = Math.sin(b.wobble) * (3 + b.thickness * 0.35);
2048            const thickness = ((a.thickness + b.thickness) / 2) * alpha;
2049
2050            ctx.strokeStyle = hexToRgba(a.color, alpha * 0.95);
2051            ctx.lineWidth = thickness;
2052            ctx.beginPath();
2053            ctx.moveTo(a.x, a.y);
2054            ctx.quadraticCurveTo(mx + nx * wobbleOff, my + ny * wobbleOff, b.x, b.y);
2055            ctx.stroke();
2056
2057            ctx.strokeStyle = hexToRgba('#fff8e7', alpha * 0.28);
2058            ctx.lineWidth = Math.max(1, thickness * 0.3);
2059            ctx.beginPath();
2060            ctx.moveTo(a.x, a.y);
2061            ctx.quadraticCurveTo(mx + nx * wobbleOff * 0.6, my + ny * wobbleOff * 0.6, b.x, b.y);
2062            ctx.stroke();
2063        }
2064
2065        ctx.restore();
2066    }
2067
2068    // ====================================
2069    // UPDATE & RENDER
2070    // ====================================
2071
2072    let lastFrameTime = Date.now();
2073
2074    function update() {
2075        if (!ctx || currentTrailType === 'none') {
2076            particles = [];
2077            spaghettiPath = [];
2078            return;
2079        }
2080
2081        // Calculate deltaTime
2082        const now = Date.now();
2083        const deltaTime = Math.min((now - lastFrameTime) / 16.67, 2); // Cap at 2x to prevent huge jumps
2084        lastFrameTime = now;
2085
2086        // Clear canvas
2087        ctx.clearRect(0, 0, canvas.width, canvas.height);
2088
2089        if (currentTrailType === 'spaghetti') {
2090            updateAndDrawSpaghetti(deltaTime);
2091        }
2092
2093        if (window.PupwifeCursorTrailHandlers) {
2094            window.PupwifeCursorTrailHandlers.updateFrame(currentTrailType, {
2095                ctx: ctx,
2096                canvas: canvas,
2097                deltaTime: deltaTime,
2098                lastMouseX: lastMouseX,
2099                lastMouseY: lastMouseY
2100            });
2101        }
2102
2103        if (isMatterTrail(currentTrailType) && window.PupwifeCursorTrailsMatter) {
2104            window.PupwifeCursorTrailsMatter.stepAndDraw(ctx, canvas, deltaTime);
2105            animationFrameId = requestAnimationFrame(update);
2106            return;
2107        }
2108
2109        // Update and draw particles
2110        for (let i = particles.length - 1; i >= 0; i--) {
2111            const p = particles[i];
2112
2113            // Special update for angle-based particles (spiral, vortex, tornado)
2114            if (p.type === 'spiral' || p.type === 'vortex' || p.type === 'tornado') {
2115                p.angle += p.angleSpeed * deltaTime;
2116                p.radius += p.radiusSpeed * deltaTime;
2117                
2118                // Update position based on angle and radius
2119                const centerX = p.centerX || 0;
2120                const centerY = p.centerY || 0;
2121                p.x = centerX + Math.cos(p.angle) * p.radius;
2122                p.y = centerY + Math.sin(p.angle) * p.radius;
2123                
2124                // Also apply vertical movement for tornado
2125                if (p.type === 'tornado' && p.vy) {
2126                    p.centerY += p.vy * deltaTime;
2127                }
2128            } else {
2129                // Normal position update
2130                p.x += p.vx * deltaTime;
2131                p.y += p.vy * deltaTime;
2132            }
2133
2134            // Update life
2135            p.life -= (deltaTime * 0.016) / p.maxLife;
2136
2137            // Apply gravity for some types
2138            if (p.type === 'bubbles' || p.type === 'snow' || p.type === 'leaves' || p.type === 'acorn' || p.type === 'pinecone' ||
2139                p.type === 'starfish' || p.type === 'vinylRecord') {
2140                const gravity = p.gravity !== undefined ? p.gravity : 0.05;
2141                p.vy += gravity * deltaTime;
2142            }
2143
2144            // Physics bounce objects
2145            if (p.type === 'bouncyBall' || p.type === 'physicsBox' || p.type === 'chainLink' ||
2146                p.type === 'fruit' || p.type === 'coffeeBean' || p.type === 'sprinkle' || p.type === 'dice' ||
2147                (p.type === 'snowball' && !p.splatted) ||
2148                (p.type === 'domino' && p.fallen)) {
2149                p.vy += p.gravity * deltaTime;
2150
2151                const halfW = p.type === 'domino' ? p.width : p.size;
2152                const halfH = p.type === 'domino' ? p.height : p.size;
2153
2154                if (p.x <= halfW || p.x >= canvas.width - halfW) {
2155                    p.vx *= -p.bounce;
2156                    p.x = Math.max(halfW, Math.min(canvas.width - halfW, p.x));
2157                }
2158                if (p.y <= halfH || p.y >= canvas.height - halfH) {
2159                    p.vy *= -p.bounce;
2160                    p.y = Math.max(halfH, Math.min(canvas.height - halfH, p.y));
2161                    if (p.y >= canvas.height - halfH) {
2162                        p.vx *= 0.92;
2163                        if (p.type === 'domino' && p.fallen) {
2164                            p.rotationSpeed *= 0.9;
2165                        }
2166                    }
2167                }
2168            }
2169
2170            // Chain link parent constraint
2171            if (p.type === 'chainLink' && p.parent && p.parent.life > 0) {
2172                const dx = p.x - p.parent.x;
2173                const dy = p.y - p.parent.y;
2174                const dist = Math.sqrt(dx * dx + dy * dy) || 0.001;
2175                if (dist > p.linkLength) {
2176                    const pull = (dist - p.linkLength) * 0.08;
2177                    p.vx -= (dx / dist) * pull;
2178                    p.vy -= (dy / dist) * pull;
2179                    p.parent.vx += (dx / dist) * pull * 0.5;
2180                    p.parent.vy += (dy / dist) * pull * 0.5;
2181                }
2182                p.rotation = Math.atan2(dy, dx) + Math.PI / 2;
2183            }
2184
2185            // Domino tipping
2186            if (p.type === 'domino' && !p.fallen) {
2187                const speed = Math.sqrt(mouseVelX * mouseVelX + mouseVelY * mouseVelY);
2188                const dx = p.x - lastMouseX;
2189                const dy = p.y - lastMouseY;
2190                const distToCursor = Math.sqrt(dx * dx + dy * dy);
2191                if (speed > 8 && distToCursor < 40) {
2192                    p.fallen = true;
2193                    p.rotationSpeed = (mouseVelX > 0 ? 1 : -1) * (0.12 + speed * 0.008);
2194                    p.vx = mouseVelX * 0.05;
2195                } else {
2196                    for (let j = 0; j < particles.length; j++) {
2197                        const other = particles[j];
2198                        if (other === p || other.type !== 'domino' || !other.fallen) continue;
2199                        const odx = p.x - other.x;
2200                        const ody = p.y - other.y;
2201                        if (Math.sqrt(odx * odx + ody * ody) < 22) {
2202                            p.fallen = true;
2203                            p.rotationSpeed = other.rotationSpeed * 0.85;
2204                            p.vx = other.vx * 0.5;
2205                            break;
2206                        }
2207                    }
2208                }
2209            }
2210
2211            if (p.type === 'domino' && p.fallen) {
2212                p.rotation += p.rotationSpeed * deltaTime;
2213                p.vx *= 0.995;
2214            }
2215
2216            // Seahorse swim curve
2217            if (p.type === 'seahorse') {
2218                p.swimPhase += 0.12 * deltaTime;
2219                p.vx += Math.sin(p.swimPhase) * 0.08 * p.direction * deltaTime;
2220                p.vy += Math.cos(p.swimPhase * 0.7) * 0.04 * deltaTime;
2221            }
2222
2223            // Feather flutter
2224            if (p.type === 'feather') {
2225                p.flutter += 0.18 * deltaTime;
2226                p.vx += Math.sin(p.flutter) * 0.12 * deltaTime;
2227                p.vy += Math.cos(p.flutter * 0.6) * 0.05 * deltaTime;
2228                p.rotationSpeed += Math.sin(p.flutter) * 0.002;
2229            }
2230
2231            // Mushroom grow
2232            if (p.type === 'mushroom') {
2233                p.grow = Math.min(1, p.grow + 0.02 * deltaTime);
2234                p.vy += 0.04 * deltaTime;
2235            }
2236
2237            // Mana orb orbit
2238            if (p.type === 'manaOrb') {
2239                p.orbitAngle += p.orbitSpeed * deltaTime;
2240                p.centerX += mouseVelX * 0.02;
2241                p.centerY += mouseVelY * 0.02;
2242                p.x = p.centerX + Math.cos(p.orbitAngle) * p.orbitRadius * p.life;
2243                p.y = p.centerY + Math.sin(p.orbitAngle) * p.orbitRadius * p.life;
2244                p.pulse += 0.1 * deltaTime;
2245            }
2246
2247            // Portal pulse
2248            if (p.type === 'portal') {
2249                p.pulse += 0.14 * deltaTime;
2250            }
2251
2252            // Chat bubble float
2253            if (p.type === 'chatBubble') {
2254                p.wobble += 0.1 * deltaTime;
2255                p.vx += Math.sin(p.wobble) * 0.06 * deltaTime;
2256            }
2257
2258            // Spaghetti sauce droplets
2259            if (p.type === 'spaghettiSauce') {
2260                p.vy += 0.06 * deltaTime;
2261            }
2262
2263            if (window.PupwifeCursorTrailHandlers) {
2264                window.PupwifeCursorTrailHandlers.updateParticle(p, deltaTime, {
2265                    canvas: canvas,
2266                    mouseVelX: mouseVelX,
2267                    mouseVelY: mouseVelY,
2268                    lastMouseX: lastMouseX,
2269                    lastMouseY: lastMouseY
2270                });
2271            }
2272
2273            // Physics bounce — snowball
2274            if (p.type === 'snowball' && !p.splatted) {
2275                p.vx *= 0.99;
2276            }
2277
2278            // Coral drift
2279            if (p.type === 'coral') {
2280                p.vy += Math.sin(Date.now() * 0.002 + p.rotation) * 0.02 * deltaTime;
2281            }
2282            
2283            // Handle raindrop splash
2284            if (p.type === 'raindrop' && p.splashY === null && p.y >= canvas.height - 20) {
2285                p.splashY = p.y;
2286                p.vx = 0;
2287                p.vy = 0;
2288            }
2289
2290            // Apply rotation
2291            if (p.rotation !== undefined) {
2292                p.rotation += p.rotationSpeed * deltaTime;
2293            }
2294
2295            // Remove dead particles
2296            if (p.life <= 0) {
2297                particles.splice(i, 1);
2298                continue;
2299            }
2300
2301            // Draw particle
2302            drawParticle(p);
2303        }
2304
2305        animationFrameId = requestAnimationFrame(update);
2306    }
2307
2308    const HANDLER_DRAW_TYPES = {
2309        tardigrade: true,
2310        seashell: true,
2311        seaweed: true,
2312        pearlBubble: true,
2313        bioluminescence: true,
2314        snowball: true,
2315        valentineCandy: true,
2316        tetromino: true,
2317        pokerChip: true,
2318        xpOrb: true,
2319        pixelHeart: true,
2320        paperClip: true,
2321        stickyNote: true,
2322        popcorn: true,
2323        gummyWoim: true,
2324        shootingStar: true,
2325        snail: true,
2326        moth: true,
2327        bat: true,
2328        frog: true,
2329        woim: true,
2330        equalizerBar: true,
2331        glitterSparkle: true,
2332        moonDust: true
2333    };
2334
2335    function drawParticle(p) {
2336        ctx.save();
2337        ctx.globalAlpha = p.life;
2338
2339        ctx.translate(p.x, p.y);
2340        if (p.rotation !== undefined) {
2341            ctx.rotate(p.rotation);
2342        }
2343
2344        switch (p.type) {
2345            case 'sparkle': drawSparkle(p); break;
2346            case 'fairy': drawFairy(p); break;
2347            case 'rainbow':
2348            case 'fire': drawCircle(p); break;
2349            case 'bubble': drawBubble(p); break;
2350            case 'heart': drawHeart(p); break;
2351            case 'star': drawStar(p); break;
2352            case 'snow': drawSnowflake(p); break;
2353            case 'leaf': drawLeaf(p); break;
2354            // NEW DRAW CASES
2355            case 'lightning': drawLightning(p); break;
2356            case 'cherryBlossom': drawCherryBlossom(p); break;
2357            case 'firefly': drawFirefly(p); break;
2358            case 'butterfly': drawButterfly(p); break;
2359            case 'comet': drawComet(p); break;
2360            case 'magicRune': drawMagicRune(p); break;
2361            case 'crystal': drawCrystal(p); break;
2362            case 'energyOrb': drawEnergyOrb(p); break;
2363            case 'matrix': drawMatrix(p); break;
2364            case 'neonLine': drawNeonLine(p); break;
2365            case 'glitch': drawGlitch(p); break;
2366            case 'musicNote': drawMusicNote(p); break;
2367            case 'confetti': drawConfetti(p); break;
2368            case 'paintSplatter': drawPaintSplatter(p); break;
2369            case 'smoke': drawSmoke(p); break;
2370            case 'ripple': drawRipple(p); break;
2371            case 'dna': drawDNA(p); break;
2372            case 'circuit': drawCircuit(p); break;
2373            case 'binary': drawBinary(p); break;
2374            case 'pawPrint': drawPawPrint(p); break;
2375            case 'coloredHeart': drawColoredHeart(p); break;
2376            case 'dandelion': drawDandelion(p); break;
2377            case 'rosePetal': drawRosePetal(p); break;
2378            case 'pixelated': drawPixel(p); break;
2379            case 'galaxy': drawGalaxy(p); break;
2380            case 'auroraWave': drawAuroraWave(p); break;
2381            case 'geometric': drawGeometric(p); break;
2382            case 'spiral': drawSpiral(p); break;
2383            case 'inkBlot': drawInkBlot(p); break;
2384            case 'paintStroke': drawPaintStroke(p); break;
2385            case 'laser': drawLaser(p); break;
2386            case 'hologram': drawHologram(p); break;
2387            case 'cloud': drawCloud(p); break;
2388            case 'tornado': drawTornado(p); break;
2389            case 'emoji': drawEmoji(p); break;
2390            case 'candy': drawCandy(p); break;
2391            case 'electricSpark': drawElectricSpark(p); break;
2392            case 'plasma': drawPlasma(p); break;
2393            case 'vortex': drawVortex(p); break;
2394            case 'cosmicDust': drawCosmicDust(p); break;
2395            // NEW PHYSICS & OCEAN TRAILS
2396            case 'bouncyBall': drawBouncyBall(p); break;
2397            case 'physicsBox': drawPhysicsBox(p); break;
2398            case 'jellyfish': drawJellyfish(p); break;
2399            case 'fish': drawFish(p); break;
2400            case 'acorn': drawAcorn(p); break;
2401            case 'pinecone': drawPinecone(p); break;
2402            case 'raindrop': drawRaindrop(p); break;
2403            case 'mist': drawMist(p); break;
2404            case 'fog': drawFog(p); break;
2405            case 'chalk': drawChalk(p); break;
2406            case 'pastel': drawPastel(p); break;
2407            case 'dataPacket': drawDataPacket(p); break;
2408            case 'soundWave': drawSoundWave(p); break;
2409            case 'trafficLight': drawTrafficLight(p); break;
2410            case 'bee': drawBee(p); break;
2411            case 'lensFlare': drawLensFlare(p); break;
2412            case 'prism': drawPrism(p); break;
2413            case 'chainLink': drawChainLink(p); break;
2414            case 'domino': drawDomino(p); break;
2415            case 'seahorse': drawSeahorse(p); break;
2416            case 'starfish': drawStarfish(p); break;
2417            case 'coral': drawCoral(p); break;
2418            case 'mushroom': drawMushroom(p); break;
2419            case 'feather': drawFeather(p); break;
2420            case 'fruit': drawFruit(p); break;
2421            case 'portal': drawPortal(p); break;
2422            case 'manaOrb': drawManaOrb(p); break;
2423            case 'vinylRecord': drawVinylRecord(p); break;
2424            case 'coffeeBean': drawCoffeeBean(p); break;
2425            case 'sprinkle': drawSprinkle(p); break;
2426            case 'dice': drawDice(p); break;
2427            case 'chatBubble': drawChatBubble(p); break;
2428            case 'spaghettiSauce': drawSpaghettiSauce(p); break;
2429        }
2430
2431        if (window.PupwifeCursorTrailHandlers &&
2432            window.PupwifeCursorTrailHandlers.draw &&
2433            HANDLER_DRAW_TYPES[p.type]) {
2434            window.PupwifeCursorTrailHandlers.draw(ctx, p);
2435        }
2436
2437        ctx.restore();
2438    }
2439
2440    function drawSparkle(p) {
2441        ctx.fillStyle = p.color;
2442        ctx.fillRect(-p.size/2, -1, p.size, 2);
2443        ctx.fillRect(-1, -p.size/2, 2, p.size);
2444    }
2445
2446    function drawFairy(p) {
2447        ctx.shadowBlur = p.glow;
2448        ctx.shadowColor = p.color;
2449        ctx.fillStyle = p.color;
2450        ctx.beginPath();
2451        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2452        ctx.fill();
2453    }
2454
2455    function drawCircle(p) {
2456        ctx.fillStyle = p.color;
2457        ctx.beginPath();
2458        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2459        ctx.fill();
2460    }
2461
2462    function drawBubble(p) {
2463        // Outer bubble
2464        ctx.strokeStyle = `rgba(200, 230, 255, ${p.life * 0.6})`;
2465        ctx.lineWidth = 2;
2466        ctx.beginPath();
2467        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2468        ctx.stroke();
2469
2470        // Inner highlight
2471        ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.3})`;
2472        ctx.beginPath();
2473        ctx.arc(-p.size * 0.3, -p.size * 0.3, p.size * 0.3, 0, Math.PI * 2);
2474        ctx.fill();
2475    }
2476
2477    function drawHeart(p) {
2478        ctx.fillStyle = `rgba(255, 105, 180, ${p.life})`;
2479        
2480        const size = p.size;
2481        ctx.beginPath();
2482        ctx.moveTo(0, size * 0.3);
2483        ctx.bezierCurveTo(-size * 0.5, -size * 0.3, -size, size * 0.1, 0, size * 0.8);
2484        ctx.bezierCurveTo(size, size * 0.1, size * 0.5, -size * 0.3, 0, size * 0.3);
2485        ctx.fill();
2486    }
2487
2488    function drawStar(p) {
2489        ctx.fillStyle = `rgba(255, 223, 0, ${p.life})`;
2490        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.8})`;
2491        ctx.lineWidth = 2;
2492
2493        const size = p.size;
2494        const points = p.points;
2495        
2496        ctx.beginPath();
2497        for (let i = 0; i < points * 2; i++) {
2498            const angle = (i * Math.PI) / points;
2499            const radius = i % 2 === 0 ? size : size * 0.5;
2500            const x = Math.cos(angle) * radius;
2501            const y = Math.sin(angle) * radius;
2502            
2503            if (i === 0) ctx.moveTo(x, y);
2504            else ctx.lineTo(x, y);
2505        }
2506        ctx.closePath();
2507        ctx.fill();
2508        ctx.stroke();
2509    }
2510
2511    function drawSnowflake(p) {
2512        ctx.strokeStyle = `rgba(240, 248, 255, ${p.life})`;
2513        ctx.fillStyle = `rgba(255, 255, 255, ${p.life})`;
2514        ctx.lineWidth = 2;
2515
2516        const size = p.size;
2517        
2518        // Simple snowflake
2519        for (let i = 0; i < 6; i++) {
2520            ctx.save();
2521            ctx.rotate((i * Math.PI) / 3);
2522            ctx.beginPath();
2523            ctx.moveTo(0, 0);
2524            ctx.lineTo(0, -size);
2525            ctx.stroke();
2526            ctx.restore();
2527        }
2528
2529        // Center dot
2530        ctx.beginPath();
2531        ctx.arc(0, 0, size * 0.15, 0, Math.PI * 2);
2532        ctx.fill();
2533    }
2534
2535    function drawLeaf(p) {
2536        ctx.fillStyle = p.color;
2537        
2538        const size = p.size;
2539        
2540        // Leaf shape
2541        ctx.beginPath();
2542        ctx.moveTo(0, -size * 0.5);
2543        ctx.quadraticCurveTo(size * 0.5, -size * 0.2, size * 0.3, size * 0.5);
2544        ctx.quadraticCurveTo(0, size * 0.3, 0, -size * 0.5);
2545        ctx.quadraticCurveTo(-size * 0.5, -size * 0.2, -size * 0.3, size * 0.5);
2546        ctx.quadraticCurveTo(0, size * 0.3, 0, -size * 0.5);
2547        ctx.fill();
2548
2549        // Vein
2550        ctx.strokeStyle = 'rgba(0, 0, 0, 0.2)';
2551        ctx.lineWidth = 1;
2552        ctx.beginPath();
2553        ctx.moveTo(0, -size * 0.5);
2554        ctx.lineTo(0, size * 0.5);
2555        ctx.stroke();
2556    }
2557
2558    // ====================================
2559    // NEW DRAW FUNCTIONS (40 MORE!)
2560    // ====================================
2561
2562    function drawLightning(p) {
2563        ctx.strokeStyle = p.color;
2564        ctx.lineWidth = 3;
2565        ctx.shadowBlur = 15;
2566        ctx.shadowColor = p.color;
2567        
2568        ctx.beginPath();
2569        ctx.moveTo(0, 0);
2570        for (let seg of p.segments) {
2571            ctx.lineTo(seg.x, seg.y);
2572        }
2573        ctx.stroke();
2574    }
2575
2576    function drawCherryBlossom(p) {
2577        p.wobble += 0.05;
2578        const wobbleX = Math.sin(p.wobble) * 3;
2579        
2580        ctx.fillStyle = '#ffc0cb';
2581        for (let i = 0; i < 5; i++) {
2582            ctx.save();
2583            ctx.rotate((i * Math.PI * 2) / 5);
2584            ctx.translate(wobbleX, 0);
2585            ctx.beginPath();
2586            ctx.ellipse(p.size * 0.6, 0, p.size * 0.5, p.size * 0.3, 0, 0, Math.PI * 2);
2587            ctx.fill();
2588            ctx.restore();
2589        }
2590        
2591        // Center
2592        ctx.fillStyle = '#ffeb3b';
2593        ctx.beginPath();
2594        ctx.arc(0, 0, p.size * 0.2, 0, Math.PI * 2);
2595        ctx.fill();
2596    }
2597
2598    function drawFirefly(p) {
2599        p.pulse += 0.15;
2600        const brightness = 0.5 + Math.sin(p.pulse) * 0.5;
2601        
2602        ctx.shadowBlur = p.glow * brightness;
2603        ctx.shadowColor = p.color;
2604        ctx.fillStyle = p.color;
2605        ctx.globalAlpha = brightness;
2606        ctx.beginPath();
2607        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2608        ctx.fill();
2609        ctx.globalAlpha = 1;
2610    }
2611
2612    function drawButterfly(p) {
2613        p.wingFlap += 0.3;
2614        const flapAngle = Math.sin(p.wingFlap) * 0.3;
2615        
2616        // Left wing
2617        ctx.save();
2618        ctx.rotate(flapAngle);
2619        ctx.fillStyle = p.color;
2620        ctx.beginPath();
2621        ctx.ellipse(-p.size * 0.5, 0, p.size * 0.6, p.size * 0.4, 0, 0, Math.PI * 2);
2622        ctx.fill();
2623        ctx.restore();
2624        
2625        // Right wing
2626        ctx.save();
2627        ctx.rotate(-flapAngle);
2628        ctx.fillStyle = p.color;
2629        ctx.beginPath();
2630        ctx.ellipse(p.size * 0.5, 0, p.size * 0.6, p.size * 0.4, 0, 0, Math.PI * 2);
2631        ctx.fill();
2632        ctx.restore();
2633        
2634        // Body
2635        ctx.fillStyle = '#000';
2636        ctx.fillRect(-p.size * 0.1, -p.size * 0.4, p.size * 0.2, p.size * 0.8);
2637    }
2638
2639    function drawComet(p) {
2640        // Track tail
2641        p.tail.push({x: 0, y: 0});
2642        if (p.tail.length > 8) p.tail.shift();
2643        
2644        // Draw tail
2645        ctx.strokeStyle = p.color;
2646        ctx.lineWidth = 3;
2647        ctx.beginPath();
2648        for (let i = 0; i < p.tail.length; i++) {
2649            ctx.globalAlpha = (i / p.tail.length) * p.life;
2650            const point = p.tail[i];
2651            if (i === 0) ctx.moveTo(point.x, point.y);
2652            else ctx.lineTo(point.x, point.y);
2653        }
2654        ctx.stroke();
2655        ctx.globalAlpha = 1;
2656        
2657        // Draw head
2658        ctx.fillStyle = p.color;
2659        ctx.shadowBlur = 10;
2660        ctx.shadowColor = p.color;
2661        ctx.beginPath();
2662        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2663        ctx.fill();
2664    }
2665
2666    function drawMagicRune(p) {
2667        ctx.fillStyle = p.color;
2668        ctx.shadowBlur = 15;
2669        ctx.shadowColor = p.color;
2670        ctx.font = `${p.size}px serif`;
2671        ctx.textAlign = 'center';
2672        ctx.textBaseline = 'middle';
2673        ctx.fillText(p.rune, 0, 0);
2674    }
2675
2676    function drawCrystal(p) {
2677        ctx.fillStyle = p.color;
2678        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.8})`;
2679        ctx.lineWidth = 2;
2680        
2681        ctx.beginPath();
2682        for (let i = 0; i < p.sides; i++) {
2683            const angle = (i * Math.PI * 2) / p.sides;
2684            const x = Math.cos(angle) * p.size;
2685            const y = Math.sin(angle) * p.size;
2686            if (i === 0) ctx.moveTo(x, y);
2687            else ctx.lineTo(x, y);
2688        }
2689        ctx.closePath();
2690        ctx.fill();
2691        ctx.stroke();
2692    }
2693
2694    function drawEnergyOrb(p) {
2695        p.pulse += 0.2;
2696        const pulseSize = p.size + Math.sin(p.pulse) * 3;
2697        
2698        ctx.shadowBlur = p.glow;
2699        ctx.shadowColor = p.color;
2700        ctx.fillStyle = p.color;
2701        ctx.beginPath();
2702        ctx.arc(0, 0, pulseSize, 0, Math.PI * 2);
2703        ctx.fill();
2704    }
2705
2706    function drawMatrix(p) {
2707        ctx.fillStyle = `rgba(0, 255, 0, ${p.life})`;
2708        ctx.font = `${p.size}px monospace`;
2709        ctx.textAlign = 'center';
2710        ctx.textBaseline = 'middle';
2711        ctx.fillText(p.char, 0, 0);
2712    }
2713
2714    function drawNeonLine(p) {
2715        ctx.save();
2716        ctx.rotate(p.angle);
2717        ctx.strokeStyle = p.color;
2718        ctx.lineWidth = 3;
2719        ctx.shadowBlur = p.glow;
2720        ctx.shadowColor = p.color;
2721        ctx.beginPath();
2722        ctx.moveTo(0, 0);
2723        ctx.lineTo(p.length, 0);
2724        ctx.stroke();
2725        ctx.restore();
2726    }
2727
2728    function drawGlitch(p) {
2729        ctx.fillStyle = p.color;
2730        ctx.fillRect(-p.width/2, -p.height/2, p.width, p.height);
2731    }
2732
2733    function drawMusicNote(p) {
2734        ctx.fillStyle = p.color;
2735        ctx.font = `${p.size}px Arial`;
2736        ctx.textAlign = 'center';
2737        ctx.textBaseline = 'middle';
2738        ctx.fillText(p.note, 0, 0);
2739    }
2740
2741    function drawConfetti(p) {
2742        ctx.fillStyle = p.color;
2743        if (p.shape === 'circle') {
2744            ctx.beginPath();
2745            ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2746            ctx.fill();
2747        } else {
2748            ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);
2749        }
2750    }
2751
2752    function drawPaintSplatter(p) {
2753        ctx.fillStyle = p.color;
2754        
2755        // Main splat
2756        ctx.beginPath();
2757        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
2758        ctx.fill();
2759        
2760        // Splatter drips
2761        for (let splat of p.splats) {
2762            ctx.beginPath();
2763            ctx.arc(
2764                Math.cos(splat.angle) * splat.length,
2765                Math.sin(splat.angle) * splat.length,
2766                p.size * 0.3,
2767                0,
2768                Math.PI * 2
2769            );
2770            ctx.fill();
2771        }
2772    }
2773
2774    function drawSmoke(p) {
2775        p.expansion += 0.02;
2776        const expandedSize = p.size * p.expansion;
2777        
2778        ctx.fillStyle = `rgba(100, 100, 100, ${p.life * 0.3})`;
2779        ctx.beginPath();
2780        ctx.arc(0, 0, expandedSize, 0, Math.PI * 2);
2781        ctx.fill();
2782    }
2783
2784    function drawRipple(p) {
2785        const progress = 1 - p.life;
2786        const currentSize = p.size + (p.maxSize - p.size) * progress;
2787        
2788        ctx.strokeStyle = `rgba(100, 200, 255, ${p.life})`;
2789        ctx.lineWidth = 2;
2790        ctx.beginPath();
2791        ctx.arc(0, 0, currentSize, 0, Math.PI * 2);
2792        ctx.stroke();
2793    }
2794
2795    function drawDNA(p) {
2796        p.twist += 0.1;
2797        
2798        const height = p.size * 4;
2799        ctx.strokeStyle = '#0ff';
2800        ctx.lineWidth = 2;
2801        
2802        // Draw double helix
2803        ctx.beginPath();
2804        for (let i = 0; i < 20; i++) {
2805            const y = (i / 20 - 0.5) * height;
2806            const x = Math.sin(p.twist + i * 0.3) * p.size;
2807            if (i === 0) ctx.moveTo(x, y);
2808            else ctx.lineTo(x, y);
2809        }
2810        ctx.stroke();
2811        
2812        ctx.strokeStyle = '#f0f';
2813        ctx.beginPath();
2814        for (let i = 0; i < 20; i++) {
2815            const y = (i / 20 - 0.5) * height;
2816            const x = -Math.sin(p.twist + i * 0.3) * p.size;
2817            if (i === 0) ctx.moveTo(x, y);
2818            else ctx.lineTo(x, y);
2819        }
2820        ctx.stroke();
2821    }
2822
2823    function drawCircuit(p) {
2824        ctx.strokeStyle = '#0f0';
2825        ctx.fillStyle = '#0f0';
2826        ctx.lineWidth = 2;
2827        
2828        // Draw node
2829        ctx.beginPath();
2830        ctx.arc(0, 0, p.size * 0.3, 0, Math.PI * 2);
2831        ctx.fill();
2832        
2833        // Draw lines
2834        for (let line of p.lines) {
2835            ctx.beginPath();
2836            ctx.moveTo(0, 0);
2837            ctx.lineTo(
2838                Math.cos(line.angle) * line.length,
2839                Math.sin(line.angle) * line.length
2840            );
2841            ctx.stroke();
2842        }
2843    }
2844
2845    function drawBinary(p) {
2846        ctx.fillStyle = `rgba(0, 255, 0, ${p.life})`;
2847        ctx.font = `${p.size}px monospace`;
2848        ctx.textAlign = 'center';
2849        ctx.textBaseline = 'middle';
2850        ctx.fillText(p.digit, 0, 0);
2851    }
2852
2853    function drawPawPrint(p) {
2854        if (pawPrintImage && pawPrintImageLoaded && pawPrintImage.complete && pawPrintImage.naturalWi
2854dth > 0) {
2855            // Image is loaded, use it to create a colored paw print
2856            ctx.save();
2857            ctx.globalAlpha = p.life * 0.9;
2858            
2859            // Draw the image first to establish the alpha mask
2860            ctx.drawImage(pawPrintImage, -p.size/2, -p.size/2, p.size, p.size);
2861            
2862            // Use source-atop to fill only the drawn area with our color
2863            // This replaces the black paw print with our color while keeping the shape
2864            ctx.globalCompositeOperation = 'source-atop';
2865            ctx.fillStyle = p.color || '#ff69b4';
2866            ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);
2867            
2868            ctx.restore();
2869        } else {
2870            // Fallback: draw simple paw print if image hasn't loaded yet
2871            ctx.fillStyle = p.color || '#ff69b4';
2872            ctx.globalAlpha = p.life;
2873            
2874            // Main pad
2875            ctx.beginPath();
2876            ctx.ellipse(0, p.size * 0.2, p.size * 0.6, p.size * 0.5, 0, 0, Math.PI * 2);
2877            ctx.fill();
2878            
2879            // Toes
2880            for (let i = 0; i < 4; i++) {
2881                const angle = (i - 1.5) * 0.5;
2882                const x = Math.sin(angle) * p.size * 0.5;
2883                const y = -p.size * 0.4;
2884                ctx.beginPath();
2885                ctx.ellipse(x, y, p.size * 0.25, p.size * 0.35, 0, 0, Math.PI * 2);
2886                ctx.fill();
2887            }
2888            
2889            ctx.globalAlpha = 1;
2890        }
2891    }
2892
2893    function drawColoredHeart(p) {
2894        ctx.font = `${p.size}px Arial`;
2895        ctx.textAlign = 'center';
2896        ctx.textBaseline = 'middle';
2897        ctx.fillText(p.emoji, 0, 0);
2898    }
2899
2900    function drawDandelion(p) {
2901        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life})`;
2902        ctx.fillStyle = `rgba(255, 255, 255, ${p.life})`;
2903        ctx.lineWidth = 1;
2904        
2905        // Seeds radiating out
2906        for (let i = 0; i < 8; i++) {
2907            const angle = (i * Math.PI * 2) / 8;
2908            ctx.beginPath();
2909            ctx.moveTo(0, 0);
2910            ctx.lineTo(Math.cos(angle) * p.size, Math.sin(angle) * p.size);
2911            ctx.stroke();
2912            
2913            ctx.beginPath();
2914            ctx.arc(Math.cos(angle) * p.size, Math.sin(angle) * p.size, 1, 0, Math.PI * 2);
2915            ctx.fill();
2916        }
2917    }
2918
2919    function drawRosePetal(p) {
2920        p.wobble += 0.05;
2921        const wobbleX = Math.sin(p.wobble) * 2;
2922        
2923        ctx.fillStyle = p.color;
2924        ctx.translate(wobbleX, 0);
2925        ctx.beginPath();
2926        ctx.ellipse(0, 0, p.size * 0.7, p.size, 0, 0, Math.PI * 2);
2927        ctx.fill();
2928    }
2929
2930    function drawPixel(p) {
2931        ctx.fillStyle = p.color;
2932        ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);
2933    }
2934
2935    function drawGalaxy(p) {
2936        p.orbitAngle += p.orbitSpeed;
2937        const orbitX = Math.cos(p.orbitAngle) * 3;
2938        const orbitY = Math.sin(p.orbitAngle) * 3;
2939        
2940        ctx.shadowBlur = 10;
2941        ctx.shadowColor = p.color;
2942        ctx.fillStyle = p.color;
2943        ctx.beginPath();
2944        ctx.arc(orbitX, orbitY, p.size, 0, Math.PI * 2);
2945        ctx.fill();
2946    }
2947
2948    function drawAuroraWave(p) {
2949        p.wave += 0.1;
2950        
2951        ctx.fillStyle = p.color;
2952        ctx.globalAlpha = p.life * 0.4;
2953        ctx.beginPath();
2954        for (let i = 0; i < p.width; i += 5) {
2955            const x = i - p.width / 2;
2956            const y = Math.sin(p.wave + i * 0.1) * p.height * 0.5;
2957            if (i === 0) ctx.moveTo(x, y - p.height);
2958            else ctx.lineTo(x, y);
2959        }
2960        ctx.lineTo(p.width / 2, p.height);
2961        ctx.lineTo(-p.width / 2, p.height);
2962        ctx.closePath();
2963        ctx.fill();
2964        ctx.globalAlpha = 1;
2965    }
2966
2967    function drawGeometric(p) {
2968        // Draw wireframe geometric shapes with inner patterns
2969        ctx.strokeStyle = p.color;
2970        ctx.lineWidth = 3;
2971        ctx.shadowBlur = 8;
2972        ctx.shadowColor = p.color;
2973        
2974        // Outer shape
2975        ctx.beginPath();
2976        for (let i = 0; i < p.sides; i++) {
2977            const angle = (i * Math.PI * 2) / p.sides;
2978            const x = Math.cos(angle) * p.size;
2979            const y = Math.sin(angle) * p.size;
2980            if (i === 0) ctx.moveTo(x, y);
2981            else ctx.lineTo(x, y);
2982        }
2983        ctx.closePath();
2984        ctx.stroke();
2985        
2986        // Inner pattern (connecting to center)
2987        ctx.lineWidth = 1;
2988        ctx.globalAlpha = p.life * 0.5;
2989        for (let i = 0; i < p.sides; i++) {
2990            const angle = (i * Math.PI * 2) / p.sides;
2991            const x = Math.cos(angle) * p.size;
2992            const y = Math.sin(angle) * p.size;
2993            ctx.beginPath();
2994            ctx.moveTo(0, 0);
2995            ctx.lineTo(x, y);
2996            ctx.stroke();
2997        }
2998        ctx.globalAlpha = 1;
2999    }
3000
3001    function drawSpiral(p) {
3002        ctx.strokeStyle = p.color;
3003        ctx.lineWidth = 3;
3004        ctx.shadowBlur = 10;
3005        ctx.shadowColor = p.color;
3006        
3007        ctx.beginPath();
3008        ctx.arc(0, 0, 5, 0, Math.PI * 2);
3009        ctx.fill();
3010    }
3011
3012    function drawInkBlot(p) {
3013        p.expansion += 0.02;
3014        
3015        ctx.fillStyle = p.color;
3016        ctx.globalAlpha = p.life * 0.7;
3017        
3018        // Main blob
3019        ctx.beginPath();
3020        ctx.arc(0, 0, p.size * p.expansion, 0, Math.PI * 2);
3021        ctx.fill();
3022        
3023        // Extra blobs
3024        for (let blob of p.blobs) {
3025            ctx.beginPath();
3026            ctx.arc(blob.x, blob.y, blob.size * p.expansion, 0, Math.PI * 2);
3027            ctx.fill();
3028        }
3029        
3030        ctx.globalAlpha = 1;
3031    }
3032
3033    function drawPaintStroke(p) {
3034        ctx.save();
3035        ctx.rotate(p.angle);
3036        ctx.fillStyle = p.color;
3037        ctx.fillRect(0, -p.width/2, p.length, p.width);
3038        ctx.restore();
3039    }
3040
3041    function drawLaser(p) {
3042        ctx.save();
3043        ctx.rotate(p.angle);
3044        ctx.strokeStyle = p.color;
3045        ctx.lineWidth = 4;
3046        ctx.shadowBlur = p.glow;
3047        ctx.shadowColor = p.color;
3048        ctx.beginPath();
3049        ctx.moveTo(0, 0);
3050        ctx.lineTo(p.length, 0);
3051        ctx.stroke();
3052        ctx.restore();
3053    }
3054
3055    function drawHologram(p) {
3056        ctx.strokeStyle = p.color;
3057        ctx.fillStyle = p.color;
3058        ctx.globalAlpha = p.life * 0.6;
3059        
3060        // Main shape
3061        ctx.beginPath();
3062        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3063        ctx.stroke();
3064        
3065        // Scan lines
3066        for (let i = 0; i < p.scanLines; i++) {
3067            const y = -p.size + (i * p.size * 2) / p.scanLines;
3068            ctx.beginPath();
3069            ctx.moveTo(-p.size, y);
3070            ctx.lineTo(p.size, y);
3071            ctx.stroke();
3072        }
3073        
3074        ctx.globalAlpha = 1;
3075    }
3076
3077    function drawCloud(p) {
3078        ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.7})`;
3079        
3080        for (let puff of p.puffs) {
3081            ctx.beginPath();
3082            ctx.arc(puff.x, puff.y, puff.size, 0, Math.PI * 2);
3083            ctx.fill();
3084        }
3085    }
3086
3087    function drawTornado(p) {
3088        ctx.fillStyle = p.color;
3089        ctx.beginPath();
3090        ctx.arc(0, 0, 3, 0, Math.PI * 2);
3091        ctx.fill();
3092    }
3093
3094    function drawEmoji(p) {
3095        ctx.font = `${p.size}px Arial`;
3096        ctx.textAlign = 'center';
3097        ctx.textBaseline = 'middle';
3098        ctx.fillText(p.emoji, 0, 0);
3099    }
3100
3101    function drawCandy(p) {
3102        ctx.fillStyle = p.color;
3103        
3104        if (p.candyType === 0) {
3105            // Lollipop
3106            ctx.beginPath();
3107            ctx.arc(0, -p.size * 0.3, p.size * 0.6, 0, Math.PI * 2);
3108            ctx.fill();
3109            ctx.strokeStyle = p.color;
3110            ctx.lineWidth = 3;
3111            ctx.beginPath();
3112            ctx.moveTo(0, p.size * 0.3);
3113            ctx.lineTo(0, p.size);
3114            ctx.stroke();
3115        } else if (p.candyType === 1) {
3116            // Wrapped candy
3117            ctx.fillRect(-p.size * 0.6, -p.size * 0.3, p.size * 1.2, p.size * 0.6);
3118            ctx.strokeStyle = '#fff';
3119            ctx.lineWidth = 2;
3120            ctx.beginPath();
3121            ctx.moveTo(-p.size * 0.6, 0);
3122            ctx.lineTo(-p.size * 0.8, -p.size * 0.5);
3123            ctx.moveTo(p.size * 0.6, 0);
3124            ctx.lineTo(p.size * 0.8, -p.size * 0.5);
3125            ctx.stroke();
3126        } else {
3127            // Heart candy
3128            ctx.beginPath();
3129            ctx.moveTo(0, p.size * 0.2);
3130            ctx.bezierCurveTo(-p.size * 0.4, -p.size * 0.2, -p.size * 0.8, p.size * 0.1, 0, p.size * 0.7);
3131            ctx.bezierCurveTo(p.size * 0.8, p.size * 0.1, p.size * 0.4, -p.size * 0.2, 0, p.size * 0.2);
3132            ctx.fill();
3133        }
3134    }
3135
3136    function drawElectricSpark(p) {
3137        ctx.strokeStyle = p.color;
3138        ctx.lineWidth = 2;
3139        ctx.shadowBlur = p.glow;
3140        ctx.shadowColor = p.color;
3141        
3142        // Center
3143        ctx.fillStyle = p.color;
3144        ctx.beginPath();
3145        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3146        ctx.fill();
3147        
3148        // Branches
3149        for (let branch of p.branches) {
3150            ctx.beginPath();
3151            ctx.moveTo(0, 0);
3152            ctx.lineTo(
3153                Math.cos(branch.angle) * branch.length,
3154                Math.sin(branch.angle) * branch.length
3155            );
3156            ctx.stroke();
3157        }
3158    }
3159
3160    function drawPlasma(p) {
3161        p.pulse += 0.15;
3162        const pulseSize = p.size + Math.sin(p.pulse) * 4;
3163        
3164        ctx.shadowBlur = p.glow;
3165        ctx.shadowColor = p.color;
3166        
3167        // Multiple layers
3168        for (let i = 0; i < 3; i++) {
3169            ctx.fillStyle = p.color;
3170            ctx.globalAlpha = (1 - i * 0.3) * p.life;
3171            ctx.beginPath();
3172            ctx.arc(0, 0, pulseSize - i * 4, 0, Math.PI * 2);
3173            ctx.fill();
3174        }
3175        ctx.globalAlpha = 1;
3176    }
3177
3178    function drawVortex(p) {
3179        ctx.fillStyle = p.color;
3180        ctx.shadowBlur = 8;
3181        ctx.shadowColor = p.color;
3182        ctx.beginPath();
3183        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3184        ctx.fill();
3185    }
3186
3187    function drawCosmicDust(p) {
3188        p.twinkle += 0.2;
3189        const brightness = 0.5 + Math.sin(p.twinkle) * 0.5;
3190        
3191        ctx.fillStyle = p.color;
3192        ctx.shadowBlur = 5 * brightness;
3193        ctx.shadowColor = p.color;
3194        ctx.globalAlpha = brightness * p.life;
3195        ctx.beginPath();
3196        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3197        ctx.fill();
3198        ctx.globalAlpha = 1;
3199    }
3200
3201    // ====================================
3202    // NEW DRAW FUNCTIONS - PHYSICS & OCEAN
3203    // ====================================
3204
3205    function drawBouncyBall(p) {
3206        ctx.fillStyle = p.color;
3207        ctx.shadowBlur = 8;
3208        ctx.shadowColor = p.color;
3209        ctx.beginPath();
3210        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3211        ctx.fill();
3212        
3213        // Highlight
3214        ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
3215        ctx.beginPath();
3216        ctx.arc(-p.size * 0.3, -p.size * 0.3, p.size * 0.3, 0, Math.PI * 2);
3217        ctx.fill();
3218    }
3219
3220    function drawPhysicsBox(p) {
3221        ctx.fillStyle = p.color;
3222        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.3})`;
3223        ctx.lineWidth = 2;
3224        ctx.shadowBlur = 5;
3225        ctx.shadowColor = p.color;
3226        
3227        ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);
3228        ctx.strokeRect(-p.size/2, -p.size/2, p.size, p.size);
3229    }
3230
3231    function drawJellyfish(p) {
3232        p.pulse += 0.05 * p.pulseSpeed;
3233        const pulseSize = p.size * (1 + Math.sin(p.pulse) * 0.1);
3234        
3235        if (p.image && p.image.complete) {
3236            ctx.save();
3237            ctx.globalAlpha = p.life * 0.8;
3238            
3239            // Apply hue rotation for color variation
3240            const hue = (p.hue + Date.now() * 0.01) % 360;
3241            ctx.filter = `hue-rotate(${hue}deg) saturate(0.7) brightness(1.2)`;
3242            
3243            ctx.drawImage(p.image, -pulseSize/2, -pulseSize/2, pulseSize, pulseSize);
3244            ctx.restore();
3245        } else {
3246            // Fallback: draw simple jellyfish shape
3247            ctx.fillStyle = `rgba(200, 150, 255, ${p.life * 0.6})`;
3248            ctx.beginPath();
3249            ctx.ellipse(0, 0, pulseSize * 0.5, pulseSize * 0.6, 0, 0, Math.PI * 2);
3250            ctx.fill();
3251            
3252            // Tentacles
3253            ctx.strokeStyle = `rgba(200, 150, 255, ${p.life * 0.4})`;
3254            ctx.lineWidth = 2;
3255            for (let i = 0; i < 6; i++) {
3256                const angle = (i * Math.PI * 2) / 6;
3257                ctx.beginPath();
3258                ctx.moveTo(Math.cos(angle) * pulseSize * 0.3, Math.sin(angle) * pulseSize * 0.3);
3259                ctx.lineTo(Math.cos(angle) * pulseSize * 0.6, Math.sin(angle) * pulseSize * 0.6 + pulseSize * 0.3);
3260                ctx.stroke();
3261            }
3262        }
3263    }
3264
3265    function drawFish(p) {
3266        p.tailWag += 0.3;
3267        const tailOffset = Math.sin(p.tailWag) * 5;
3268        
3269        ctx.fillStyle = p.color;
3270        
3271        // Body
3272        ctx.beginPath();
3273        ctx.ellipse(0, 0, p.size * 0.6, p.size * 0.4, 0, 0, Math.PI * 2);
3274        ctx.fill();
3275        
3276        // Tail
3277        ctx.beginPath();
3278        ctx.moveTo(-p.size * 0.3, 0);
3279        ctx.lineTo(-p.size * 0.8 + tailOffset * p.direction, -p.size * 0.3);
3280        ctx.lineTo(-p.size * 0.8, 0);
3281        ctx.lineTo(-p.size * 0.8 + tailOffset * p.direction, p.size * 0.3);
3282        ctx.closePath();
3283        ctx.fill();
3284        
3285        // Eye
3286        ctx.fillStyle = '#fff';
3287        ctx.beginPath();
3288        ctx.arc(p.size * 0.2, -p.size * 0.1, p.size * 0.1, 0, Math.PI * 2);
3289        ctx.fill();
3290        ctx.fillStyle = '#000';
3291        ctx.beginPath();
3292        ctx.arc(p.size * 0.25, -p.size * 0.1, p.size * 0.05, 0, Math.PI * 2);
3293        ctx.fill();
3294    }
3295
3296    function drawAcorn(p) {
3297        ctx.fillStyle = '#8b4513';
3298        
3299        // Cap
3300        ctx.beginPath();
3301        ctx.arc(0, -p.size * 0.4, p.size * 0.3, 0, Math.PI * 2);
3302        ctx.fill();
3303        
3304        // Body
3305        ctx.beginPath();
3306        ctx.ellipse(0, p.size * 0.2, p.size * 0.4, p.size * 0.5, 0, 0, Math.PI * 2);
3307        ctx.fill();
3308        
3309        // Stem
3310        ctx.strokeStyle = '#654321';
3311        ctx.lineWidth = 2;
3312        ctx.beginPath();
3313        ctx.moveTo(0, -p.size * 0.6);
3314        ctx.lineTo(0, -p.size * 0.4);
3315        ctx.stroke();
3316    }
3317
3318    function drawPinecone(p) {
3319        ctx.fillStyle = '#8b6914';
3320        
3321        // Draw pinecone shape (oval with scales)
3322        ctx.beginPath();
3323        ctx.ellipse(0, 0, p.size * 0.5, p.size * 0.7, 0, 0, Math.PI * 2);
3324        ctx.fill();
3325        
3326        // Scales
3327        ctx.strokeStyle = '#654321';
3328        ctx.lineWidth = 1;
3329        for (let i = 0; i < 8; i++) {
3330            const angle = (i * Math.PI * 2) / 8;
3331            const x = Math.cos(angle) * p.size * 0.3;
3332            const y = Math.sin(angle) * p.size * 0.4;
3333            ctx.beginPath();
3334            ctx.moveTo(0, 0);
3335            ctx.lineTo(x, y);
3336            ctx.stroke();
3337        }
3338    }
3339
3340    function drawRaindrop(p) {
3341        if (p.splashY !== null) {
3342            // Draw splash
3343            const splashSize = (1 - p.life) * 15;
3344            ctx.strokeStyle = `rgba(150, 200, 255, ${p.life})`;
3345            ctx.lineWidth = 2;
3346            for (let i = 0; i < 4; i++) {
3347                const angle = (i * Math.PI * 2) / 4;
3348                ctx.beginPath();
3349                ctx.moveTo(0, 0);
3350                ctx.lineTo(Math.cos(angle) * splashSize, Math.sin(angle) * splashSize);
3351                ctx.stroke();
3352            }
3353        } else {
3354            // Draw raindrop
3355            ctx.fillStyle = `rgba(150, 200, 255, ${p.life})`;
3356            ctx.beginPath();
3357            ctx.moveTo(0, -p.size);
3358            ctx.lineTo(-p.size * 0.3, 0);
3359            ctx.quadraticCurveTo(0, p.size * 0.3, p.size * 0.3, 0);
3360            ctx.closePath();
3361            ctx.fill();
3362        }
3363    }
3364
3365    function drawMist(p) {
3366        p.expansion += 0.01;
3367        const expandedSize = p.size * p.expansion;
3368        
3369        ctx.fillStyle = `rgba(200, 200, 220, ${p.opacity * p.life})`;
3370        ctx.beginPath();
3371        ctx.arc(0, 0, expandedSize, 0, Math.PI * 2);
3372        ctx.fill();
3373    }
3374
3375    function drawFog(p) {
3376        p.expansion += 0.008;
3377        const expandedSize = p.size * p.expansion;
3378        
3379        ctx.fillStyle = `rgba(180, 180, 200, ${p.opacity * p.life})`;
3380        ctx.beginPath();
3381        ctx.arc(0, 0, expandedSize, 0, Math.PI * 2);
3382        ctx.fill();
3383        
3384        // Additional layers for fog effect
3385        ctx.fillStyle = `rgba(160, 160, 180, ${p.opacity * p.life * 0.5})`;
3386        ctx.beginPath();
3387        ctx.arc(expandedSize * 0.3, expandedSize * 0.2, expandedSize * 0.6, 0, Math.PI * 2);
3388        ctx.fill();
3389    }
3390
3391    function drawChalk(p) {
3392        p.smudge += 0.05;
3393        const smudgeSize = p.size * (1 + p.smudge * 0.3);
3394        
3395        ctx.fillStyle = p.color;
3396        ctx.globalAlpha = p.life * 0.7;
3397        
3398        // Main mark
3399        ctx.beginPath();
3400        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3401        ctx.fill();
3402        
3403        // Smudge effect
3404        ctx.globalAlpha = p.life * 0.3;
3405        ctx.beginPath();
3406        ctx.arc(0, 0, smudgeSize, 0, Math.PI * 2);
3407        ctx.fill();
3408        ctx.globalAlpha = 1;
3409    }
3410
3411    function drawPastel(p) {
3412        p.blend += 0.02;
3413        
3414        // Create gradient for pastel effect
3415        const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, p.size);
3416        gradient.addColorStop(0, p.color);
3417        gradient.addColorStop(0.5, p.color.replace(')', ', 0.6)').replace('rgb', 'rgba').replace('hsl', 'hsla'));
3418        gradient.addColorStop(1, p.color.replace(')', ', 0.2)').replace('rgb', 'rgba').replace('hsl', 'hsla'));
3419        
3420        ctx.fillStyle = gradient;
3421        ctx.beginPath();
3422        ctx.arc(0, 0, p.size * (1 + p.blend * 0.2), 0, Math.PI * 2);
3423        ctx.fill();
3424    }
3425
3426    function drawDataPacket(p) {
3427        // Track path
3428        p.path.push({x: 0, y: 0});
3429        if (p.path.length > 10) p.path.shift();
3430        
3431        // Draw path
3432        ctx.strokeStyle = `rgba(0, 255, 0, ${p.life * 0.3})`;
3433        ctx.lineWidth = 1;
3434        ctx.beginPath();
3435        for (let i = 0; i < p.path.length; i++) {
3436            const point = p.path[i];
3437            if (i === 0) ctx.moveTo(point.x, point.y);
3438            else ctx.lineTo(point.x, point.y);
3439        }
3440        ctx.stroke();
3441        
3442        // Draw packet
3443        ctx.fillStyle = p.color;
3444        ctx.fillRect(-p.size/2, -p.size/2, p.size, p.size);
3445        ctx.strokeStyle = '#0f0';
3446        ctx.lineWidth = 1;
3447        ctx.strokeRect(-p.size/2, -p.size/2, p.size, p.size);
3448        
3449        // Draw data text
3450        ctx.fillStyle = '#000';
3451        ctx.font = `${p.size * 0.4}px monospace`;
3452        ctx.textAlign = 'center';
3453        ctx.textBaseline = 'middle';
3454        ctx.fillText(p.data, 0, 0);
3455    }
3456
3457    function drawSoundWave(p) {
3458        const progress = 1 - p.life;
3459        const currentSize = p.size + (p.maxSize - p.size) * progress;
3460        p.phase += p.frequency;
3461        
3462        ctx.strokeStyle = p.color;
3463        ctx.lineWidth = 2;
3464        ctx.globalAlpha = p.life;
3465        
3466        // Draw wave pattern
3467        ctx.beginPath();
3468        for (let i = 0; i < currentSize; i += 2) {
3469            const angle = (i / currentSize) * Math.PI * 2;
3470            const radius = currentSize * 0.3 + Math.sin(p.phase + i * 0.1) * currentSize * 0.1;
3471            const x = Math.cos(angle) * radius;
3472            const y = Math.sin(angle) * radius;
3473            if (i === 0) ctx.moveTo(x, y);
3474            else ctx.lineTo(x, y);
3475        }
3476        ctx.closePath();
3477        ctx.stroke();
3478        
3479        ctx.globalAlpha = 1;
3480    }
3481
3482    function drawTrafficLight(p) {
3483        p.cycleTime += 0.02;
3484        if (p.cycleTime >= 1) {
3485            p.cycleTime = 0;
3486            p.state = (p.state + 1) % 3;
3487        }
3488        
3489        // Draw housing
3490        ctx.fillStyle = '#333';
3491        ctx.fillRect(-p.size/2, -p.size * 0.8, p.size, p.size * 1.6);
3492        
3493        // Draw lights
3494        const lights = ['#f00', '#ff0', '#0f0'];
3495        const lightSize = p.size * 0.3;
3496        const spacing = p.size * 0.5;
3497        
3498        for (let i = 0; i < 3; i++) {
3499            const y = -p.size * 0.6 + i * spacing;
3500            ctx.fillStyle = i === p.state ? lights[i] : '#333';
3501            ctx.beginPath();
3502            ctx.arc(0, y, lightSize, 0, Math.PI * 2);
3503            ctx.fill();
3504            
3505            if (i === p.state) {
3506                ctx.shadowBlur = 15;
3507                ctx.shadowColor = lights[i];
3508                ctx.fill();
3509                ctx.shadowBlur = 0;
3510            }
3511        }
3512    }
3513
3514    function drawBee(p) {
3515        p.wingFlap += 0.5;
3516        p.zigzag += 0.2;
3517        const zigzagOffset = Math.sin(p.zigzag) * 3;
3518        
3519        ctx.translate(zigzagOffset, 0);
3520        
3521        // Body
3522        ctx.fillStyle = p.color;
3523        ctx.beginPath();
3524        ctx.ellipse(0, 0, p.size * 0.6, p.size * 0.4, 0, 0, Math.PI * 2);
3525        ctx.fill();
3526        
3527        // Stripes
3528        ctx.fillStyle = '#000';
3529        for (let i = 0; i < 3; i++) {
3530            ctx.fillRect(-p.size * 0.5, -p.size * 0.3 + i * p.size * 0.2, p.size, p.size * 0.1);
3531        }
3532        
3533        // Wings
3534        const flapAngle = Math.sin(p.wingFlap) * 0.3;
3535        ctx.fillStyle = 'rgba(200, 200, 255, 0.6)';
3536        ctx.save();
3537        ctx.rotate(flapAngle);
3538        ctx.beginPath();
3539        ctx.ellipse(p.size * 0.3, -p.size * 0.2, p.size * 0.3, p.size * 0.2, 0, 0, Math.PI * 2);
3540        ctx.fill();
3541        ctx.restore();
3542        
3543        ctx.save();
3544        ctx.rotate(-flapAngle);
3545        ctx.beginPath();
3546        ctx.ellipse(p.size * 0.3, p.size * 0.2, p.size * 0.3, p.size * 0.2, 0, 0, Math.PI * 2);
3547        ctx.fill();
3548        ctx.restore();
3549    }
3550
3551    function drawLensFlare(p) {
3552        p.rotation += p.rotationSpeed;
3553        
3554        ctx.save();
3555        ctx.rotate(p.rotation);
3556        
3557        // Helper function to adjust alpha in rgba color
3558        function adjustAlpha(colorStr, newAlpha) {
3559            // Handle rgba format
3560            if (colorStr.startsWith('rgba(')) {
3561                return colorStr.replace(/rgba\(([^)]+)\)/, (match, values) => {
3562                    const parts = values.split(',').map(v => v.trim());
3563                    if (parts.length === 4) {
3564                        return `rgba(${parts[0]}, ${parts[1]}, ${parts[2]}, ${newAlpha})`;
3565                    }
3566                    return match;
3567                });
3568            }
3569            // Handle rgb format
3570            if (colorStr.startsWith('rgb(')) {
3571                return colorStr.replace('rgb(', 'rgba(').replace(')', `, ${newAlpha})`);
3572            }
3573            // Handle hsl/hsla format
3574            if (colorStr.startsWith('hsla(')) {
3575                return colorStr.replace(/hsla\(([^)]+)\)/, (match, values) => {
3576                    const parts = values.split(',').map(v => v.trim());
3577                    if (parts.length === 4) {
3578                        return `hsla(${parts[0]}, ${parts[1]}, ${parts[2]}, ${newAlpha})`;
3579                    }
3580                    return match;
3581                });
3582            }
3583            if (colorStr.startsWith('hsl(')) {
3584                return colorStr.replace('hsl(', 'hsla(').replace(')', `, ${newAlpha})`);
3585            }
3586            // Fallback: wrap in rgba
3587            return `rgba(255, 255, 255, ${newAlpha})`;
3588        }
3589        
3590        // Main flare
3591        const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, p.size);
3592        gradient.addColorStop(0, p.color);
3593        gradient.addColorStop(0.5, adjustAlpha(p.color, 0.5));
3594        gradient.addColorStop(1, adjustAlpha(p.color, 0));
3595        
3596        ctx.fillStyle = gradient;
3597        ctx.beginPath();
3598        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3599        ctx.fill();
3600        
3601        // Rays
3602        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.3})`;
3603        ctx.lineWidth = 2;
3604        for (let i = 0; i < 8; i++) {
3605            const angle = (i * Math.PI * 2) / 8;
3606            ctx.beginPath();
3607            ctx.moveTo(0, 0);
3608            ctx.lineTo(Math.cos(angle) * p.size, Math.sin(angle) * p.size);
3609            ctx.stroke();
3610        }
3611        
3612        ctx.restore();
3613    }
3614
3615    function drawPrism(p) {
3616        p.lightAngle += 0.05;
3617        
3618        // Draw triangular prism
3619        ctx.fillStyle = `rgba(200, 200, 255, ${p.life * 0.6})`;
3620        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.8})`;
3621        ctx.lineWidth = 2;
3622        
3623        ctx.beginPath();
3624        ctx.moveTo(0, -p.size);
3625        ctx.lineTo(-p.size * 0.6, p.size * 0.5);
3626        ctx.lineTo(p.size * 0.6, p.size * 0.5);
3627        ctx.closePath();
3628        ctx.fill();
3629        ctx.stroke();
3630        
3631        // Refracted light (rainbow effect)
3632        if (typeof gsap !== 'undefined') {
3633            // Use GSAP for smooth color transitions if available
3634            const hue = (p.lightAngle * 57.3) % 360;
3635            ctx.fillStyle = `hsla(${hue}, 70%, 60%, ${p.life * 0.4})`;
3636        } else {
3637            // Fallback: simple rainbow gradient
3638            const gradient = ctx.createLinearGradient(-p.size, 0, p.size, 0);
3639            gradient.addColorStop(0, `hsla(${(p.lightAngle * 57.3) % 360}, 70%, 60%, ${p.life * 0.4})`);
3640            gradient.addColorStop(0.5, `hsla(${((p.lightAngle * 57.3) + 60) % 360}, 70%, 60%, ${p.life * 0.4})`);
3641            gradient.addColorStop(1, `hsla(${((p.lightAngle * 57.3) + 120) % 360}, 70%, 60%, ${p.life * 0.4})`);
3642            ctx.fillStyle = gradient;
3643        }
3644        
3645        // Draw refracted light rays
3646        for (let i = 0; i < 3; i++) {
3647            const angle = p.lightAngle + (i - 1) * 0.3;
3648            ctx.beginPath();
3649            ctx.moveTo(0, -p.size);
3650            ctx.lineTo(Math.cos(angle) * p.size * 1.5, Math.sin(angle) * p.size * 1.5);
3651            ctx.stroke();
3652        }
3653    }
3654
3655    // ====================================
3656    // NEW DRAW FUNCTIONS — latest trails
3657    // ====================================
3658
3659    function drawChainLink(p) {
3660        if (p.parent && p.parent.life > 0) {
3661            const dx = p.parent.x - p.x;
3662            const dy = p.parent.y - p.y;
3663            ctx.save();
3664            if (p.rotation) ctx.rotate(-p.rotation);
3665            ctx.strokeStyle = `rgba(80, 60, 40, ${p.life * 0.5})`;
3666            ctx.lineWidth = 2;
3667            ctx.beginPath();
3668            ctx.moveTo(dx, dy);
3669            ctx.lineTo(0, 0);
3670            ctx.stroke();
3671            ctx.restore();
3672        }
3673        ctx.fillStyle = p.color;
3674        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.25})`;
3675        ctx.lineWidth = 1.5;
3676        ctx.beginPath();
3677        ctx.ellipse(0, 0, p.size * 0.55, p.size * 0.85, 0, 0, Math.PI * 2);
3678        ctx.fill();
3679        ctx.stroke();
3680        ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.2})`;
3681        ctx.beginPath();
3682        ctx.ellipse(0, -p.size * 0.15, p.size * 0.25, p.size * 0.35, 0, 0, Math.PI * 2);
3683        ctx.fill();
3684    }
3685
3686    function drawDomino(p) {
3687        const w = p.width;
3688        const h = p.height;
3689        ctx.fillStyle = p.color;
3690        ctx.strokeStyle = `rgba(0, 0, 0, ${p.life * 0.35})`;
3691        ctx.lineWidth = 1;
3692        ctx.fillRect(-w / 2, -h / 2, w, h);
3693        ctx.strokeRect(-w / 2, -h / 2, w, h);
3694        ctx.fillStyle = `rgba(30, 30, 30, ${p.life * 0.85})`;
3695        const pipR = Math.max(1.2, w * 0.18);
3696        const pipPositions = {
3697            1: [[0, 0]],
3698            2: [[-0.25, -0.25], [0.25, 0.25]],
3699            3: [[-0.25, -0.25], [0, 0], [0.25, 0.25]],
3700            4: [[-0.25, -0.25], [0.25, -0.25], [-0.25, 0.25], [0.25, 0.25]],
3701            5: [[-0.25, -0.25], [0.25, -0.25], [0, 0], [-0.25, 0.25], [0.25, 0.25]],
3702            6: [[-0.25, -0.3], [0.25, -0.3], [-0.25, 0], [0.25, 0], [-0.25, 0.3], [0.25, 0.3]]
3703        };
3704        const positions = pipPositions[p.dots] || pipPositions[1];
3705        positions.forEach(function (pos) {
3706            ctx.beginPath();
3707            ctx.arc(pos[0] * w, pos[1] * h, pipR, 0, Math.PI * 2);
3708            ctx.fill();
3709        });
3710    }
3711
3712    function drawSeahorse(p) {
3713        p.swimPhase += 0.08;
3714        const curl = Math.sin(p.swimPhase) * 0.15;
3715        ctx.scale(p.direction, 1);
3716        ctx.fillStyle = p.color;
3717        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.2})`;
3718        ctx.lineWidth = 1;
3719        ctx.beginPath();
3720        ctx.moveTo(p.size * 0.4, 0);
3721        ctx.quadraticCurveTo(p.size * 0.1, -p.size * 0.5, -p.size * 0.2, -p.size * 0.15);
3722        ctx.quadraticCurveTo(-p.size * 0.55 + curl * p.size, p.size * 0.35, -p.size * 0.35, p.size * 0.55);
3723        ctx.quadraticCurveTo(-p.size * 0.1, p.size * 0.75, p.size * 0.15, p.size * 0.35);
3724        ctx.quadraticCurveTo(p.size * 0.35, p.size * 0.1, p.size * 0.4, 0);
3725        ctx.fill();
3726        ctx.stroke();
3727        ctx.fillStyle = '#fff';
3728        ctx.beginPath();
3729        ctx.arc(p.size * 0.32, -p.size * 0.08, p.size * 0.07, 0, Math.PI * 2);
3730        ctx.fill();
3731        ctx.fillStyle = '#111';
3732        ctx.beginPath();
3733        ctx.arc(p.size * 0.34, -p.size * 0.08, p.size * 0.035, 0, Math.PI * 2);
3734        ctx.fill();
3735    }
3736
3737    function drawStarfish(p) {
3738        ctx.fillStyle = p.color;
3739        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.15})`;
3740        ctx.lineWidth = 1;
3741        ctx.beginPath();
3742        for (let i = 0; i < p.arms * 2; i++) {
3743            const angle = (i * Math.PI) / p.arms - Math.PI / 2;
3744            const radius = i % 2 === 0 ? p.size : p.size * 0.45;
3745            const px = Math.cos(angle) * radius;
3746            const py = Math.sin(angle) * radius;
3747            if (i === 0) ctx.moveTo(px, py);
3748            else ctx.lineTo(px, py);
3749        }
3750        ctx.closePath();
3751        ctx.fill();
3752        ctx.stroke();
3753    }
3754
3755    function drawCoral(p) {
3756        ctx.strokeStyle = p.color;
3757        ctx.lineWidth = 2.5;
3758        ctx.lineCap = 'round';
3759        (p.branchData || []).forEach(function (branch) {
3760            const len = p.size * branch.len;
3761            ctx.beginPath();
3762            ctx.moveTo(0, 0);
3763            ctx.quadraticCurveTo(
3764                Math.cos(branch.angle) * len * 0.4,
3765                Math.sin(branch.angle) * len * 0.4 - p.size * 0.2,
3766                Math.cos(branch.angle) * len,
3767                Math.sin(branch.angle) * len
3768            );
3769            ctx.stroke();
3770        });
3771        ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.25})`;
3772        ctx.beginPath();
3773        ctx.arc(0, 0, p.size * 0.15, 0, Math.PI * 2);
3774        ctx.fill();
3775    }
3776
3777    function drawMushroom(p) {
3778        const scale = p.grow;
3779        const capR = p.size * scale;
3780        ctx.fillStyle = p.stemColor;
3781        ctx.fillRect(-capR * 0.2, 0, capR * 0.4, capR * 0.7);
3782        ctx.fillStyle = p.capColor;
3783        ctx.beginPath();
3784        ctx.arc(0, 0, capR * 0.65, Math.PI, 0);
3785        ctx.fill();
3786        if (p.spots) {
3787            ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.7})`;
3788            for (let i = 0; i < 3; i++) {
3789                const sx = (i - 1) * capR * 0.25;
3790                ctx.beginPath();
3791                ctx.arc(sx, -capR * 0.2, capR * 0.1, 0, Math.PI * 2);
3792                ctx.fill();
3793            }
3794        }
3795    }
3796
3797    function drawFeather(p) {
3798        ctx.fillStyle = p.color;
3799        ctx.strokeStyle = `rgba(180, 180, 200, ${p.life * 0.4})`;
3800        ctx.lineWidth = 1;
3801        ctx.beginPath();
3802        ctx.moveTo(0, -p.size * 0.5);
3803        ctx.quadraticCurveTo(p.size * 0.35, 0, 0, p.size * 0.5);
3804        ctx.quadraticCurveTo(-p.size * 0.35, 0, 0, -p.size * 0.5);
3805        ctx.fill();
3806        ctx.stroke();
3807        ctx.beginPath();
3808        ctx.moveTo(0, -p.size * 0.45);
3809        ctx.lineTo(0, p.size * 0.45);
3810        ctx.stroke();
3811        for (let i = -3; i <= 3; i++) {
3812            if (i === 0) continue;
3813            ctx.beginPath();
3814            ctx.moveTo(0, i * p.size * 0.1);
3815            ctx.lineTo(p.size * 0.2, i * p.size * 0.12);
3816            ctx.stroke();
3817        }
3818    }
3819
3820    function drawFruit(p) {
3821        ctx.fillStyle = p.color;
3822        ctx.beginPath();
3823        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3824        ctx.fill();
3825        if (p.fruitKind === 'berry' || p.fruitKind === 'grape') {
3826            ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.35})`;
3827            ctx.beginPath();
3828            ctx.arc(-p.size * 0.25, -p.size * 0.25, p.size * 0.2, 0, Math.PI * 2);
3829            ctx.fill();
3830        }
3831        ctx.strokeStyle = p.stemColor;
3832        ctx.lineWidth = 2;
3833        ctx.beginPath();
3834        ctx.moveTo(0, -p.size);
3835        ctx.lineTo(p.size * 0.1, -p.size * 1.3);
3836        ctx.stroke();
3837    }
3838
3839    function drawPortal(p) {
3840        const pulseSize = p.size * (1 + Math.sin(p.pulse) * 0.12);
3841        ctx.strokeStyle = `hsla(${p.innerHue}, 90%, 65%, ${p.life * 0.9})`;
3842        ctx.lineWidth = 3;
3843        ctx.shadowBlur = 12;
3844        ctx.shadowColor = `hsl(${p.innerHue}, 90%, 60%)`;
3845        ctx.beginPath();
3846        ctx.ellipse(0, 0, pulseSize, pulseSize * 0.75, 0, 0, Math.PI * 2);
3847        ctx.stroke();
3848        ctx.strokeStyle = `hsla(${(p.innerHue + 60) % 360}, 85%, 70%, ${p.life * 0.5})`;
3849        ctx.lineWidth = 1.5;
3850        ctx.beginPath();
3851        ctx.ellipse(0, 0, pulseSize * 0.65, pulseSize * 0.5, 0, 0, Math.PI * 2);
3852        ctx.stroke();
3853        ctx.fillStyle = `hsla(${p.innerHue}, 100%, 75%, ${p.life * 0.25})`;
3854        ctx.beginPath();
3855        ctx.arc(0, 0, pulseSize * 0.2, 0, Math.PI * 2);
3856        ctx.fill();
3857        ctx.shadowBlur = 0;
3858    }
3859
3860    function drawManaOrb(p) {
3861        const pulseSize = p.size + Math.sin(p.pulse) * 3;
3862        ctx.shadowBlur = 14;
3863        ctx.shadowColor = p.color;
3864        const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, pulseSize);
3865        gradient.addColorStop(0, '#fff');
3866        gradient.addColorStop(0.4, p.color);
3867        gradient.addColorStop(1, 'rgba(0,0,0,0)');
3868        ctx.fillStyle = gradient;
3869        ctx.beginPath();
3870        ctx.arc(0, 0, pulseSize, 0, Math.PI * 2);
3871        ctx.fill();
3872        ctx.shadowBlur = 0;
3873    }
3874
3875    function drawVinylRecord(p) {
3876        ctx.fillStyle = '#1a1a1a';
3877        ctx.beginPath();
3878        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3879        ctx.fill();
3880        ctx.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.08})`;
3881        ctx.lineWidth = 0.5;
3882        for (let r = p.size * 0.3; r < p.size; r += p.size * 0.12) {
3883            ctx.beginPath();
3884            ctx.arc(0, 0, r, 0, Math.PI * 2);
3885            ctx.stroke();
3886        }
3887        ctx.fillStyle = `hsl(${p.labelHue}, 70%, 55%)`;
3888        ctx.beginPath();
3889        ctx.arc(0, 0, p.size * 0.32, 0, Math.PI * 2);
3890        ctx.fill();
3891        ctx.fillStyle = '#111';
3892        ctx.beginPath();
3893        ctx.arc(0, 0, p.size * 0.06, 0, Math.PI * 2);
3894        ctx.fill();
3895    }
3896
3897    function drawCoffeeBean(p) {
3898        ctx.fillStyle = p.color;
3899        ctx.beginPath();
3900        ctx.ellipse(0, 0, p.size * 0.7, p.size, 0, 0, Math.PI * 2);
3901        ctx.fill();
3902        ctx.strokeStyle = `rgba(0, 0, 0, ${p.life * 0.4})`;
3903        ctx.lineWidth = 1;
3904        ctx.beginPath();
3905        ctx.moveTo(0, -p.size);
3906        ctx.quadraticCurveTo(p.size * 0.35, 0, 0, p.size);
3907        ctx.stroke();
3908    }
3909
3910    function drawSprinkle(p) {
3911        ctx.fillStyle = p.color;
3912        ctx.fillRect(-p.size / 2, -p.length / 2, p.size, p.length);
3913        ctx.fillStyle = `rgba(255, 255, 255, ${p.life * 0.35})`;
3914        ctx.fillRect(-p.size / 4, -p.length / 2, p.size / 2, p.length * 0.4);
3915    }
3916
3917    function drawDice(p) {
3918        const s = p.size;
3919        ctx.fillStyle = p.color;
3920        ctx.strokeStyle = `rgba(0, 0, 0, ${p.life * 0.3})`;
3921        ctx.lineWidth = 1.5;
3922        ctx.beginPath();
3923        if (typeof ctx.roundRect === 'function') {
3924            ctx.roundRect(-s / 2, -s / 2, s, s, s * 0.15);
3925        } else {
3926            ctx.rect(-s / 2, -s / 2, s, s);
3927        }
3928        ctx.fill();
3929        ctx.stroke();
3930        ctx.fillStyle = p.pipColor;
3931        const pipR = s * 0.1;
3932        const pipMap = {
3933            1: [[0, 0]],
3934            2: [[-0.28, -0.28], [0.28, 0.28]],
3935            3: [[-0.28, -0.28], [0, 0], [0.28, 0.28]],
3936            4: [[-0.28, -0.28], [0.28, -0.28], [-0.28, 0.28], [0.28, 0.28]],
3937            5: [[-0.28, -0.28], [0.28, -0.28], [0, 0], [-0.28, 0.28], [0.28, 0.28]],
3938            6: [[-0.28, -0.32], [0.28, -0.32], [-0.28, 0], [0.28, 0], [-0.28, 0.32], [0.28, 0.32]]
3939        };
3940        (pipMap[p.face] || pipMap[1]).forEach(function (pos) {
3941            ctx.beginPath();
3942            ctx.arc(pos[0] * s, pos[1] * s, pipR, 0, Math.PI * 2);
3943            ctx.fill();
3944        });
3945    }
3946
3947    function drawChatBubble(p) {
3948        const padX = 8;
3949        const padY = 5;
3950        ctx.font = `bold ${Math.max(10, p.size * 0.55)}px Tahoma, Verdana, sans-serif`;
3951        const textW = ctx.measureText(p.text).width;
3952        const bw = textW + padX * 2;
3953        const bh = p.size + padY * 2;
3954        const hue = p.bubbleHue;
3955
3956        ctx.fillStyle = `hsla(${hue}, 55%, 92%, ${p.life * 0.95})`;
3957        ctx.strokeStyle = `hsla(${hue}, 60%, 45%, ${p.life * 0.7})`;
3958        ctx.lineWidth = 1.5;
3959        ctx.beginPath();
3960        if (typeof ctx.roundRect === 'function') {
3961            ctx.roundRect(-bw / 2, -bh / 2, bw, bh, 6);
3962        } else {
3963            ctx.rect(-bw / 2, -bh / 2, bw, bh);
3964        }
3965        ctx.fill();
3966        ctx.stroke();
3967        ctx.beginPath();
3968        ctx.moveTo(-bw * 0.15, bh / 2);
3969        ctx.lineTo(-bw * 0.05, bh / 2 + 6);
3970        ctx.lineTo(bw * 0.05, bh / 2);
3971        ctx.fill();
3972
3973        ctx.fillStyle = `hsla(${hue}, 50%, 25%, ${p.life})`;
3974        ctx.textAlign = 'center';
3975        ctx.textBaseline = 'middle';
3976        ctx.fillText(p.text, 0, 0);
3977    }
3978
3979    function drawSpaghettiSauce(p) {
3980        const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, p.size);
3981        gradient.addColorStop(0, `rgba(192, 57, 43, ${p.life * 0.85})`);
3982        gradient.addColorStop(0.6, `rgba(160, 40, 30, ${p.life * 0.6})`);
3983        gradient.addColorStop(1, `rgba(120, 30, 20, 0)`);
3984        ctx.fillStyle = gradient;
3985        ctx.beginPath();
3986        ctx.arc(0, 0, p.size, 0, Math.PI * 2);
3987        ctx.fill();
3988    }
3989
3990    // ====================================
3991    // PUBLIC API
3992    // ====================================
3993
3994    function setTrailColors(colors) {
3995        trailColorOverrides = mergeTrailColors(colors);
3996        emitDevLog('✨ Trail colors updated', trailColorOverrides);
3997    }
3998
3999    function getTrailColors() {
4000        return mergeTrailColors(trailColorOverrides);
4001    }
4002
4003    function setTrailType(type) {
4004        if (TRAIL_TYPES[type] === undefined) {
4005            emitDevWarn(`Unknown trail type: ${type}`);
4006            return;
4007        }
4008
4009        currentTrailType = type;
4010        spaghettiPath = [];
4011
4012        if (window.PupwifeCursorTrailHandlers) {
4013            window.PupwifeCursorTrailHandlers.onTrailChange(type);
4014        }
4015        if (window.PupwifeCursorTrailsMatter) {
4016            if (isMatterTrail(type) && canvas) {
4017                window.PupwifeCursorTrailsMatter.onTrailChange(type, canvas.width, canvas.height);
4018            } else if (!isMatterTrail(type)) {
4019                window.PupwifeCursorTrailsMatter.destroy();
4020            }
4021        }
4022        requestTrailExtensions();
4023
4024        // Start/stop animation
4025        if (type === 'none') {
4026            if (animationFrameId !== null) {
4027                cancelAnimationFrame(animationFrameId);
4028                animationFrameId = null;
4029            }
4030            particles = [];
4031            spaghettiPath = [];
4032            if (window.PupwifeCursorTrailsMatter) {
4033                window.PupwifeCursorTrailsMatter.destroy();
4034            }
4035            if (ctx) {
4036                ctx.clearRect(0, 0, canvas.width, canvas.height);
4037            }
4038        } else {
4039            if (animationFrameId === null) {
4040                update();
4041            }
4042        }
4043
4044        emitDevLog(`✨ Cursor trail changed to: ${type}`);
4045    }
4046
4047    function setTrailAmount(level) {
4048        currentTrailAmount = normalizeTrailAmount(level);
4049        emitDevLog(`✨ Trail amount: ${TRAIL_AMOUNT_LABELS[currentTrailAmount]}`);
4050    }
4051
4052    function getTrailAmount() {
4053        return currentTrailAmount;
4054    }
4055
4056    function getTrailType() {
4057        return currentTrailType;
4058    }
4059
4060    function cleanup() {
4061        if (animationFrameId !== null) {
4062            cancelAnimationFrame(animationFrameId);
4063        }
4064        if (canvas && canvas.parentNode) {
4065            canvas.parentNode.removeChild(canvas);
4066        }
4067        document.removeEventListener('mousemove', handleMouseMove);
4068        window.removeEventListener('resize', resizeCanvas);
4069    }
4070
4071    // ====================================
4072    // EXPORT
4073    // ====================================
4074
4075    window.CursorTrails = {
4076        init,
4077        setTrailType,
4078        setTrailAmount,
4079        setTrailColors,
4080        getTrailColors,
4081        getTrailType,
4082        getTrailAmount,
4083        cleanup,
4084        TRAIL_TYPES,
4085        TRAIL_AMOUNT_LABELS
4086    };
4087
4088    // Auto-initialize on load
4089    if (document.readyState === 'loading') {
4090        document.addEventListener('DOMContentLoaded', init);
4091    } else {
4092        init();
4093    }
4094
4095})();
4096

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.