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.
- package/LICENSE +21 -0
- package/README.md +475 -0
- package/dist/bubbles/soap_bubble_2.png +0 -0
- package/dist/bubbles/soap_bubble_3.png +0 -0
- package/dist/bubbles/soap_bubbles_1.png +0 -0
- package/dist/chunk-5ZV2U5LA.mjs +755 -0
- package/dist/chunk-5ZV2U5LA.mjs.map +1 -0
- package/dist/core/index.d.mts +40 -0
- package/dist/core/index.d.ts +40 -0
- package/dist/core/index.js +775 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/index.mjs +3 -0
- package/dist/core/index.mjs.map +1 -0
- package/dist/engine-BiVjsHvN.d.ts +30 -0
- package/dist/engine-DtYS3_cn.d.mts +30 -0
- package/dist/particle-CimoPApW.d.mts +69 -0
- package/dist/particle-CimoPApW.d.ts +69 -0
- package/dist/react/index.d.mts +18 -0
- package/dist/react/index.d.ts +18 -0
- package/dist/react/index.js +804 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +50 -0
- package/dist/react/index.mjs.map +1 -0
- package/dist/snowflakes/snow_flake_1.png +0 -0
- package/dist/snowflakes/snow_flake_2.png +0 -0
- package/dist/snowflakes/snowflake.png +0 -0
- package/dist/vanilla/index.d.mts +18 -0
- package/dist/vanilla/index.d.ts +18 -0
- package/dist/vanilla/index.js +777 -0
- package/dist/vanilla/index.js.map +1 -0
- package/dist/vanilla/index.mjs +28 -0
- package/dist/vanilla/index.mjs.map +1 -0
- package/package.json +80 -0
|
@@ -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
|