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