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