1/** 2 * Extended cursor trail handlers (particles + verlet rope). 3 * Loaded alongside cursorTrails.js â registers on window.PupwifeCursorTrailHandlers. 4 */ 5(function (global) { 6 'use strict'; 7 8 var HANDLER_TYPES = { 9 tardigrade: true, 10 seashells: true, 11 seaweed: true, 12 pearlBubbles: true, 13 bioluminescence: true, 14 snowballFight: true, 15 valentineCandy: true, 16 ropeYarn: true, 17 tetrominoes: true, 18 pokerChips: true, 19 xpOrbs: true, 20 pixelHearts: true, 21 paperClips: true, 22 stickyNotes: true, 23 popcorn: true, 24 gummyWoims: true, 25 shootingStars: true, 26 snails: true, 27 moths: true, 28 bats: true, 29 frogs: true, 30 woims: true, 31 equalizerBars: true, 32 glitterSparkles: true, 33 moonDust: true 34 }; 35 36 var TETRO_KEYS = ['I', 'O', 'T', 'S', 'Z', 'J', 'L']; 37 var TETRO_COLORS = { 38 I: '#00e5e5', O: '#e5e500', T: '#b040e0', S: '#40e040', Z: '#e04040', J: '#4040e0', L: '#e0a040' 39 }; 40 var TETRO_CELLS = { 41 I: [[0, 0], [1, 0], [2, 0], [3, 0]], 42 O: [[0, 0], [1, 0], [0, 1], [1, 1]], 43 T: [[0, 0], [1, 0], [2, 0], [1, 1]], 44 S: [[1, 0], [2, 0], [0, 1], [1, 1]], 45 Z: [[0, 0], [1, 0], [1, 1], [2, 1]], 46 J: [[0, 0], [0, 1], [1, 1], [2, 1]], 47 L: [[2, 0], [0, 1], [1, 1], [2, 1]] 48 }; 49 var CHIP_COLORS = ['#c62828', '#1565c0', '#2e7d32', '#6a1b9a', '#ef6c00']; 50 var STICKY_TEXTS = ['!', '?', 'hi', 'todo', 'uwu', 'note', 'ok', 'hmm']; 51 52 var VALENTINE_WORDS = [ 53 'LOVE', 'XOXO', 'CUTIE', 'BFF', 'HUG', 'SWEET', 'PUP', 'UWU', ':3C', 54 'HEY', 'SMILE', 'MOOD', 'YEET', 'HAI', 'COZY', 'LUCKY', 'TRUE' 55 ]; 56 57 var rope = { 58 points: [], 59 segmentLen: 10, 60 maxPoints: 28 61 }; 62 63 function isHandlerType(type) { 64 return !!HANDLER_TYPES[type]; 65 } 66 67 function onTrailChange(type) { 68 rope.points = []; 69 } 70 71 function spawnTardigrade(x, y, ctx) { 72 if (Math.random() > 0.55) return; 73 ctx.particles.push({ 74 x: x + (Math.random() - 0.5) * 12, 75 y: y + (Math.random() - 0.5) * 12, 76 vx: (Math.random() - 0.5) * 1.2, 77 vy: -0.2 - Math.random() * 0.6, 78 life: 1, 79 maxLife: 2.5 + Math.random() * 1.5, 80 size: 10 + Math.random() * 8, 81 wiggle: Math.random() * Math.PI * 2, 82 hue: 200 + Math.random() * 40, 83 type: 'tardigrade' 84 }); 85 } 86 87 function spawnSeashell(x, y, ctx) { 88 if (Math.random() > 0.5) return; 89 ctx.particles.push({ 90 x: x + (Math.random() - 0.5) * 14, 91 y: y + (Math.random() - 0.5) * 14, 92 vx: (Math.random() - 0.5) * 1.5, 93 vy: 0.4 + Math.random() * 1.2, 94 life: 1, 95 maxLife: 3 + Math.random() * 2, 96 size: 10 + Math.random() * 10, 97 rotation: Math.random() * Math.PI * 2, 98 rotationSpeed: (Math.random() - 0.5) * 0.12, 99 spiral: 0.4 + Math.random() * 0.3, 100 color: `hsl(${25 + Math.random() * 20}, ${45 + Math.random() * 25}%, ${72 + Math.random() * 15}%)`, 101 type: 'seashell' 102 }); 103 } 104 105 function spawnSeaweed(x, y, ctx) { 106 if (Math.random() > 0.45) return; 107 var segments = 4 + Math.floor(Math.random() * 3); 108 ctx.particles.push({ 109 x: x + (Math.random() - 0.5) * 10, 110 y: y + (Math.random() - 0.5) * 10, 111 vx: (Math.random() - 0.5) * 0.4, 112 vy: -0.1 - Math.random() * 0.3, 113 life: 1, 114 maxLife: 2.5 + Math.random() * 1.5, 115 size: 14 + Math.random() * 12, 116 segments: segments, 117 sway: Math.random() * Math.PI * 2, 118 swaySpeed: 0.08 + Math.random() * 0.06, 119 color: `hsl(${120 + Math.random() * 50}, ${50 + Math.random() * 30}%, ${35 + Math.random() * 20}%)`, 120 type: 'seaweed' 121 }); 122 } 123 124 function spawnPearlBubble(x, y, ctx) { 125 if (Math.random() > 0.4) return; 126 ctx.particles.push({ 127 x: x + (Math.random() - 0.5) * 10, 128 y: y + (Math.random() - 0.5) * 10, 129 vx: (Math.random() - 0.5) * 0.3, 130 vy: -0.6 - Math.random() * 1.2, 131 life: 1, 132 maxLife: 1.8 + Math.random() * 1.2, 133 size: 3 + Math.random() * 6, 134 popped: false, 135 type: 'pearlBubble' 136 }); 137 } 138 139 function spawnBioluminescence(x, y, ctx) { 140 var speed = Math.sqrt(ctx.mouseVelX * ctx.mouseVelX + ctx.mouseVelY * ctx.mouseVelY); 141 if (speed < 2 && Math.random() > 0.3) return; 142 ctx.particles.push({ 143 x: x + (Math.random() - 0.5) * 16, 144 y: y + (Math.random() - 0.5) * 16, 145 vx: (Math.random() - 0.5) * 0.8, 146 vy: (Math.random() - 0.5) * 0.8, 147 life: 1, 148 maxLife: 0.8 + Math.random() * 1.2, 149 size: 2 + Math.random() * 4, 150 flash: Math.random() * Math.PI * 2, 151 flashSpeed: 0.2 + Math.random() * 0.25, 152 intensity: Math.min(1, speed / 12), 153 color: `hsl(${170 + Math.random() * 80}, 90%, ${55 + Math.random() * 25}%)`, 154 type: 'bioluminescence' 155 }); 156 } 157 158 function spawnSnowball(x, y, ctx) { 159 if (Math.random() > 0.5) return; 160 ctx.particles.push({ 161 x: x + (Math.random() - 0.5) * 14, 162 y: y + (Math.random() - 0.5) * 14, 163 vx: (Math.random() - 0.5) * 3, 164 vy: (Math.random() - 0.5) * 2, 165 life: 1, 166 maxLife: 2 + Math.random() * 1.5, 167 size: 8 + Math.random() * 10, 168 rotation: Math.random() * Math.PI * 2, 169 rotationSpeed: (Math.random() - 0.5) * 0.2, 170 bounce: 0.35, 171 gravity: 0.35, 172 splatted: false, 173 type: 'snowball' 174 }); 175 } 176 177 function spawnValentineCandy(x, y, ctx) { 178 if (Math.random() > 0.5) return; 179 var word = VALENTINE_WORDS[Math.floor(Math.random() * VALENTINE_WORDS.length)];
180 ctx.particles.push({ 181 x: x + (Math.random() - 0.5) * 16, 182 y: y + (Math.random() - 0.5) * 16, 183 vx: (Math.random() - 0.5) * 1.5, 184 vy: 0.3 + Math.random() * 1, 185 life: 1, 186 maxLife: 3 + Math.random() * 2, 187 size: 11 + word.length * 1.2, 188 rotation: (Math.random() - 0.5) * 0.4, 189 rotationSpeed: (Math.random() - 0.5) * 0.04, 190 word: word, 191 candyHue: 330 + Math.random() * 30, 192 type: 'valentineCandy' 193 }); 194 } 195 196 function spawnRopeYarn(x, y) { 197 if (rope.points.length === 0) { 198 rope.points.push({ x: x, y: y, ox: x, oy: y }); 199 return; 200 } 201 var last = rope.points[rope.points.length - 1]; 202 var dx = x - last.x; 203 var dy = y - last.y; 204 if (dx * dx + dy * dy < 36) return; 205 rope.points.push({ x: x, y: y, ox: x, oy: y }); 206 if (rope.points.length > rope.maxPoints) { 207 rope.points.shift(); 208 } 209 } 210 211 function pushParticle(ctx, p) { 212 ctx.particles.push(p); 213 } 214 215 function spawnTetromino(x, y, ctx) { 216 if (Math.random() > 0.55) return; 217 var key = TETRO_KEYS[Math.floor(Math.random() * TETRO_KEYS.length)]; 218 pushParticle(ctx, { 219 x: x + (Math.random() - 0.5) * 12, 220 y: y + (Math.random() - 0.5) * 12, 221 vx: (Math.random() - 0.5) * 2, 222 vy: 0.5 + Math.random() * 1.5, 223 life: 1, 224 maxLife: 2.5 + Math.random() * 1.5, 225 size: 5 + Math.random() * 2, 226 rotation: Math.random() * Math.PI * 2, 227 rotationSpeed: (Math.random() - 0.5) * 0.08, 228 tetroKey: key, 229 color: TETRO_COLORS[key], 230 type: 'tetromino' 231 }); 232 } 233 234 function spawnPokerChip(x, y, ctx) { 235 if (Math.random() > 0.5) return; 236 pushParticle(ctx, { 237 x: x + (Math.random() - 0.5) * 14, 238 y: y + (Math.random() - 0.5) * 14, 239 vx: (Math.random() - 0.5) * 1.5, 240 vy: 0.3 + Math.random() * 1, 241 life: 1, 242 maxLife: 2.5 + Math.random() * 1.5, 243 size: 10 + Math.random() * 6, 244 rotation: Math.random() * Math.PI * 2, 245 rotationSpeed: (Math.random() - 0.5) * 0.15, 246 chipColor: CHIP_COLORS[Math.floor(Math.random() * CHIP_COLORS.length)], 247 type: 'pokerChip' 248 }); 249 } 250 251 function spawnXpOrb(x, y, ctx) { 252 if (Math.random() > 0.45) return; 253 pushParticle(ctx, { 254 x: x + (Math.random() - 0.5) * 12, 255 y: y + (Math.random() - 0.5) * 12, 256 vx: (Math.random() - 0.5) * 1.2, 257 vy: -0.4 - Math.random() * 0.8, 258 life: 1, 259 maxLife: 1.5 + Math.random() * 1, 260 size: 8 + Math.random() * 6, 261 pulse: Math.random() * Math.PI * 2, 262 type: 'xpOrb' 263 }); 264 } 265 266 function spawnPixelHeart(x, y, ctx) { 267 if (Math.random() > 0.5) return; 268 pushParticle(ctx, { 269 x: x + (Math.random() - 0.5) * 12, 270 y: y + (Math.random() - 0.5) * 12, 271 vx: (Math.random() - 0.5) * 1, 272 vy: -0.3 - Math.random() * 0.6, 273 life: 1, 274 maxLife: 2 + Math.random() * 1.5, 275 size: 4 + Math.floor(Math.random() * 3), 276 heartHue: Math.random() > 0.5 ? 350 : 200, 277 type: 'pixelHeart' 278 }); 279 } 280 281 function spawnPaperClip(x, y, ctx) { 282 if (Math.random() > 0.5) return; 283 pushParticle(ctx, { 284 x: x + (Math.random() - 0.5) * 14, 285 y: y + (Math.random() - 0.5) * 14, 286 vx: (Math.random() - 0.5) * 1.5, 287 vy: 0.2 + Math.random() * 0.8, 288 life: 1, 289 maxLife: 2.5 + Math.random() * 1.5, 290 size: 12 + Math.random() * 6, 291 rotation: (Math.random() - 0.5) * 0.8, 292 rotationSpeed: (Math.random() - 0.5) * 0.04, 293 type: 'paperClip' 294 }); 295 } 296 297 function spawnStickyNote(x, y, ctx) { 298 if (Math.random() > 0.5) return;
299 pushParticle(ctx, { 300 x: x + (Math.random() - 0.5) * 16, 301 y: y + (Math.random() - 0.5) * 16, 302 vx: (Math.random() - 0.5) * 1, 303 vy: 0.2 + Math.random() * 0.6, 304 life: 1, 305 maxLife: 3 + Math.random() * 2, 306 size: 14 + Math.random() * 6, 307 rotation: (Math.random() - 0.5) * 0.5, 308 rotationSpeed: (Math.random() - 0.5) * 0.03, 309 text: STICKY_TEXTS[Math.floor(Math.random() * STICKY_TEXTS.length)], 310 noteHue: 48 + Math.random() * 12, 311 type: 'stickyNote' 312 }); 313 } 314 315 function spawnPopcorn(x, y, ctx) { 316 if (Math.random() > 0.45) return; 317 pushParticle(ctx, { 318 x: x + (Math.random() - 0.5) * 16, 319 y: y + (Math.random() - 0.5) * 16, 320 vx: (Math.random() - 0.5) * 2.5, 321 vy: -1.5 - Math.random() * 2, 322 life: 1, 323 maxLife: 1.5 + Math.random() * 1, 324 size: 5 + Math.random() * 7, 325 bob: Math.random() * Math.PI * 2, 326 type: 'popcorn' 327 }); 328 } 329 330 function spawnGummyWoim(x, y, ctx) { 331 if (Math.random() > 0.5) return; 332 var segs = 5 + Math.floor(Math.random() * 3); 333 pushParticle(ctx, { 334 x: x + (Math.random() - 0.5) * 12, 335 y: y + (Math.random() - 0.5) * 12, 336 vx: (Math.random() - 0.5) * 1.5, 337 vy: 0.3 + Math.random() * 0.8, 338 life: 1, 339 maxLife: 2.5 + Math.random() * 1.5, 340 size: 16 + Math.random() * 10, 341 segments: segs, 342 wiggle: Math.random() * Math.PI * 2, 343 wormHue: Math.random() * 360, 344 type: 'gummyWoim' 345 }); 346 } 347 348 function spawnShootingStar(x, y, ctx) { 349 var speed = Math.sqrt(ctx.mouseVelX * ctx.mouseVelX + ctx.mouseVelY * ctx.mouseVelY); 350 if (speed < 1.5 && Math.random() > 0.25) return; 351 var angle = speed > 0.5 ? Math.atan2(ctx.mouseVelY, ctx.mouseVelX) : Math.random() * Math.PI * 2; 352 pushParticle(ctx, { 353 x: x, 354 y: y, 355 vx: Math.cos(angle) * (2 + speed * 0.15), 356 vy: Math.sin(angle) * (2 + speed * 0.15), 357 life: 1, 358 maxLife: 0.6 + Math.random() * 0.5, 359 size: 3 + Math.random() * 4, 360 tailLen: 20 + Math.random() * 30, 361 angle: angle, 362 type: 'shootingStar' 363 }); 364 } 365 366 function spawnSnail(x, y, ctx) { 367 if (Math.random() > 0.5) return; 368 pushParticle(ctx, { 369 x: x + (Math.random() - 0.5) * 10, 370 y: y + (Math.random() - 0.5) * 10, 371 vx: (Math.random() - 0.5) * 0.6, 372 vy: 0.15 + Math.random() * 0.4, 373 life: 1, 374 maxLife: 3 + Math.random() * 2, 375 size: 10 + Math.random() * 6, 376 slime: true, 377 wiggle: Math.random() * Math.PI * 2, 378 type: 'snail' 379 }); 380 } 381 382 function spawnMoth(x, y, ctx) { 383 if (Math.random() > 0.45) return; 384 pushParticle(ctx, { 385 x: x + (Math.random() - 0.5) * 20, 386 y: y + (Math.random() - 0.5) * 20, 387 vx: (Math.random() - 0.5) * 0.8, 388 vy: (Math.random() - 0.5) * 0.8, 389 life: 1, 390 maxLife: 2 + Math.random() * 1.5, 391 size: 10 + Math.random() * 8, 392 flutter: Math.random() * Math.PI * 2, 393 wingHue: 40 + Math.random() * 30, 394 type: 'moth' 395 }); 396 } 397 398 function spawnBat(x, y, ctx) { 399 if (Math.random() > 0.5) return; 400 pushParticle(ctx, { 401 x: x + (Math.random() - 0.5) * 16, 402 y: y + (Math.random() - 0.5) * 16, 403 vx: (Math.random() - 0.5) * 2, 404 vy: -0.3 - Math.random() * 0.8, 405 life: 1, 406 maxLife: 2 + Math.random() * 1.5, 407 size: 12 + Math.random() * 8, 408 flap: Math.random() * Math.PI * 2, 409 type: 'bat' 410 }); 411 } 412 413 function spawnFrog(x, y, ctx) { 414 if (Math.random() > 0.55) return;
415 pushParticle(ctx, { 416 x: x + (Math.random() - 0.5) * 12, 417 y: y + (Math.random() - 0.5) * 12, 418 vx: (Math.random() - 0.5) * 1.5, 419 vy: -2 - Math.random() * 2, 420 life: 1, 421 maxLife: 2 + Math.random() * 1, 422 size: 10 + Math.random() * 6, 423 hopPhase: 0, 424 grounded: false, 425 gravity: 0.25, 426 type: 'frog' 427 }); 428 } 429 430 function spawnWoim(x, y, ctx) { 431 if (Math.random() > 0.5) return; 432 pushParticle(ctx, { 433 x: x + (Math.random() - 0.5) * 12, 434 y: y + (Math.random() - 0.5) * 12, 435 vx: (Math.random() - 0.5) * 0.8, 436 vy: 0.2 + Math.random() * 0.5, 437 life: 1, 438 maxLife: 2.5 + Math.random() * 1.5, 439 size: 14 + Math.random() * 8, 440 segments: 4 + Math.floor(Math.random() * 3), 441 wiggle: Math.random() * Math.PI * 2, 442 type: 'woim' 443 }); 444 } 445 446 function spawnEqualizerBar(x, y, ctx) { 447 if (Math.random() > 0.4) return; 448 var bars = []; 449 for (var b = 0; b < 5; b++) { 450 bars.push(0.3 + Math.random() * 0.7); 451 } 452 pushParticle(ctx, { 453 x: x + (Math.random() - 0.5) * 10, 454 y: y + (Math.random() - 0.5) * 10, 455 vx: (Math.random() - 0.5) * 0.5, 456 vy: -0.1 - Math.random() * 0.3, 457 life: 1, 458 maxLife: 1.2 + Math.random() * 0.8, 459 size: 18 + Math.random() * 8, 460 bars: bars, 461 barPhase: Math.random() * Math.PI * 2, 462 barHue: 260 + Math.random() * 80, 463 type: 'equalizerBar' 464 }); 465 } 466 467 function spawnGlitterSparkle(x, y, ctx) { 468 if (Math.random() > 0.35) return; 469 pushParticle(ctx, { 470 x: x + (Math.random() - 0.5) * 14, 471 y: y + (Math.random() - 0.5) * 14, 472 vx: (Math.random() - 0.5) * 1.2, 473 vy: (Math.random() - 0.5) * 1.2, 474 life: 1, 475 maxLife: 0.5 + Math.random() * 0.6, 476 size: 3 + Math.random() * 5, 477 twinkle: Math.random() * Math.PI * 2, 478 color: `hsl(${40 + Math.random() * 40}, 90%, ${70 + Math.random() * 20}%)`, 479 type: 'glitterSparkle' 480 }); 481 } 482 483 function spawnMoonDust(x, y, ctx) { 484 if (Math.random() > 0.4) return; 485 pushParticle(ctx, { 486 x: x + (Math.random() - 0.5) * 18, 487 y: y + (Math.random() - 0.5) * 18, 488 vx: (Math.random() - 0.5) * 0.4, 489 vy: 0.15 + Math.random() * 0.5, 490 life: 1, 491 maxLife: 2 + Math.random() * 2, 492 size: 2 + Math.random() * 4, 493 drift: Math.random() * Math.PI * 2, 494 type: 'moonDust' 495 }); 496 } 497 498 var spawners = { 499 tardigrade: spawnTardigrade, 500 seashells: spawnSeashell, 501 seaweed: spawnSeaweed, 502 pearlBubbles: spawnPearlBubble, 503 bioluminescence: spawnBioluminescence, 504 snowballFight: spawnSnowball, 505 valentineCandy: spawnValentineCandy, 506 ropeYarn: spawnRopeYarn, 507 tetrominoes: spawnTetromino, 508 pokerChips: spawnPokerChip, 509 xpOrbs: spawnXpOrb, 510 pixelHearts: spawnPixelHeart, 511 paperClips: spawnPaperClip, 512 stickyNotes: spawnStickyNote, 513 popcorn: spawnPopcorn, 514 gummyWoims: spawnGummyWoim, 515 shootingStars: spawnShootingStar, 516 snails: spawnSnail, 517 moths: spawnMoth, 518 bats: spawnBat, 519 frogs: spawnFrog, 520 woims: spawnWoim, 521 equalizerBars: spawnEqualizerBar, 522 glitterSparkles: spawnGlitterSparkle, 523 moonDust: spawnMoonDust 524 }; 525 526 function spawn(type, x, y, ctx) { 527 if (spawners[type]) spawners[type](x, y, ctx); 528 } 529 530 function updateRopeYarn(mouseX, mouseY, deltaTime) { 531 if (rope.points.length === 0) return; 532 var head = rope.points[rope.points.length - 1]; 533 head.x = mouseX; 534 head.y = mouseY; 535 536 for (var i = 0; i < rope.points.length; i++) { 537 var p = rope.points[i]; 538 var vx = p.x - p.ox; 539 var vy = p.y - p.oy; 540 p.ox = p.x; 541 p.oy = p.y; 542 p.x += vx * 0.92; 543 p.y += vy * 0.92 + 0.15 * deltaTime; 544 } 545 546 for (var iter = 0; iter < 3; iter++) { 547 for (var j = 1; j < rope.points.length; j++) { 548 var a = rope.points[j - 1]; 549 var b = rope.points[j];
550 var dx = b.x - a.x; 551 var dy = b.y - a.y; 552 var dist = Math.sqrt(dx * dx + dy * dy) || 0.001; 553 var diff = (dist - rope.segmentLen) / dist; 554 var offX = dx * diff * 0.5; 555 var offY = dy * diff * 0.5; 556 if (j < rope.points.length - 1) { 557 a.x += offX; 558 a.y += offY; 559 } 560 b.x -= offX; 561 b.y -= offY; 562 } 563 } 564 } 565 566 function drawRopeYarn(ctx2d, canvas) { 567 if (rope.points.length < 2) return; 568 ctx2d.save(); 569 ctx2d.lineCap = 'round'; 570 ctx2d.lineJoin = 'round'; 571 for (var i = 1; i < rope.points.length; i++) { 572 var t = i / rope.points.length; 573 var a = rope.points[i - 1]; 574 var b = rope.points[i]; 575 ctx2d.strokeStyle = `rgba(${200 + t * 30}, ${140 + t * 40}, ${220 - t * 30}, ${0.35 + t * 0.45})`; 576 ctx2d.lineWidth = 5 - t * 2; 577 ctx2d.beginPath(); 578 ctx2d.moveTo(a.x, a.y); 579 ctx2d.lineTo(b.x, b.y); 580 ctx2d.stroke(); 581 } 582 ctx2d.restore(); 583 } 584 585 function updateFrame(trailType, state) { 586 if (trailType === 'ropeYarn') { 587 updateRopeYarn(state.lastMouseX, state.lastMouseY, state.deltaTime); 588 drawRopeYarn(state.ctx, state.canvas); 589 } 590 } 591 592 function updateParticle(p, deltaTime, state) { 593 if (p.type === 'tardigrade') { 594 p.wiggle += 0.14 * deltaTime; 595 p.vy += 0.03 * deltaTime; 596 } 597 if (p.type === 'seaweed') { 598 p.sway += p.swaySpeed * deltaTime; 599 p.vx += Math.sin(p.sway) * 0.06 * deltaTime; 600 } 601 if (p.type === 'pearlBubble' && !p.popped && p.life < 0.22) { 602 p.popped = true; 603 p.vx = 0; 604 p.vy = 0; 605 } 606 if (p.type === 'bioluminescence') { 607 p.flash += p.flashSpeed * deltaTime * (1 + p.intensity); 608 } 609 if (p.type === 'snowball' && !p.splatted) { 610 p.vy += p.gravity * deltaTime; 611 if (p.y >= state.canvas.height - p.size - 10) { 612 p.splatted = true; 613 p.vy = 0; 614 p.vx *= 0.3; 615 p.life = Math.min(p.life, 0.35); 616 } 617 } 618 if (p.type === 'valentineCandy') { 619 p.vy += 0.05 * deltaTime; 620 } 621 if (p.type === 'popcorn') { 622 p.bob += 0.2 * deltaTime; 623 p.vy += 0.08 * deltaTime; 624 } 625 if (p.type === 'gummyWoim' || p.type === 'woim') { 626 p.wiggle += 0.16 * deltaTime; 627 } 628 if (p.type === 'snail') { 629 p.wiggle += 0.1 * deltaTime; 630 p.vx += Math.sin(p.wiggle) * 0.04 * deltaTime; 631 } 632 if (p.type === 'moth' && state.lastMouseX !== undefined) { 633 p.flutter += 0.22 * deltaTime; 634 var mx = state.lastMouseX - p.x; 635 var my = state.lastMouseY - p.y; 636 var dist = Math.sqrt(mx * mx + my * my) || 1; 637 p.vx += (mx / dist) * 0.04 * deltaTime; 638 p.vy += (my / dist) * 0.04 * deltaTime; 639 p.vx += Math.sin(p.flutter) * 0.06 * deltaTime; 640 } 641 if (p.type === 'bat') { 642 p.flap += 0.35 * deltaTime; 643 p.vy += Math.sin(p.flap) * 0.05 * deltaTime; 644 } 645 if (p.type === 'frog' && !p.grounded) { 646 p.vy += p.gravity * deltaTime; 647 if (p.y >= state.canvas.height - p.size - 12) { 648 p.grounded = true; 649 p.vy = -1.2 - Math.random() * 0.8; 650 p.vx = (Math.random() - 0.5) * 2; 651 } 652 } 653 if (p.type === 'equalizerBar') { 654 p.barPhase += 0.18 * deltaTime; 655 for (var bi = 0; bi < p.bars.length; bi++) { 656 p.bars[bi] = 0.25 + Math.abs(Math.sin(p.barPhase + bi * 0.9)) * 0.75; 657 } 658 } 659 if (p.type === 'glitterSparkle') { 660 p.twinkle += 0.25 * deltaTime; 661 } 662 if (p.type === 'moonDust') { 663 p.drift += 0.08 * deltaTime; 664 p.vx += Math.sin(p.drift) * 0.03 * deltaTime; 665 p.vy += 0.02 * deltaTime; 666 } 667 if (p.type === 'xpOrb') { 668 p.pulse += 0.14 * deltaTime; 669 } 670 } 671 672 function draw(ctx2d, p) { 673 switch (p.type) { 674 case 'tardigrade': 675 drawTardigrade(ctx2d, p); 676 break; 677 case 'seashell': 678 drawSeashell(ctx2d, p); 679 break; 680 case 'seaweed': 681 drawSeaweed(ctx2d, p); 682 break; 683 case 'pearlBubble': 684 drawPearlBubble(ctx2d, p); 685 break; 686 case 'bioluminescence': 687 drawBioluminescence(ctx2d, p); 688 break; 689 case 'snowball': 690 drawSnowball(ctx2d, p); 691 break; 692 case 'valentineCandy': 693 drawValentineCandy(ctx2d, p); 694 break; 695 case 'tetromino': 696 drawTetromino(ctx2d, p); 697 break; 698 case 'pokerChip': 699 drawPokerChip(ctx2d, p); 700 break; 701 case 'xpOrb': 702 drawXpOrb(ctx2d, p); 703 break; 704 case 'pixelHeart': 705 drawPixelHeart(ctx2d, p); 706 break; 707 case 'paperClip': 708 drawPaperClip(ctx2d, p); 709 break; 710 case 'stickyNote': 711 drawStickyNote(ctx2d, p); 712 break; 713 case 'popcorn': 714 drawPopcorn(ctx2d, p); 715 break; 716 case 'gummyWoim': 717 drawGummyWoim(ctx2d, p); 718 break; 719 case 'shootingStar': 720 drawShootingStar(ctx2d, p); 721 break; 722 case 'snail': 723 drawSnail(ctx2d, p); 724 break; 725 case 'moth': 726 drawMoth(ctx2d, p); 727 break; 728 case 'bat': 729 drawBat(ctx2d, p); 730 break; 731 case 'frog': 732 drawFrog(ctx2d, p); 733 break; 734 case 'woim': 735 drawWoim(ctx2d, p); 736 break;
737 case 'equalizerBar': 738 drawEqualizerBar(ctx2d, p); 739 break; 740 case 'glitterSparkle': 741 drawGlitterSparkle(ctx2d, p); 742 break; 743 case 'moonDust': 744 drawMoonDust(ctx2d, p); 745 break; 746 } 747 } 748 749 function drawTardigrade(ctx2d, p) { 750 var s = p.size; 751 var wobble = Math.sin(p.wiggle) * 0.15; 752 ctx2d.fillStyle = `hsla(${p.hue}, 35%, 62%, ${p.life * 0.9})`; 753 ctx2d.beginPath(); 754 ctx2d.ellipse(0, 0, s * 0.55, s * 0.4, wobble, 0, Math.PI * 2); 755 ctx2d.fill(); 756 ctx2d.fillStyle = `rgba(40, 30, 50, ${p.life * 0.8})`; 757 for (var leg = 0; leg < 4; leg++) { 758 var side = leg < 2 ? -1 : 1; 759 var lx = (leg % 2) * s * 0.25 - s * 0.12; 760 ctx2d.fillRect(lx, side * s * 0.35, s * 0.12, s * 0.22); 761 } 762 ctx2d.fillStyle = `rgba(30, 20, 40, ${p.life})`; 763 ctx2d.beginPath(); 764 ctx2d.arc(s * 0.2, -s * 0.08, s * 0.08, 0, Math.PI * 2); 765 ctx2d.fill(); 766 } 767 768 function drawSeashell(ctx2d, p) { 769 ctx2d.strokeStyle = p.color; 770 ctx2d.lineWidth = 2; 771 ctx2d.beginPath(); 772 for (var t = 0; t <= 1; t += 0.05) { 773 var angle = t * Math.PI * 3; 774 var r = p.size * p.spiral * t; 775 var px = Math.cos(angle) * r; 776 var py = Math.sin(angle) * r; 777 if (t === 0) ctx2d.moveTo(px, py); 778 else ctx2d.lineTo(px, py); 779 } 780 ctx2d.stroke(); 781 } 782 783 function drawSeaweed(ctx2d, p) { 784 ctx2d.strokeStyle = p.color; 785 ctx2d.lineWidth = 3; 786 ctx2d.lineCap = 'round'; 787 var segH = p.size / p.segments; 788 var px = 0; 789 var py = 0; 790 ctx2d.beginPath(); 791 ctx2d.moveTo(px, py); 792 for (var i = 1; i <= p.segments; i++) { 793 var sway = Math.sin(p.sway + i * 0.6) * p.size * 0.15; 794 px = sway; 795 py = i * segH; 796 ctx2d.lineTo(px, py); 797 } 798 ctx2d.stroke(); 799 } 800 801 function drawPearlBubble(ctx2d, p) { 802 if (p.popped) { 803 var popSize = (1 - p.life) * p.size * 3; 804 ctx2d.strokeStyle = `rgba(255, 255, 255, ${p.life * 0.6})`; 805 ctx2d.lineWidth = 1.5; 806 for (var i = 0; i < 6; i++) { 807 var ang = (i / 6) * Math.PI * 2; 808 ctx2d.beginPath(); 809 ctx2d.moveTo(0, 0); 810 ctx2d.lineTo(Math.cos(ang) * popSize, Math.sin(ang) * popSize); 811 ctx2d.stroke(); 812 } 813 return; 814 } 815 var g = ctx2d.createRadialGradient(-p.size * 0.2, -p.size * 0.2, 0, 0, 0, p.size); 816 g.addColorStop(0, `rgba(255, 255, 255, ${p.life * 0.95})`); 817 g.addColorStop(0.5, `rgba(220, 240, 255, ${p.life * 0.5})`); 818 g.addColorStop(1, `rgba(180, 220, 255, 0)`); 819 ctx2d.fillStyle = g; 820 ctx2d.beginPath(); 821 ctx2d.arc(0, 0, p.size, 0, Math.PI * 2); 822 ctx2d.fill(); 823 } 824 825 function drawBioluminescence(ctx2d, p) { 826 var bright = 0.4 + Math.sin(p.flash) * 0.5 * p.intensity; 827 ctx2d.shadowBlur = 10 * bright; 828 ctx2d.shadowColor = p.color; 829 ctx2d.fillStyle = p.color; 830 ctx2d.globalAlpha = bright * p.life; 831 ctx2d.beginPath(); 832 ctx2d.arc(0, 0, p.size, 0, Math.PI * 2); 833 ctx2d.fill(); 834 ctx2d.globalAlpha = 1; 835 ctx2d.shadowBlur = 0; 836 } 837 838 function drawSnowball(ctx2d, p) { 839 if (p.splatted) { 840 ctx2d.fillStyle = `rgba(240, 248, 255, ${p.life * 0.7})`; 841 ctx2d.beginPath(); 842 ctx2d.ellipse(0, p.size * 0.2, p.size * 1.2, p.size * 0.45, 0, 0, Math.PI * 2); 843 ctx2d.fill(); 844 return; 845 } 846 ctx2d.fillStyle = `rgba(245, 250, 255, ${p.life * 0.95})`; 847 ctx2d.beginPath(); 848 ctx2d.arc(0, 0, p.size, 0, Math.PI * 2); 849 ctx2d.fill(); 850 } 851 852 function drawValentineCandy(ctx2d, p) { 853 var w = p.size * 1.1; 854 var h = p.size * 0.85; 855 ctx2d.fillStyle = `hsl(${p.candyHue}, 55%, 88%)`; 856 ctx2d.strokeStyle = `hsl(${p.candyHue}, 45%, 70%)`; 857 ctx2d.lineWidth = 1.5; 858 ctx2d.beginPath(); 859 ctx2d.moveTo(0, -h * 0.35); 860 ctx2d.bezierCurveTo(w * 0.5, -h * 0.5, w * 0.55, h * 0.1, 0, h * 0.45); 861 ctx2d.bezierCurveTo(-w * 0.55, h * 0.1, -w * 0.5, -h * 0.5, 0, -h * 0.35); 862 ctx2d.fill(); 863 ctx2d.stroke(); 864 ctx2d.fillStyle = `hsl(${p.candyHue}, 40%, 35%)`; 865 ctx2d.font = `bold ${Math.max(7, p.size * 0.38)}px Montserrat, sans-serif`; 866 ctx2d.textAlign = 'center'; 867 ctx2d.textBaseline = 'middle'; 868 ctx2d.fillText(p.word, 0, h * 0.05); 869 } 870 871 function drawTetromino(ctx2d, p) { 872 var cell = p.size; 873 var cells = TETRO_CELLS[p.tetroKey] || TETRO_CELLS.T; 874 var minX = 0; 875 var minY = 0; 876 var maxX = 0; 877 var maxY = 0;
878 cells.forEach(function (c) { 879 minX = Math.min(minX, c[0]); 880 minY = Math.min(minY, c[1]); 881 maxX = Math.max(maxX, c[0]); 882 maxY = Math.max(maxY, c[1]); 883 }); 884 var ox = -((minX + maxX) / 2) * cell; 885 var oy = -((minY + maxY) / 2) * cell; 886 ctx2d.fillStyle = p.color; 887 ctx2d.strokeStyle = 'rgba(0,0,0,0.25)'; 888 ctx2d.lineWidth = 0.5; 889 cells.forEach(function (c) { 890 var px = ox + c[0] * cell; 891 var py = oy + c[1] * cell; 892 ctx2d.fillRect(px - cell / 2, py - cell / 2, cell - 1, cell - 1); 893 ctx2d.strokeRect(px - cell / 2, py - cell / 2, cell - 1, cell - 1); 894 }); 895 } 896 897 function drawPokerChip(ctx2d, p) { 898 var r = p.size; 899 ctx2d.fillStyle = p.chipColor; 900 ctx2d.beginPath(); 901 ctx2d.arc(0, 0, r, 0, Math.PI * 2); 902 ctx2d.fill(); 903 ctx2d.strokeStyle = '#fff'; 904 ctx2d.lineWidth = 2; 905 ctx2d.beginPath(); 906 ctx2d.arc(0, 0, r * 0.65, 0, Math.PI * 2); 907 ctx2d.stroke(); 908 for (var i = 0; i < 8; i++) { 909 var a = (i / 8) * Math.PI * 2; 910 ctx2d.fillStyle = 'rgba(255,255,255,0.85)'; 911 ctx2d.fillRect(Math.cos(a) * r * 0.85 - 1.5, Math.sin(a) * r * 0.85 - 3, 3, 6); 912 } 913 } 914 915 function drawXpOrb(ctx2d, p) { 916 var glow = 0.6 + Math.sin(p.pulse) * 0.35; 917 var r = p.size * (0.9 + glow * 0.15); 918 ctx2d.shadowBlur = 12 * glow; 919 ctx2d.shadowColor = '#7cfc00'; 920 var g = ctx2d.createRadialGradient(0, 0, 0, 0, 0, r); 921 g.addColorStop(0, `rgba(220, 255, 180, ${p.life * glow})`); 922 g.addColorStop(0.5, `rgba(120, 220, 60, ${p.life * glow * 0.7})`); 923 g.addColorStop(1, 'rgba(60, 160, 30, 0)'); 924 ctx2d.fillStyle = g; 925 ctx2d.beginPath(); 926 ctx2d.arc(0, 0, r, 0, Math.PI * 2); 927 ctx2d.fill(); 928 ctx2d.shadowBlur = 0; 929 ctx2d.fillStyle = `rgba(255, 255, 220, ${p.life * 0.9})`; 930 ctx2d.font = `bold ${Math.max(7, p.size * 0.55)}px Montserrat, sans-serif`; 931 ctx2d.textAlign = 'center'; 932 ctx2d.textBaseline = 'middle'; 933 ctx2d.fillText('XP', 0, 0); 934 } 935 936 function drawPixelHeart(ctx2d, p) { 937 var u = p.size; 938 var grid = [ 939 [0, 1, 1, 0, 1, 1, 0], 940 [1, 1, 1, 1, 1, 1, 1], 941 [1, 1, 1, 1, 1, 1, 1], 942 [0, 1, 1, 1, 1, 1, 0], 943 [0, 0, 1, 1, 1, 0, 0], 944 [0, 0, 0, 1, 0, 0, 0] 945 ]; 946 ctx2d.fillStyle = `hsl(${p.heartHue}, 80%, 55%)`; 947 for (var row = 0; row < grid.length; row++) { 948 for (var col = 0; col < grid[row].length; col++) { 949 if (grid[row][col]) { 950 ctx2d.fillRect((col - 3) * u, (row - 2.5) * u, u - 0.5, u - 0.5); 951 } 952 } 953 } 954 } 955 956 function drawPaperClip(ctx2d, p) { 957 var s = p.size; 958 ctx2d.strokeStyle = `rgba(140, 150, 165, ${p.life * 0.95})`; 959 ctx2d.lineWidth = 2.5; 960 ctx2d.lineCap = 'round'; 961 ctx2d.beginPath(); 962 ctx2d.moveTo(-s * 0.15, s * 0.35); 963 ctx2d.lineTo(-s * 0.15, -s * 0.25); 964 ctx2d.arc(-s * 0.05, -s * 0.25, s * 0.12, Math.PI, 0, true); 965 ctx2d.lineTo(s * 0.15, s * 0.15); 966 ctx2d.arc(s * 0.05, s * 0.15, s * 0.12, 0, Math.PI, true); 967 ctx2d.lineTo(-s * 0.05, -s * 0.05); 968 ctx2d.stroke(); 969 } 970 971 function drawStickyNote(ctx2d, p) { 972 var w = p.size; 973 var h = p.size * 0.9; 974 ctx2d.fillStyle = `hsl(${p.noteHue}, 85%, 78%)`; 975 ctx2d.strokeStyle = `hsla(${p.noteHue}, 40%, 50%, 0.5)`; 976 ctx2d.lineWidth = 1; 977 ctx2d.fillRect(-w / 2, -h / 2, w, h); 978 ctx2d.strokeRect(-w / 2, -h / 2, w, h); 979 ctx2d.fillStyle = 'rgba(60, 50, 30, 0.55)'; 980 ctx2d.font = `${Math.max(8, w * 0.38)}px Montserrat, sans-serif`; 981 ctx2d.textAlign = 'center'; 982 ctx2d.textBaseline = 'middle';
983 ctx2d.fillText(p.text, 0, 0); 984 ctx2d.beginPath(); 985 ctx2d.moveTo(-w * 0.3, h * 0.15); 986 ctx2d.lineTo(-w * 0.1, h * 0.2); 987 ctx2d.stroke(); 988 } 989 990 function drawPopcorn(ctx2d, p) { 991 var bob = Math.sin(p.bob) * 2; 992 ctx2d.fillStyle = `rgba(255, 252, 245, ${p.life * 0.95})`; 993 for (var i = 0; i < 3; i++) { 994 var ox = (i - 1) * p.size * 0.35; 995 var oy = bob + (i % 2) * -p.size * 0.15; 996 ctx2d.beginPath(); 997 ctx2d.arc(ox, oy, p.size * 0.45, 0, Math.PI * 2); 998 ctx2d.fill(); 999 } 1000 ctx2d.fillStyle = `rgba(255, 235, 200, ${p.life * 0.8})`; 1001 ctx2d.beginPath(); 1002 ctx2d.arc(0, bob + p.size * 0.1, p.size * 0.25, 0, Math.PI * 2); 1003 ctx2d.fill(); 1004 } 1005 1006 function drawGummyWoim(ctx2d, p) { 1007 var segLen = p.size / p.segments; 1008 ctx2d.lineCap = 'round'; 1009 ctx2d.lineJoin = 'round'; 1010 for (var i = 0; i < p.segments; i++) { 1011 var t = i / p.segments; 1012 var hue = (p.wormHue + i * 25) % 360; 1013 ctx2d.strokeStyle = `hsla(${hue}, 85%, 55%, ${p.life * 0.9})`; 1014 ctx2d.lineWidth = 5 - t; 1015 var x1 = Math.sin(p.wiggle + i * 0.7) * p.size * 0.2; 1016 var y1 = i * segLen - p.size / 2; 1017 var x2 = Math.sin(p.wiggle + (i + 1) * 0.7) * p.size * 0.2; 1018 var y2 = (i + 1) * segLen - p.size / 2; 1019 ctx2d.beginPath(); 1020 ctx2d.moveTo(x1, y1); 1021 ctx2d.lineTo(x2, y2); 1022 ctx2d.stroke(); 1023 } 1024 } 1025 1026 function drawShootingStar(ctx2d, p) { 1027 var len = p.tailLen * p.life; 1028 ctx2d.strokeStyle = `rgba(255, 255, 240, ${p.life * 0.9})`; 1029 ctx2d.lineWidth = p.size * 0.5; 1030 ctx2d.lineCap = 'round'; 1031 ctx2d.beginPath(); 1032 ctx2d.moveTo(0, 0); 1033 ctx2d.lineTo(-Math.cos(p.angle) * len, -Math.sin(p.angle) * len); 1034 ctx2d.stroke(); 1035 ctx2d.fillStyle = `rgba(255, 255, 255, ${p.life})`; 1036 ctx2d.beginPath(); 1037 ctx2d.arc(0, 0, p.size, 0, Math.PI * 2); 1038 ctx2d.fill(); 1039 } 1040 1041 function drawSnail(ctx2d, p) { 1042 var s = p.size; 1043 if (p.slime) { 1044 ctx2d.fillStyle = `rgba(180, 220, 160, ${p.life * 0.25})`; 1045 ctx2d.beginPath(); 1046 ctx2d.ellipse(0, s * 0.35, s * 0.7, s * 0.2, 0, 0, Math.PI * 2); 1047 ctx2d.fill(); 1048 } 1049 ctx2d.fillStyle = `hsl(30, 50%, 72%)`; 1050 ctx2d.beginPath(); 1051 ctx2d.arc(-s * 0.15, s * 0.1, s * 0.55, 0, Math.PI * 2); 1052 ctx2d.fill(); 1053 ctx2d.strokeStyle = `hsla(25, 40%, 45%, ${p.life})`; 1054 ctx2d.lineWidth = 1.5; 1055 ctx2d.beginPath(); 1056 ctx2d.arc(-s * 0.15, s * 0.05, s * 0.35, 0, Math.PI * 2); 1057 ctx2d.stroke(); 1058 ctx2d.fillStyle = `hsla(120, 35%, 75%, ${p.life})`; 1059 ctx2d.beginPath(); 1060 ctx2d.ellipse(s * 0.25, s * 0.15, s * 0.35, s * 0.2, Math.sin(p.wiggle) * 0.2, 0, Math.PI * 2); 1061 ctx2d.fill(); 1062 ctx2d.fillStyle = `rgba(40,30,30,${p.life})`; 1063 ctx2d.beginPath(); 1064 ctx2d.arc(s * 0.42, s * 0.05, s * 0.06, 0, Math.PI * 2); 1065 ctx2d.fill(); 1066 } 1067 1068 function drawMoth(ctx2d, p) { 1069 var flap = Math.sin(p.flutter) * 0.35; 1070 ctx2d.fillStyle = `hsla(${p.wingHue}, 25%, 75%, ${p.life * 0.85})`; 1071 ctx2d.beginPath(); 1072 ctx2d.ellipse(-p.size * 0.35, 0, p.size * 0.4, p.size * 0.55 + flap * p.size, -0.3, 0, Math.PI * 2); 1073 ctx2d.ellipse(p.size * 0.35, 0, p.size * 0.4, p.size * 0.55 - flap * p.size, 0.3, 0, Math.PI * 2); 1074 ctx2d.fill(); 1075 ctx2d.fillStyle = `rgba(50, 40, 35, ${p.life})`; 1076 ctx2d.beginPath(); 1077 ctx2d.ellipse(0, 0, p.size * 0.12, p.size * 0.2, 0, 0, Math.PI * 2); 1078 ctx2d.fill(); 1079 } 1080 1081 function drawBat(ctx2d, p) { 1082 var flap = Math.sin(p.flap) * 0.4; 1083 ctx2d.fillStyle = `rgba(35, 30, 45, ${p.life * 0.9})`; 1084 ctx2d.beginPath(); 1085 ctx2d.moveTo(0, 0); 1086 ctx2d.quadraticCurveTo(-p.size * 0.6, -p.size * (0.5 + flap), -p.size * 0.9, p.size * 0.1); 1087 ctx2d.quadraticCurveTo(-p.size * 0.35, p.size * 0.15, 0, p.size * 0.05); 1088 ctx2d.quadraticCurveTo(p.size * 0.35, p.size * 0.15, p.size * 0.9, p.size * 0.1); 1089 ctx2d.quadraticCurveTo(p.size * 0.6, -p.size * (0.5 - flap), 0, 0); 1090 ctx2d.fill(); 1091 } 1092 1093 function drawFrog(ctx2d, p) { 1094 var s = p.size; 1095 ctx2d.fillStyle = `hsl(120, 55%, 45%)`; 1096 ctx2d.beginPath(); 1097 ctx2d.ellipse(0, 0, s * 0.55, s * 0.4, 0, 0, Math.PI * 2); 1098 ctx2d.fill(); 1099 ctx2d.fillStyle = `hsl(120, 50%, 38%)`; 1100 ctx2d.beginPath(); 1101 ctx2d.arc(-s * 0.25, -s * 0.25, s * 0.18, 0, Math.PI * 2); 1102 ctx2d.arc(s * 0.25, -s * 0.25, s * 0.18, 0, Math.PI * 2); 1103 ctx2d.fill(); 1104 ctx2d.fillStyle = `rgba(20, 40, 20, ${p.life})`; 1105 ctx2d.beginPath(); 1106 ctx2d.arc(-s * 0.25, -s * 0.25, s * 0.07, 0, Math.PI * 2); 1107 ctx2d.arc(s * 0.25, -s * 0.25, s * 0.07, 0, Math.PI * 2); 1108 ctx2d.fill(); 1109 } 1110 1111 function drawWoim(ctx2d, p) { 1112 var segLen = p.size / p.segments; 1113 ctx2d.lineCap = 'round'; 1114 ctx2d.strokeStyle = `hsla(15, 45%, 55%, ${p.life * 0.9})`; 1115 ctx2d.lineWidth = 4; 1116 ctx2d.beginPath(); 1117 for (var i = 0; i <= p.segments; i++) { 1118 var px = Math.sin(p.wiggle + i * 0.8) * p.size * 0.15; 1119 var py = i * segLen - p.size / 2; 1120 if (i === 0) ctx2d.moveTo(px, py); 1121 else ctx2d.lineTo(px, py); 1122 } 1123 ctx2d.stroke(); 1124 ctx2d.fillStyle = `rgba(40, 25, 20, ${p.life})`; 1125 ctx2d.beginPath(); 1126 var hx = Math.sin(p.wiggle) * p.size * 0.15; 1127 ctx2d.arc(hx, -p.size / 2 + 2, 3, 0, Math.PI * 2); 1128 ctx2d.fill(); 1129 } 1130 1131 function drawEqualizerBar(ctx2d, p) { 1132 var barW = p.size / (p.bars.length * 1.6); 1133 var gap = barW * 0.4; 1134 var totalW = p.bars.length * (barW + gap); 1135 var startX = -totalW / 2; 1136 for (var i = 0; i < p.bars.length; i++) { 1137 var h = p.bars[i] * p.size; 1138 var x = startX + i * (barW + gap); 1139 ctx2d.fillStyle = `hsla(${p.barHue + i * 12}, 75%, 60%, ${p.life * 0.9})`; 1140 ctx2d.fillRect(x, -h / 2, barW, h); 1141 } 1142 } 1143 1144 function drawGlitterSparkle(ctx2d, p) { 1145 var bright = 0.5 + Math.sin(p.twinkle) * 0.5; 1146 ctx2d.strokeStyle = p.color; 1147 ctx2d.lineWidth = 1.5; 1148 ctx2d.globalAlpha = bright * p.life; 1149 var s = p.size; 1150 ctx2d.beginPath(); 1151 ctx2d.moveTo(-s, 0); 1152 ctx2d.lineTo(s, 0); 1153 ctx2d.moveTo(0, -s); 1154 ctx2d.lineTo(0, s); 1155 ctx2d.moveTo(-s * 0.7, -s * 0.7); 1156 ctx2d.lineTo(s * 0.7, s * 0.7); 1157 ctx2d.moveTo(s * 0.7, -s * 0.7); 1158 ctx2d.lineTo(-s * 0.7, s * 0.7); 1159 ctx2d.stroke(); 1160 ctx2d.globalAlpha = 1; 1161 } 1162 1163 function drawMoonDust(ctx2d, p) { 1164 ctx2d.shadowBlur = 6; 1165 ctx2d.shadowColor = 'rgba(220, 230, 255, 0.6)'; 1166 ctx2d.fillStyle = `rgba(230, 235, 255, ${p.life * 0.75})`; 1167 ctx2d.beginPath(); 1168 ctx2d.arc(0, 0, p.size, 0, Math.PI * 2); 1169 ctx2d.fill(); 1170 ctx2d.shadowBlur = 0; 1171 } 1172 1173 global.PupwifeCursorTrailHandlers = { 1174 HANDLER_TYPES: HANDLER_TYPES, 1175 isHandlerType: isHandlerType, 1176 spawn: spawn, 1177 updateFrame: updateFrame, 1178 updateParticle: updateParticle, 1179 draw: draw, 1180 onTrailChange: onTrailChange 1181 }; 1182})(window);
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.