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