cursor-fx 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,755 @@
1
+ // src/core/utils.ts
2
+ function randomColor(colors) {
3
+ return colors[Math.floor(Math.random() * colors.length)];
4
+ }
5
+ function randomRange(min, max) {
6
+ return Math.random() * (max - min) + min;
7
+ }
8
+ function createCanvas(container) {
9
+ const canvas = document.createElement("canvas");
10
+ canvas.style.position = "fixed";
11
+ canvas.style.top = "0";
12
+ canvas.style.left = "0";
13
+ canvas.style.pointerEvents = "none";
14
+ canvas.style.zIndex = "9999";
15
+ canvas.width = window.innerWidth;
16
+ canvas.height = window.innerHeight;
17
+ container.appendChild(canvas);
18
+ return canvas;
19
+ }
20
+ function resizeCanvas(canvas) {
21
+ canvas.width = window.innerWidth;
22
+ canvas.height = window.innerHeight;
23
+ }
24
+ function drawStar(ctx, x, y, size, spikes = 5) {
25
+ const outerRadius = size;
26
+ const innerRadius = size * 0.4;
27
+ ctx.beginPath();
28
+ for (let i = 0; i < spikes * 2; i++) {
29
+ const angle = i * Math.PI / spikes;
30
+ const radius = i % 2 === 0 ? outerRadius : innerRadius;
31
+ const dx = Math.cos(angle) * radius;
32
+ const dy = Math.sin(angle) * radius;
33
+ if (i === 0) {
34
+ ctx.moveTo(x + dx, y + dy);
35
+ } else {
36
+ ctx.lineTo(x + dx, y + dy);
37
+ }
38
+ }
39
+ ctx.closePath();
40
+ ctx.fill();
41
+ }
42
+ function drawRectangle(ctx, x, y, width, height) {
43
+ ctx.fillRect(x - width / 2, y - height / 2, width, height);
44
+ }
45
+ function drawSnowflake(ctx, x, y, size, rotation = 0) {
46
+ ctx.save();
47
+ ctx.translate(x, y);
48
+ ctx.rotate(rotation);
49
+ ctx.shadowBlur = 4;
50
+ ctx.shadowColor = "#FFFFFF";
51
+ ctx.lineWidth = 1.8;
52
+ ctx.beginPath();
53
+ for (let i = 0; i < 6; i++) {
54
+ const angle = i * Math.PI / 3;
55
+ ctx.moveTo(0, 0);
56
+ ctx.lineTo(Math.cos(angle) * size, Math.sin(angle) * size);
57
+ }
58
+ ctx.stroke();
59
+ ctx.restore();
60
+ }
61
+ function drawBubble(ctx, x, y, size) {
62
+ const baseColor = ctx.fillStyle;
63
+ const mainGradient = ctx.createRadialGradient(x - size * 0.25, y - size * 0.25, 0, x, y, size);
64
+ mainGradient.addColorStop(0, "rgba(255, 255, 255, 0.3)");
65
+ mainGradient.addColorStop(0.3, baseColor);
66
+ mainGradient.addColorStop(0.7, baseColor);
67
+ mainGradient.addColorStop(1, "rgba(255, 255, 255, 0.1)");
68
+ ctx.beginPath();
69
+ ctx.arc(x, y, size, 0, Math.PI * 2);
70
+ ctx.fillStyle = mainGradient;
71
+ ctx.fill();
72
+ ctx.strokeStyle = "rgba(255, 255, 255, 0.3)";
73
+ ctx.lineWidth = 1;
74
+ ctx.stroke();
75
+ ctx.fillStyle = "rgba(255, 255, 255, 0.6)";
76
+ ctx.beginPath();
77
+ ctx.arc(x - size * 0.3, y - size * 0.3, size * 0.35, 0, Math.PI * 2);
78
+ ctx.fill();
79
+ ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
80
+ ctx.beginPath();
81
+ ctx.arc(x - size * 0.4, y - size * 0.4, size * 0.15, 0, Math.PI * 2);
82
+ ctx.fill();
83
+ ctx.fillStyle = "rgba(255, 255, 255, 0.2)";
84
+ ctx.beginPath();
85
+ ctx.arc(x + size * 0.4, y + size * 0.4, size * 0.2, 0, Math.PI * 2);
86
+ ctx.fill();
87
+ }
88
+ function drawCross(ctx, x, y, size) {
89
+ const halfSize = size / 2;
90
+ const thickness = size * 0.3;
91
+ ctx.fillRect(x - thickness / 2, y - halfSize, thickness, size);
92
+ ctx.fillRect(x - halfSize, y - thickness / 2, size, thickness);
93
+ }
94
+ function drawOval(ctx, x, y, width, height) {
95
+ ctx.save();
96
+ ctx.translate(x, y);
97
+ ctx.scale(width / height, 1);
98
+ ctx.beginPath();
99
+ ctx.arc(0, 0, height, 0, Math.PI * 2);
100
+ ctx.fill();
101
+ ctx.restore();
102
+ }
103
+
104
+ // src/core/engine.ts
105
+ var CursorFXEngine = class {
106
+ // Create particles on every valid move
107
+ constructor(options = {}) {
108
+ this.particles = [];
109
+ this.animationId = null;
110
+ this.effect = null;
111
+ this.maxParticles = 500;
112
+ // Cap to prevent performance issues
113
+ this.lastParticleTime = 0;
114
+ this.particleThrottle = 16;
115
+ // Create particles every 16ms (~60fps)
116
+ this.lastMouseX = 0;
117
+ this.lastMouseY = 0;
118
+ this.minMoveDistance = 0;
119
+ if (options.canvas) {
120
+ this.canvas = options.canvas;
121
+ } else {
122
+ const container = options.container || document.body;
123
+ this.canvas = createCanvas(container);
124
+ }
125
+ const ctx = this.canvas.getContext("2d");
126
+ if (!ctx) {
127
+ throw new Error("Failed to get 2D context");
128
+ }
129
+ this.ctx = ctx;
130
+ this.handleResize = this.handleResize.bind(this);
131
+ this.handleMouseMove = this.handleMouseMove.bind(this);
132
+ this.handleTouchMove = this.handleTouchMove.bind(this);
133
+ this.animate = this.animate.bind(this);
134
+ window.addEventListener("resize", this.handleResize);
135
+ }
136
+ handleResize() {
137
+ resizeCanvas(this.canvas);
138
+ }
139
+ handleMouseMove(e) {
140
+ if (!this.effect) return;
141
+ const now = Date.now();
142
+ const dx = e.clientX - this.lastMouseX;
143
+ const dy = e.clientY - this.lastMouseY;
144
+ const distance = Math.sqrt(dx * dx + dy * dy);
145
+ const throttle = this.effect.throttle ?? this.particleThrottle;
146
+ const minDist = this.effect.minMoveDistance ?? this.minMoveDistance;
147
+ if (now - this.lastParticleTime >= throttle && distance >= minDist) {
148
+ this.lastParticleTime = now;
149
+ this.lastMouseX = e.clientX;
150
+ this.lastMouseY = e.clientY;
151
+ const particles = this.effect.onMouseMove(e.clientX, e.clientY);
152
+ this.addParticles(particles);
153
+ }
154
+ }
155
+ handleTouchMove(e) {
156
+ if (!this.effect || e.touches.length === 0) return;
157
+ const now = Date.now();
158
+ const touch = e.touches[0];
159
+ const dx = touch.clientX - this.lastMouseX;
160
+ const dy = touch.clientY - this.lastMouseY;
161
+ const distance = Math.sqrt(dx * dx + dy * dy);
162
+ const throttle = this.effect.throttle ?? this.particleThrottle;
163
+ const minDist = this.effect.minMoveDistance ?? this.minMoveDistance;
164
+ if (now - this.lastParticleTime >= throttle && distance >= minDist) {
165
+ this.lastParticleTime = now;
166
+ this.lastMouseX = touch.clientX;
167
+ this.lastMouseY = touch.clientY;
168
+ const particles = this.effect.onMouseMove(touch.clientX, touch.clientY);
169
+ this.addParticles(particles);
170
+ }
171
+ }
172
+ addParticle(particle) {
173
+ if (this.particles.length < this.maxParticles) {
174
+ this.particles.push(particle);
175
+ }
176
+ }
177
+ addParticles(particles) {
178
+ const availableSlots = this.maxParticles - this.particles.length;
179
+ if (availableSlots > 0) {
180
+ this.particles.push(...particles.slice(0, availableSlots));
181
+ }
182
+ }
183
+ update() {
184
+ this.particles.forEach((particle) => particle.update());
185
+ this.particles = this.particles.filter((particle) => !particle.isDead());
186
+ }
187
+ draw() {
188
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
189
+ this.particles.forEach((particle) => particle.draw(this.ctx));
190
+ }
191
+ animate() {
192
+ this.update();
193
+ this.draw();
194
+ this.animationId = requestAnimationFrame(this.animate);
195
+ }
196
+ start(effect) {
197
+ if (this.animationId === null) {
198
+ this.effect = effect;
199
+ this.animationId = requestAnimationFrame(this.animate);
200
+ document.addEventListener("mousemove", this.handleMouseMove);
201
+ document.addEventListener("touchmove", this.handleTouchMove, { passive: true });
202
+ }
203
+ }
204
+ stop() {
205
+ if (this.animationId !== null) {
206
+ cancelAnimationFrame(this.animationId);
207
+ this.animationId = null;
208
+ document.removeEventListener("mousemove", this.handleMouseMove);
209
+ document.removeEventListener("touchmove", this.handleTouchMove);
210
+ }
211
+ }
212
+ clear() {
213
+ this.particles = [];
214
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
215
+ }
216
+ destroy() {
217
+ this.stop();
218
+ this.clear();
219
+ window.removeEventListener("resize", this.handleResize);
220
+ if (this.canvas.parentElement) {
221
+ this.canvas.parentElement.removeChild(this.canvas);
222
+ }
223
+ }
224
+ };
225
+
226
+ // src/core/particle.ts
227
+ var Particle = class {
228
+ constructor(config) {
229
+ this.x = config.x;
230
+ this.y = config.y;
231
+ this.vx = config.vx ?? (Math.random() - 0.5) * 4;
232
+ this.vy = config.vy ?? (Math.random() - 0.5) * 4;
233
+ this.size = config.size ?? 3;
234
+ this.color = config.color ?? "#ffffff";
235
+ this.maxLife = config.maxLife ?? 40;
236
+ this.life = 0;
237
+ this.gravity = config.gravity ?? 0.1;
238
+ this.opacity = 1;
239
+ this.rotation = config.rotation ?? 0;
240
+ this.rotationSpeed = config.rotationSpeed ?? 0;
241
+ this.shape = config.shape ?? "star";
242
+ this.wobbleAmplitude = config.wobbleAmplitude ?? 0;
243
+ this.wobbleSpeed = config.wobbleSpeed ?? 0;
244
+ this.wobblePhase = Math.random() * Math.PI * 2;
245
+ this.windDrift = config.windDrift ?? 0;
246
+ this.image = config.image;
247
+ this.initialScale = config.initialScale ?? 1;
248
+ this.scaleUpDuration = config.scaleUpDuration ?? 0;
249
+ this.scale = this.initialScale;
250
+ }
251
+ update() {
252
+ this.life++;
253
+ this.vy += this.gravity;
254
+ if (this.scaleUpDuration > 0 && this.life < this.scaleUpDuration) {
255
+ const progress = this.life / this.scaleUpDuration;
256
+ const easedProgress = 1 - Math.pow(1 - progress, 3);
257
+ this.scale = this.initialScale + (1 - this.initialScale) * easedProgress;
258
+ } else {
259
+ this.scale = 1;
260
+ }
261
+ if (this.wobbleAmplitude > 0) {
262
+ this.wobblePhase += this.wobbleSpeed;
263
+ this.x += this.vx + Math.sin(this.wobblePhase) * this.wobbleAmplitude;
264
+ } else {
265
+ this.x += this.vx;
266
+ }
267
+ if (this.windDrift > 0) {
268
+ const drift = Math.sin(this.life * 0.05) * this.windDrift * 0.3 + Math.sin(this.life * 0.02) * this.windDrift * 0.7;
269
+ this.x += drift;
270
+ }
271
+ this.y += this.vy;
272
+ this.rotation += this.rotationSpeed;
273
+ this.opacity = 1 - this.life / this.maxLife;
274
+ }
275
+ isDead() {
276
+ return this.life >= this.maxLife;
277
+ }
278
+ draw(ctx) {
279
+ ctx.save();
280
+ ctx.globalAlpha = this.opacity;
281
+ ctx.translate(this.x, this.y);
282
+ ctx.scale(this.scale, this.scale);
283
+ ctx.translate(-this.x, -this.y);
284
+ if (this.image && this.image.complete) {
285
+ ctx.imageSmoothingEnabled = true;
286
+ ctx.imageSmoothingQuality = "high";
287
+ const imgSize = this.size * 2.5;
288
+ ctx.translate(this.x, this.y);
289
+ if (this.rotation !== 0) {
290
+ ctx.rotate(this.rotation);
291
+ }
292
+ ctx.drawImage(
293
+ this.image,
294
+ -imgSize / 2,
295
+ -imgSize / 2,
296
+ imgSize,
297
+ imgSize
298
+ );
299
+ ctx.restore();
300
+ return;
301
+ }
302
+ ctx.fillStyle = this.color;
303
+ switch (this.shape) {
304
+ case "rectangle":
305
+ if (this.rotation !== 0) {
306
+ ctx.translate(this.x, this.y);
307
+ ctx.rotate(this.rotation);
308
+ ctx.translate(-this.x, -this.y);
309
+ }
310
+ drawRectangle(ctx, this.x, this.y, this.size, this.size * 1.5);
311
+ break;
312
+ case "circle":
313
+ ctx.shadowBlur = 15;
314
+ ctx.shadowColor = this.color;
315
+ ctx.beginPath();
316
+ ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
317
+ ctx.fill();
318
+ break;
319
+ case "snowflake":
320
+ ctx.strokeStyle = this.color;
321
+ ctx.lineWidth = 1.5;
322
+ drawSnowflake(ctx, this.x, this.y, this.size, this.rotation);
323
+ break;
324
+ case "bubble":
325
+ drawBubble(ctx, this.x, this.y, this.size);
326
+ break;
327
+ case "cross":
328
+ ctx.shadowBlur = 18;
329
+ ctx.shadowColor = this.color;
330
+ drawCross(ctx, this.x, this.y, this.size);
331
+ break;
332
+ case "oval":
333
+ ctx.shadowBlur = 12;
334
+ ctx.shadowColor = this.color;
335
+ drawOval(ctx, this.x, this.y, this.size * 2, this.size);
336
+ break;
337
+ default:
338
+ drawStar(ctx, this.x, this.y, this.size, 5);
339
+ break;
340
+ }
341
+ ctx.restore();
342
+ }
343
+ };
344
+
345
+ // src/core/imageLoader.ts
346
+ var ImageLoader = class {
347
+ static async loadImage(src) {
348
+ if (this.images.has(src)) {
349
+ return this.images.get(src);
350
+ }
351
+ if (this.loading.has(src)) {
352
+ return this.loading.get(src);
353
+ }
354
+ const loadPromise = new Promise((resolve, reject) => {
355
+ const img = new Image();
356
+ img.onload = () => {
357
+ this.images.set(src, img);
358
+ this.loading.delete(src);
359
+ resolve(img);
360
+ };
361
+ img.onerror = () => {
362
+ this.loading.delete(src);
363
+ reject(new Error(`Failed to load image: ${src}`));
364
+ };
365
+ img.src = src;
366
+ });
367
+ this.loading.set(src, loadPromise);
368
+ return loadPromise;
369
+ }
370
+ static async loadBubbles(basePath = "/bubbles") {
371
+ const bubblePaths = [
372
+ `${basePath}/soap_bubbles_1.png`,
373
+ `${basePath}/soap_bubble_2.png`,
374
+ `${basePath}/soap_bubble_3.png`
375
+ ];
376
+ try {
377
+ return await Promise.all(bubblePaths.map((path) => this.loadImage(path)));
378
+ } catch (error) {
379
+ console.warn("Failed to load some bubble images, falling back to canvas rendering", error);
380
+ return [];
381
+ }
382
+ }
383
+ static async loadSnowflakes(basePath = "/snowflakes") {
384
+ const snowflakePaths = [
385
+ // `${basePath}/snowflake.png`,
386
+ `${basePath}/snow_flake_1.png`,
387
+ `${basePath}/snow_flake_2.png`
388
+ ];
389
+ try {
390
+ return await Promise.all(snowflakePaths.map((path) => this.loadImage(path)));
391
+ } catch (error) {
392
+ console.warn("Failed to load some snowflake images, falling back to canvas rendering", error);
393
+ return [];
394
+ }
395
+ }
396
+ static getRandomBubble() {
397
+ const bubbleImages = Array.from(this.images.entries()).filter(([key]) => key.includes("bubble")).map(([, img]) => img);
398
+ if (bubbleImages.length === 0) return null;
399
+ return bubbleImages[Math.floor(Math.random() * bubbleImages.length)];
400
+ }
401
+ static getRandomSnowflake() {
402
+ const snowflakeImages = Array.from(this.images.entries()).filter(([key]) => key.includes("snow")).map(([, img]) => img);
403
+ if (snowflakeImages.length === 0) return null;
404
+ return snowflakeImages[Math.floor(Math.random() * snowflakeImages.length)];
405
+ }
406
+ static isLoaded() {
407
+ return this.images.size > 0;
408
+ }
409
+ static clear() {
410
+ this.images.clear();
411
+ this.loading.clear();
412
+ }
413
+ };
414
+ ImageLoader.images = /* @__PURE__ */ new Map();
415
+ ImageLoader.loading = /* @__PURE__ */ new Map();
416
+
417
+ // src/core/effects/fairyDust.ts
418
+ var DEFAULT_COLORS = [
419
+ "#FFD700",
420
+ // Gold
421
+ "#FFC700",
422
+ // Golden Yellow
423
+ "#FFB700",
424
+ // Amber
425
+ "#FFED4E",
426
+ // Light Gold
427
+ "#F4E04D"
428
+ // Pale Gold
429
+ ];
430
+ function createFairyDustEffect(options = {}) {
431
+ const {
432
+ colors = DEFAULT_COLORS,
433
+ particleCount = 2,
434
+ particleSize = 6,
435
+ // Increased from 4 for better visibility
436
+ gravity = -0.05,
437
+ // Slight upward float for magical feel
438
+ maxLife = 40,
439
+ velocity = 3
440
+ } = options;
441
+ return {
442
+ onMouseMove(x, y) {
443
+ const particles = [];
444
+ for (let i = 0; i < particleCount; i++) {
445
+ particles.push(
446
+ new Particle({
447
+ x: x + (Math.random() - 0.5) * 15,
448
+ y: y + (Math.random() - 0.5) * 15,
449
+ vx: (Math.random() - 0.5) * velocity,
450
+ vy: (Math.random() - 0.5) * velocity - 1,
451
+ // Slight upward bias
452
+ size: Math.random() * particleSize + 3,
453
+ // Increased from 2 (now 3-9px)
454
+ color: randomColor(colors),
455
+ maxLife: maxLife + Math.random() * 20,
456
+ gravity,
457
+ shape: "cross"
458
+ })
459
+ );
460
+ }
461
+ return particles;
462
+ }
463
+ };
464
+ }
465
+
466
+ // src/core/effects/sparkle.ts
467
+ var DEFAULT_COLORS2 = [
468
+ "#FFD700",
469
+ // Gold
470
+ "#FF69B4",
471
+ // Hot Pink
472
+ "#00CED1",
473
+ // Dark Turquoise
474
+ "#9370DB"
475
+ // Medium Purple
476
+ ];
477
+ function createSparkleEffect(options = {}) {
478
+ const {
479
+ colors = DEFAULT_COLORS2,
480
+ particleCount = 1,
481
+ // Reduced for better performance
482
+ particleSize = 6,
483
+ // Increased from 3 for better visibility
484
+ gravity = 0.1,
485
+ maxLife = 20,
486
+ // Further reduced for faster cleanup
487
+ velocity = 4
488
+ } = options;
489
+ return {
490
+ onMouseMove(x, y) {
491
+ const particles = [];
492
+ for (let i = 0; i < particleCount; i++) {
493
+ particles.push(
494
+ new Particle({
495
+ x: x + (Math.random() - 0.5) * 10,
496
+ y: y + (Math.random() - 0.5) * 10,
497
+ vx: (Math.random() - 0.5) * velocity,
498
+ vy: (Math.random() - 0.5) * velocity,
499
+ size: Math.random() * particleSize + 3,
500
+ // Increased from 2 (now 3-9px)
501
+ color: randomColor(colors),
502
+ maxLife: maxLife + Math.random() * 10,
503
+ // Reduced random variation
504
+ gravity
505
+ })
506
+ );
507
+ }
508
+ return particles;
509
+ }
510
+ };
511
+ }
512
+
513
+ // src/core/effects/confetti.ts
514
+ var DEFAULT_COLORS3 = [
515
+ "#FF6B6B",
516
+ // Red
517
+ "#4ECDC4",
518
+ // Turquoise
519
+ "#FFE66D",
520
+ // Yellow
521
+ "#95E1D3",
522
+ // Mint
523
+ "#F38181",
524
+ // Pink
525
+ "#AA96DA",
526
+ // Purple
527
+ "#FCBAD3",
528
+ // Light Pink
529
+ "#A8D8EA"
530
+ // Sky Blue
531
+ ];
532
+ function createConfettiEffect(options = {}) {
533
+ const {
534
+ colors = DEFAULT_COLORS3,
535
+ particleCount = 3,
536
+ particleSize = 4,
537
+ gravity = 0.3,
538
+ // Stronger gravity for falling effect
539
+ maxLife = 60,
540
+ // Longer lifetime for confetti
541
+ velocity = 6
542
+ } = options;
543
+ return {
544
+ onMouseMove(x, y) {
545
+ const particles = [];
546
+ for (let i = 0; i < particleCount; i++) {
547
+ particles.push(
548
+ new Particle({
549
+ x: x + (Math.random() - 0.5) * 20,
550
+ y: y + (Math.random() - 0.5) * 20,
551
+ vx: (Math.random() - 0.5) * velocity,
552
+ vy: (Math.random() - 1) * velocity * 0.5,
553
+ // Bias upward initially
554
+ size: Math.random() * particleSize + 3,
555
+ color: randomColor(colors),
556
+ maxLife: maxLife + Math.random() * 20,
557
+ gravity,
558
+ rotation: Math.random() * Math.PI * 2,
559
+ // Random initial rotation
560
+ rotationSpeed: (Math.random() - 0.5) * 0.2,
561
+ // Rotation speed
562
+ shape: "rectangle"
563
+ })
564
+ );
565
+ }
566
+ return particles;
567
+ }
568
+ };
569
+ }
570
+
571
+ // src/core/effects/retroCRT.ts
572
+ var DEFAULT_COLORS4 = [
573
+ "#00FF00",
574
+ // Classic phosphor green
575
+ "#33FF33",
576
+ // Bright phosphor green
577
+ "#00CC00",
578
+ // Medium phosphor green
579
+ "#00DD00"
580
+ // Light phosphor green
581
+ ];
582
+ function createRetroCRTEffect(options = {}) {
583
+ const {
584
+ colors = DEFAULT_COLORS4,
585
+ particleCount = 3,
586
+ particleSize = 4,
587
+ // Slightly larger for better visibility
588
+ gravity = 0,
589
+ // No gravity - phosphor glows in place
590
+ maxLife = 60,
591
+ // Longer persistence like real phosphor
592
+ velocity = 1
593
+ // Very slow movement for authentic glow
594
+ } = options;
595
+ return {
596
+ onMouseMove(x, y) {
597
+ const particles = [];
598
+ for (let i = 0; i < particleCount; i++) {
599
+ particles.push(
600
+ new Particle({
601
+ x: x + (Math.random() - 0.5) * 8,
602
+ y: y + (Math.random() - 0.5) * 8,
603
+ vx: (Math.random() - 0.5) * velocity,
604
+ vy: (Math.random() - 0.5) * velocity,
605
+ size: Math.random() * particleSize + 2,
606
+ color: randomColor(colors),
607
+ maxLife: maxLife + Math.random() * 15,
608
+ gravity,
609
+ shape: "circle"
610
+ // Back to circles with strong glow
611
+ })
612
+ );
613
+ }
614
+ return particles;
615
+ }
616
+ };
617
+ }
618
+
619
+ // src/core/effects/snow.ts
620
+ var DEFAULT_COLORS5 = [
621
+ "#FFFFFF",
622
+ // Pure white
623
+ "#F0F8FF",
624
+ // Alice blue
625
+ "#E6F3FF",
626
+ // Light blue white
627
+ "#F5F5F5"
628
+ // White smoke
629
+ ];
630
+ function createSnowEffect(options = {}) {
631
+ const {
632
+ colors = DEFAULT_COLORS5,
633
+ particleCount = 1,
634
+ // One snowflake at a time
635
+ particleSize = 7,
636
+ // Bigger for better visibility
637
+ gravity = 0.12,
638
+ // Faster falling
639
+ maxLife = 150,
640
+ // Shorter lifetime for faster fall
641
+ velocity = 0.4,
642
+ // Slightly more drift
643
+ throttle = 120,
644
+ // Spawn every 120ms - less frequent
645
+ minMoveDistance = 12
646
+ // Only spawn when cursor moves 12px
647
+ } = options;
648
+ return {
649
+ throttle,
650
+ minMoveDistance,
651
+ onMouseMove(x, y) {
652
+ const particles = [];
653
+ for (let i = 0; i < particleCount; i++) {
654
+ const snowflakeImage = ImageLoader.getRandomSnowflake() ;
655
+ particles.push(
656
+ new Particle({
657
+ x: x + (Math.random() - 0.5) * 30,
658
+ y: y + (Math.random() - 0.5) * 15,
659
+ vx: (Math.random() - 0.5) * velocity,
660
+ vy: Math.random() * 0.2 + 0.1,
661
+ // Slightly faster downward initial velocity
662
+ size: Math.random() * particleSize + 3,
663
+ // Variable sizes (3-10px for canvas, base for images)
664
+ color: randomColor(colors),
665
+ maxLife: maxLife + Math.random() * 60,
666
+ gravity,
667
+ rotation: Math.random() * Math.PI * 2,
668
+ // Random initial rotation
669
+ rotationSpeed: (Math.random() - 0.5) * 0.03,
670
+ // More visible rotation
671
+ shape: "snowflake",
672
+ windDrift: 0.8,
673
+ // Gentle wind drift/sway
674
+ image: snowflakeImage || void 0
675
+ // Use image if flag is true and images loaded
676
+ })
677
+ );
678
+ }
679
+ return particles;
680
+ }
681
+ };
682
+ }
683
+
684
+ // src/core/effects/bubble.ts
685
+ var DEFAULT_COLORS6 = [
686
+ "rgba(173, 216, 230, 0.4)",
687
+ // Light blue - transparent
688
+ "rgba(135, 206, 235, 0.4)",
689
+ // Sky blue - transparent
690
+ "rgba(176, 224, 230, 0.4)",
691
+ // Powder blue - transparent
692
+ "rgba(175, 238, 238, 0.4)",
693
+ // Pale turquoise - transparent
694
+ "rgba(224, 255, 255, 0.4)"
695
+ // Light cyan - transparent
696
+ ];
697
+ function createBubbleEffect(options = {}) {
698
+ const {
699
+ colors = DEFAULT_COLORS6,
700
+ particleCount = 1,
701
+ // Spawn one bubble at a time
702
+ gravity = -0.02,
703
+ // Gentle upward buoyancy
704
+ maxLife = 180,
705
+ // Longer lifetime for slow rise
706
+ velocity = 0.2,
707
+ // Minimal base horizontal drift
708
+ throttle = 150,
709
+ // Spawn every 150ms - much less frequent
710
+ minMoveDistance = 15
711
+ // Only spawn when cursor moves 15px
712
+ } = options;
713
+ return {
714
+ throttle,
715
+ minMoveDistance,
716
+ onMouseMove(x, y) {
717
+ const particles = [];
718
+ for (let i = 0; i < particleCount; i++) {
719
+ const bubbleImage = ImageLoader.getRandomBubble();
720
+ const baseSize = 15 + Math.random() * 30;
721
+ particles.push(
722
+ new Particle({
723
+ x: x + (Math.random() - 0.5) * 10,
724
+ // Spawn closer to pointer
725
+ y: y + (Math.random() - 0.5) * 10,
726
+ vx: (Math.random() - 0.5) * velocity,
727
+ vy: -Math.random() * 0.15 - 0.1,
728
+ // Very slow, consistent upward
729
+ size: baseSize,
730
+ // Wide size variation (15-45)
731
+ color: randomColor(colors),
732
+ maxLife: maxLife + Math.random() * 60,
733
+ gravity,
734
+ shape: "bubble",
735
+ wobbleAmplitude: 0.3,
736
+ // Gentle horizontal wobble
737
+ wobbleSpeed: 0.05,
738
+ // Slow wobble oscillation
739
+ image: bubbleImage || void 0,
740
+ // Use image if loaded, fallback to canvas
741
+ initialScale: 0.3,
742
+ // Start at 30% size for pop-up effect
743
+ scaleUpDuration: 15
744
+ // Grow to full size over 15 frames (~250ms)
745
+ })
746
+ );
747
+ }
748
+ return particles;
749
+ }
750
+ };
751
+ }
752
+
753
+ export { CursorFXEngine, ImageLoader, Particle, createBubbleEffect, createCanvas, createConfettiEffect, createFairyDustEffect, createRetroCRTEffect, createSnowEffect, createSparkleEffect, drawBubble, drawCross, drawOval, drawRectangle, drawSnowflake, drawStar, randomColor, randomRange, resizeCanvas };
754
+ //# sourceMappingURL=chunk-5ZV2U5LA.mjs.map
755
+ //# sourceMappingURL=chunk-5ZV2U5LA.mjs.map