@xdy-npm/react-particle-backgrounds 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/dist/index.mjs ADDED
@@ -0,0 +1,1081 @@
1
+ import { createContext, useState, useEffect, useCallback, useContext, useRef, useMemo } from 'react';
2
+ import Particles, { initParticlesEngine } from '@tsparticles/react';
3
+ import { loadSlim } from '@tsparticles/slim';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/components/ParticlesBackground.tsx
7
+
8
+ // src/themes/base.ts
9
+ var baseConfig = {
10
+ fullScreen: {
11
+ enable: true,
12
+ zIndex: 0
13
+ },
14
+ background: {
15
+ color: { value: "transparent" }
16
+ },
17
+ fpsLimit: 120,
18
+ detectRetina: true
19
+ };
20
+ var DEFAULT_COLORS = [
21
+ "#ffb3d9",
22
+ "#f43f5e",
23
+ "#a78bfa",
24
+ "#3b82f6",
25
+ "#10b981",
26
+ "#f59e0b",
27
+ "#ff6b6b",
28
+ "#8b5cf6",
29
+ "#06b6d4",
30
+ "#fb7185"
31
+ ];
32
+
33
+ // src/themes/starline.ts
34
+ var starlineTheme = {
35
+ id: "starline",
36
+ name: "Star Links",
37
+ icon: "\u2728",
38
+ description: "Classic particle linking effect",
39
+ backgroundGradient: "linear-gradient(180deg, #0f172a 0%, #1e293b 50%, #334155 100%)",
40
+ options: (isDark) => {
41
+ const colors = DEFAULT_COLORS;
42
+ return {
43
+ ...baseConfig,
44
+ interactivity: {
45
+ detectsOn: "window",
46
+ events: {
47
+ onClick: { enable: true, mode: "push" },
48
+ onHover: { enable: true, mode: "grab" },
49
+ resize: { enable: true }
50
+ },
51
+ modes: {
52
+ push: { quantity: 6 },
53
+ grab: {
54
+ distance: 200,
55
+ links: { opacity: 1, color: colors }
56
+ }
57
+ }
58
+ },
59
+ particles: {
60
+ color: { value: colors },
61
+ links: {
62
+ color: colors,
63
+ distance: 150,
64
+ enable: true,
65
+ opacity: 0.5,
66
+ width: 1
67
+ },
68
+ move: {
69
+ direction: "none",
70
+ enable: true,
71
+ outModes: { default: "out" },
72
+ random: false,
73
+ speed: 2,
74
+ straight: false
75
+ },
76
+ number: {
77
+ density: { enable: true, width: 1920, height: 1080 },
78
+ value: 100
79
+ },
80
+ opacity: { value: 0.7 },
81
+ shape: { type: "circle" },
82
+ size: { value: { min: 2, max: 6 } },
83
+ shadow: {
84
+ blur: 8,
85
+ color: { value: colors },
86
+ enable: true,
87
+ offset: { x: 0, y: 0 }
88
+ }
89
+ }
90
+ };
91
+ }
92
+ };
93
+
94
+ // src/themes/snow.ts
95
+ var snowTheme = {
96
+ id: "snow",
97
+ name: "Snowfall",
98
+ icon: "\u2744\uFE0F",
99
+ description: "Romantic falling snowflakes",
100
+ backgroundGradient: "linear-gradient(180deg, #1e3a5f 0%, #2d4a6b 50%, #3d5a7b 100%)",
101
+ options: (isDark) => ({
102
+ ...baseConfig,
103
+ interactivity: {
104
+ detectsOn: "window",
105
+ events: {
106
+ onHover: { enable: true, mode: "repulse" },
107
+ resize: { enable: true }
108
+ },
109
+ modes: {
110
+ repulse: { distance: 100, duration: 0.4 }
111
+ }
112
+ },
113
+ particles: {
114
+ color: { value: isDark ? "#ffffff" : "#87CEEB" },
115
+ move: {
116
+ direction: "bottom",
117
+ enable: true,
118
+ outModes: { default: "out" },
119
+ speed: { min: 1, max: 3 },
120
+ straight: false,
121
+ drift: { min: -0.5, max: 0.5 }
122
+ },
123
+ number: {
124
+ density: { enable: true, width: 1920, height: 1080 },
125
+ value: 80
126
+ },
127
+ opacity: {
128
+ value: { min: 0.3, max: 0.8 },
129
+ animation: { enable: true, speed: 1, minimumValue: 0.3, sync: false }
130
+ },
131
+ shape: { type: "circle" },
132
+ size: { value: { min: 2, max: 6 } },
133
+ wobble: {
134
+ enable: true,
135
+ distance: 10,
136
+ speed: { min: -5, max: 5 }
137
+ },
138
+ shadow: {
139
+ blur: 5,
140
+ color: { value: isDark ? "#ffffff" : "#87CEEB" },
141
+ enable: true,
142
+ offset: { x: 0, y: 0 }
143
+ }
144
+ }
145
+ })
146
+ };
147
+
148
+ // src/themes/bubble.ts
149
+ var bubbleTheme = {
150
+ id: "bubble",
151
+ name: "Bubbles",
152
+ icon: "\u{1FAE7}",
153
+ description: "Dreamy rising bubbles",
154
+ backgroundGradient: "linear-gradient(180deg, #0a2647 0%, #144272 50%, #205295 100%)",
155
+ options: (isDark) => ({
156
+ ...baseConfig,
157
+ interactivity: {
158
+ detectsOn: "window",
159
+ events: {
160
+ onClick: { enable: true, mode: "pop" },
161
+ onHover: { enable: true, mode: "bubble" },
162
+ resize: { enable: true }
163
+ },
164
+ modes: {
165
+ bubble: { distance: 200, size: 15, duration: 2, opacity: 0.8 },
166
+ pop: {}
167
+ }
168
+ },
169
+ particles: {
170
+ color: {
171
+ value: isDark ? ["#00d9ff", "#00ff9d", "#ff00e6", "#ffee00"] : ["#ffb3d9", "#ff91c7", "#ffc0e5", "#ffd6e8"]
172
+ },
173
+ move: {
174
+ direction: "top",
175
+ enable: true,
176
+ outModes: { default: "out" },
177
+ speed: { min: 1, max: 2 },
178
+ straight: false
179
+ },
180
+ number: {
181
+ density: { enable: true, width: 1920, height: 1080 },
182
+ value: 50
183
+ },
184
+ opacity: {
185
+ value: { min: 0.2, max: 0.6 },
186
+ animation: { enable: true, speed: 0.5, minimumValue: 0.1, sync: false }
187
+ },
188
+ shape: { type: "circle" },
189
+ size: {
190
+ value: { min: 5, max: 15 },
191
+ animation: { enable: true, speed: 3, minimumValue: 3, sync: false }
192
+ },
193
+ stroke: {
194
+ width: 1,
195
+ color: { value: isDark ? "rgba(255,255,255,0.3)" : "rgba(0,0,0,0.1)" }
196
+ },
197
+ shadow: {
198
+ blur: 10,
199
+ color: { value: isDark ? "#ffb3d9" : "#ff91c7" },
200
+ enable: true,
201
+ offset: { x: 0, y: 0 }
202
+ }
203
+ }
204
+ })
205
+ };
206
+
207
+ // src/themes/stars.ts
208
+ var starsTheme = {
209
+ id: "stars",
210
+ name: "Twinkling Stars",
211
+ icon: "\u2B50",
212
+ description: "Sparkling starry sky",
213
+ backgroundGradient: "linear-gradient(180deg, #000000 0%, #1a1a2e 50%, #16213e 100%)",
214
+ options: (isDark) => ({
215
+ ...baseConfig,
216
+ interactivity: {
217
+ detectsOn: "window",
218
+ events: {
219
+ onClick: { enable: true, mode: "push" },
220
+ onHover: { enable: true, mode: "connect" },
221
+ resize: { enable: true }
222
+ },
223
+ modes: {
224
+ push: { quantity: 3 },
225
+ connect: { distance: 100, links: { opacity: 0.3 }, radius: 150 }
226
+ }
227
+ },
228
+ particles: {
229
+ color: {
230
+ value: isDark ? ["#ffffff", "#ffffd4", "#ffecd2", "#d4f1ff"] : ["#ffd700", "#ffb347", "#ff6b6b", "#4ecdc4"]
231
+ },
232
+ move: {
233
+ direction: "none",
234
+ enable: true,
235
+ outModes: { default: "out" },
236
+ random: true,
237
+ speed: 0.5,
238
+ straight: false
239
+ },
240
+ number: {
241
+ density: { enable: true, width: 1920, height: 1080 },
242
+ value: 120
243
+ },
244
+ opacity: {
245
+ value: { min: 0.2, max: 1 },
246
+ animation: {
247
+ enable: true,
248
+ speed: 1,
249
+ minimumValue: 0.1,
250
+ sync: false
251
+ }
252
+ },
253
+ shape: { type: "star", options: { star: { sides: 5 } } },
254
+ size: { value: { min: 1, max: 4 } },
255
+ twinkle: {
256
+ lines: { enable: false },
257
+ particles: {
258
+ enable: true,
259
+ frequency: 0.05,
260
+ opacity: 1,
261
+ color: { value: isDark ? "#ffffff" : "#ffd700" }
262
+ }
263
+ },
264
+ shadow: {
265
+ blur: 6,
266
+ color: { value: isDark ? "#ffffff" : "#ffd700" },
267
+ enable: true,
268
+ offset: { x: 0, y: 0 }
269
+ }
270
+ }
271
+ })
272
+ };
273
+
274
+ // src/themes/firefly.ts
275
+ var fireflyTheme = {
276
+ id: "firefly",
277
+ name: "Fireflies",
278
+ icon: "\u{1FAB2}",
279
+ description: "Warm glowing firefly effect",
280
+ backgroundGradient: "linear-gradient(180deg, #1a1a1a 0%, #2d2d2d 50%, #1a1a1a 100%)",
281
+ options: (isDark) => ({
282
+ ...baseConfig,
283
+ interactivity: {
284
+ detectsOn: "window",
285
+ events: {
286
+ onHover: { enable: true, mode: "slow" },
287
+ resize: { enable: true }
288
+ },
289
+ modes: {
290
+ slow: { factor: 3, radius: 200 }
291
+ }
292
+ },
293
+ particles: {
294
+ color: {
295
+ value: isDark ? ["#ffff00", "#adff2f", "#7fff00", "#00ff7f"] : ["#ffc107", "#ff9800", "#ff5722", "#4caf50"]
296
+ },
297
+ move: {
298
+ direction: "none",
299
+ enable: true,
300
+ outModes: { default: "bounce" },
301
+ random: true,
302
+ speed: 1,
303
+ straight: false,
304
+ trail: {
305
+ enable: true,
306
+ length: 5,
307
+ fill: { color: "transparent" }
308
+ }
309
+ },
310
+ number: {
311
+ density: { enable: true, width: 1920, height: 1080 },
312
+ value: 40
313
+ },
314
+ opacity: {
315
+ value: { min: 0.3, max: 1 },
316
+ animation: {
317
+ enable: true,
318
+ speed: 2,
319
+ minimumValue: 0.1,
320
+ sync: false
321
+ }
322
+ },
323
+ shape: { type: "circle" },
324
+ size: { value: { min: 2, max: 5 } },
325
+ shadow: {
326
+ blur: 15,
327
+ color: { value: isDark ? "#adff2f" : "#ffc107" },
328
+ enable: true,
329
+ offset: { x: 0, y: 0 }
330
+ }
331
+ }
332
+ })
333
+ };
334
+
335
+ // src/themes/geometry.ts
336
+ var geometryTheme = {
337
+ id: "geometry",
338
+ name: "Geometry",
339
+ icon: "\u{1F537}",
340
+ description: "Floating abstract geometric shapes",
341
+ backgroundGradient: "linear-gradient(180deg, #0f0c29 0%, #302b63 50%, #24243e 100%)",
342
+ options: (isDark) => ({
343
+ ...baseConfig,
344
+ interactivity: {
345
+ detectsOn: "window",
346
+ events: {
347
+ onClick: { enable: true, mode: "push" },
348
+ onHover: { enable: true, mode: "repulse" },
349
+ resize: { enable: true }
350
+ },
351
+ modes: {
352
+ push: { quantity: 2 },
353
+ repulse: { distance: 150, duration: 0.4 }
354
+ }
355
+ },
356
+ particles: {
357
+ color: {
358
+ value: ["#ffb3d9", "#ff91c7", "#ffc0e5", "#ffd6e8"]
359
+ },
360
+ move: {
361
+ direction: "none",
362
+ enable: true,
363
+ outModes: { default: "bounce" },
364
+ random: false,
365
+ speed: 1.5,
366
+ straight: false
367
+ },
368
+ number: {
369
+ density: { enable: true, width: 1920, height: 1080 },
370
+ value: 30
371
+ },
372
+ opacity: {
373
+ value: { min: 0.3, max: 0.7 }
374
+ },
375
+ rotate: {
376
+ value: { min: 0, max: 360 },
377
+ direction: "random",
378
+ animation: { enable: true, speed: 5 }
379
+ },
380
+ shape: {
381
+ type: ["triangle", "square", "polygon"],
382
+ options: {
383
+ polygon: { sides: 6 }
384
+ }
385
+ },
386
+ size: { value: { min: 10, max: 25 } },
387
+ stroke: {
388
+ width: 1,
389
+ color: { value: isDark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.2)" }
390
+ },
391
+ shadow: {
392
+ blur: 10,
393
+ color: { value: isDark ? "#ffb3d9" : "#ff91c7" },
394
+ enable: true,
395
+ offset: { x: 2, y: 2 }
396
+ }
397
+ }
398
+ })
399
+ };
400
+
401
+ // src/themes/wave.ts
402
+ var waveTheme = {
403
+ id: "wave",
404
+ name: "Particle Ocean",
405
+ icon: "\u{1F30A}",
406
+ description: "3D particle wave effect (requires three.js)",
407
+ isThreeJS: true,
408
+ backgroundGradient: "linear-gradient(180deg, #000000 0%, #0a1628 50%, #0d1f3c 100%)",
409
+ options: () => ({
410
+ ...baseConfig,
411
+ particles: {
412
+ number: { value: 0 }
413
+ }
414
+ })
415
+ };
416
+
417
+ // src/themes/index.ts
418
+ var particleThemes = [
419
+ starlineTheme,
420
+ snowTheme,
421
+ bubbleTheme,
422
+ starsTheme,
423
+ fireflyTheme,
424
+ geometryTheme,
425
+ waveTheme
426
+ ];
427
+ var getThemeById = (id) => {
428
+ return particleThemes.find((theme) => theme.id === id) || starlineTheme;
429
+ };
430
+ var DEFAULT_THEME_ID = "starline";
431
+ var ParticleContext = createContext(null);
432
+ var STORAGE_KEY = "rpb-theme-id";
433
+ var ParticleProvider = ({
434
+ defaultTheme = DEFAULT_THEME_ID,
435
+ isDark: isDarkProp = true,
436
+ persist = true,
437
+ children
438
+ }) => {
439
+ const [themeId, setThemeId] = useState(() => {
440
+ if (persist && typeof window !== "undefined") {
441
+ return localStorage.getItem(STORAGE_KEY) || defaultTheme;
442
+ }
443
+ return defaultTheme;
444
+ });
445
+ const [isDark, setDark] = useState(isDarkProp);
446
+ useEffect(() => {
447
+ setDark(isDarkProp);
448
+ }, [isDarkProp]);
449
+ const setTheme = useCallback((id) => {
450
+ setThemeId(id);
451
+ if (persist && typeof window !== "undefined") {
452
+ localStorage.setItem(STORAGE_KEY, id);
453
+ }
454
+ }, [persist]);
455
+ return /* @__PURE__ */ jsx(ParticleContext.Provider, { value: { themeId, isDark, setTheme, setDark }, children });
456
+ };
457
+ var useParticleTheme = () => {
458
+ const ctx = useContext(ParticleContext);
459
+ if (!ctx) {
460
+ throw new Error("useParticleTheme must be used within a <ParticleProvider>");
461
+ }
462
+ return ctx;
463
+ };
464
+ var useParticleThemeOptional = () => {
465
+ return useContext(ParticleContext);
466
+ };
467
+ var ParticleWave = ({
468
+ background = "linear-gradient(180deg, #000000 0%, #0a1628 50%, #0d1f3c 100%)",
469
+ className,
470
+ style
471
+ }) => {
472
+ const containerRef = useRef(null);
473
+ const animationRef = useRef();
474
+ const hasThree = useRef(true);
475
+ useEffect(() => {
476
+ let THREE;
477
+ try {
478
+ THREE = globalThis.require ? globalThis.require("three") : (() => {
479
+ throw new Error("no require");
480
+ })();
481
+ } catch (e) {
482
+ hasThree.current = false;
483
+ console.warn('[react-particle-backgrounds] "three" is not installed. The wave theme requires it as a peer dependency.');
484
+ return;
485
+ }
486
+ if (!containerRef.current) return;
487
+ const container = containerRef.current;
488
+ const width = container.clientWidth;
489
+ const height = container.clientHeight;
490
+ const scene = new THREE.Scene();
491
+ scene.background = null;
492
+ const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1e3);
493
+ camera.position.set(0, 50, 100);
494
+ camera.lookAt(0, 0, 0);
495
+ const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
496
+ renderer.setSize(width, height);
497
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
498
+ container.appendChild(renderer.domElement);
499
+ const particleCount = 15e3;
500
+ const waveWidth = 200;
501
+ const waveDepth = 100;
502
+ const geometry = new THREE.BufferGeometry();
503
+ const positions = new Float32Array(particleCount * 3);
504
+ const colors = new Float32Array(particleCount * 3);
505
+ const sizes = new Float32Array(particleCount);
506
+ for (let i = 0; i < particleCount; i++) {
507
+ const i3 = i * 3;
508
+ positions[i3] = (Math.random() - 0.5) * waveWidth;
509
+ positions[i3 + 1] = 0;
510
+ positions[i3 + 2] = (Math.random() - 0.5) * waveDepth;
511
+ const colorChoice = Math.random();
512
+ if (colorChoice < 0.5) {
513
+ colors[i3] = 0;
514
+ colors[i3 + 1] = 0.8 + Math.random() * 0.2;
515
+ colors[i3 + 2] = 1;
516
+ } else if (colorChoice < 0.8) {
517
+ colors[i3] = 0.1;
518
+ colors[i3 + 1] = 0.3 + Math.random() * 0.3;
519
+ colors[i3 + 2] = 0.9 + Math.random() * 0.1;
520
+ } else {
521
+ colors[i3] = 0.8 + Math.random() * 0.2;
522
+ colors[i3 + 1] = 0.9 + Math.random() * 0.1;
523
+ colors[i3 + 2] = 1;
524
+ }
525
+ sizes[i] = Math.random() * 2 + 0.5;
526
+ }
527
+ geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
528
+ geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
529
+ geometry.setAttribute("size", new THREE.BufferAttribute(sizes, 1));
530
+ const vertexShader = `
531
+ attribute float size;
532
+ varying vec3 vColor;
533
+ void main() {
534
+ vColor = color;
535
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
536
+ gl_PointSize = size * (300.0 / -mvPosition.z);
537
+ gl_Position = projectionMatrix * mvPosition;
538
+ }
539
+ `;
540
+ const fragmentShader = `
541
+ varying vec3 vColor;
542
+ void main() {
543
+ float dist = length(gl_PointCoord - vec2(0.5));
544
+ if (dist > 0.5) discard;
545
+ float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
546
+ float glow = exp(-dist * 3.0);
547
+ vec3 finalColor = vColor + glow * 0.5;
548
+ gl_FragColor = vec4(finalColor, alpha * 0.8);
549
+ }
550
+ `;
551
+ const material = new THREE.ShaderMaterial({
552
+ vertexShader,
553
+ fragmentShader,
554
+ transparent: true,
555
+ vertexColors: true,
556
+ blending: THREE.AdditiveBlending,
557
+ depthWrite: false
558
+ });
559
+ const particles = new THREE.Points(geometry, material);
560
+ scene.add(particles);
561
+ const bokehCount = 100;
562
+ const bokehGeometry = new THREE.BufferGeometry();
563
+ const bokehPositions = new Float32Array(bokehCount * 3);
564
+ const bokehColors = new Float32Array(bokehCount * 3);
565
+ const bokehSizes = new Float32Array(bokehCount);
566
+ for (let i = 0; i < bokehCount; i++) {
567
+ const i3 = i * 3;
568
+ bokehPositions[i3] = (Math.random() - 0.5) * waveWidth * 1.5;
569
+ bokehPositions[i3 + 1] = Math.random() * -30 - 10;
570
+ bokehPositions[i3 + 2] = (Math.random() - 0.5) * waveDepth * 2;
571
+ bokehColors[i3] = 0.2;
572
+ bokehColors[i3 + 1] = 0.5 + Math.random() * 0.3;
573
+ bokehColors[i3 + 2] = 1;
574
+ bokehSizes[i] = Math.random() * 15 + 8;
575
+ }
576
+ bokehGeometry.setAttribute("position", new THREE.BufferAttribute(bokehPositions, 3));
577
+ bokehGeometry.setAttribute("color", new THREE.BufferAttribute(bokehColors, 3));
578
+ bokehGeometry.setAttribute("size", new THREE.BufferAttribute(bokehSizes, 1));
579
+ const bokehMaterial = new THREE.ShaderMaterial({
580
+ vertexShader,
581
+ fragmentShader: `
582
+ varying vec3 vColor;
583
+ void main() {
584
+ float dist = length(gl_PointCoord - vec2(0.5));
585
+ if (dist > 0.5) discard;
586
+ float alpha = 1.0 - smoothstep(0.2, 0.5, dist);
587
+ gl_FragColor = vec4(vColor, alpha * 0.3);
588
+ }
589
+ `,
590
+ transparent: true,
591
+ vertexColors: true,
592
+ blending: THREE.AdditiveBlending,
593
+ depthWrite: false
594
+ });
595
+ const bokehParticles = new THREE.Points(bokehGeometry, bokehMaterial);
596
+ scene.add(bokehParticles);
597
+ const dropCount = 150;
598
+ const trailLength = 12;
599
+ const rainDrops = [];
600
+ for (let i = 0; i < dropCount; i++) {
601
+ const shouldActivate = Math.random() < 0.1;
602
+ const x = (Math.random() - 0.5) * waveWidth * 0.9;
603
+ const z = (Math.random() - 0.5) * waveDepth * 0.7;
604
+ const waveHeight = Math.sin(x * 0.05) * 8 + Math.sin(z * 0.08) * 5;
605
+ const startHeight = shouldActivate ? waveHeight + Math.random() * 40 : -200;
606
+ rainDrops.push({
607
+ x,
608
+ z,
609
+ y: startHeight,
610
+ velocity: shouldActivate ? 0.5 + Math.random() * 0.7 : 0,
611
+ active: shouldActivate,
612
+ maxHeight: waveHeight + 80 + Math.random() * 120,
613
+ trail: new Array(trailLength).fill(startHeight),
614
+ opacity: shouldActivate ? 1 : 0
615
+ });
616
+ }
617
+ const trailPointCount = dropCount * trailLength;
618
+ const dropTrailGeometry = new THREE.BufferGeometry();
619
+ const dropTrailPositions = new Float32Array(trailPointCount * 3);
620
+ const dropTrailColors = new Float32Array(trailPointCount * 3);
621
+ const dropTrailSizes = new Float32Array(trailPointCount);
622
+ for (let i = 0; i < dropCount; i++) {
623
+ for (let j = 0; j < trailLength; j++) {
624
+ dropTrailSizes[i * trailLength + j] = 4 * (1 - j / trailLength) * 0.5;
625
+ }
626
+ }
627
+ dropTrailGeometry.setAttribute("position", new THREE.BufferAttribute(dropTrailPositions, 3));
628
+ dropTrailGeometry.setAttribute("color", new THREE.BufferAttribute(dropTrailColors, 3));
629
+ dropTrailGeometry.setAttribute("size", new THREE.BufferAttribute(dropTrailSizes, 1));
630
+ const dropTrailMaterial = new THREE.ShaderMaterial({
631
+ vertexShader,
632
+ fragmentShader: `
633
+ varying vec3 vColor;
634
+ void main() {
635
+ float dist = length(gl_PointCoord - vec2(0.5));
636
+ if (dist > 0.5) discard;
637
+ float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
638
+ gl_FragColor = vec4(vColor, alpha * 0.8);
639
+ }
640
+ `,
641
+ transparent: true,
642
+ vertexColors: true,
643
+ blending: THREE.AdditiveBlending,
644
+ depthWrite: false
645
+ });
646
+ const dropTrailParticles = new THREE.Points(dropTrailGeometry, dropTrailMaterial);
647
+ scene.add(dropTrailParticles);
648
+ const dropHeadGeometry = new THREE.BufferGeometry();
649
+ const dropHeadPositions = new Float32Array(dropCount * 3);
650
+ const dropHeadColors = new Float32Array(dropCount * 3);
651
+ const dropHeadSizes = new Float32Array(dropCount);
652
+ for (let i = 0; i < dropCount; i++) {
653
+ dropHeadSizes[i] = (6 + Math.random() * 4) * 0.5;
654
+ }
655
+ dropHeadGeometry.setAttribute("position", new THREE.BufferAttribute(dropHeadPositions, 3));
656
+ dropHeadGeometry.setAttribute("color", new THREE.BufferAttribute(dropHeadColors, 3));
657
+ dropHeadGeometry.setAttribute("size", new THREE.BufferAttribute(dropHeadSizes, 1));
658
+ const dropHeadMaterial = new THREE.ShaderMaterial({
659
+ vertexShader,
660
+ fragmentShader: `
661
+ varying vec3 vColor;
662
+ void main() {
663
+ float dist = length(gl_PointCoord - vec2(0.5));
664
+ if (dist > 0.5) discard;
665
+ float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
666
+ float glow = exp(-dist * 2.0);
667
+ vec3 finalColor = vColor + glow * 0.8;
668
+ gl_FragColor = vec4(finalColor, alpha);
669
+ }
670
+ `,
671
+ transparent: true,
672
+ vertexColors: true,
673
+ blending: THREE.AdditiveBlending,
674
+ depthWrite: false
675
+ });
676
+ const dropHeadParticles = new THREE.Points(dropHeadGeometry, dropHeadMaterial);
677
+ scene.add(dropHeadParticles);
678
+ let time = 0;
679
+ let lastSpawnTime = 0;
680
+ const getWaveHeight = (x, z, t) => Math.sin(x * 0.05 + t) * 8 + Math.sin(z * 0.08 + t * 0.8) * 5 + Math.sin((x + z) * 0.03 + t * 1.2) * 3;
681
+ const activateDrop = (drop) => {
682
+ drop.x = (Math.random() - 0.5) * waveWidth * 0.9;
683
+ drop.z = (Math.random() - 0.5) * waveDepth * 0.7;
684
+ const wh = getWaveHeight(drop.x, drop.z, time);
685
+ drop.y = wh;
686
+ drop.velocity = 0.5 + Math.random() * 0.7;
687
+ drop.maxHeight = wh + 80 + Math.random() * 120;
688
+ drop.active = true;
689
+ drop.opacity = 0.5;
690
+ drop.trail.fill(wh);
691
+ };
692
+ const animate = () => {
693
+ time += 0.02;
694
+ const wavePos = particles.geometry.attributes.position.array;
695
+ for (let i = 0; i < particleCount; i++) {
696
+ const i3 = i * 3;
697
+ wavePos[i3 + 1] = getWaveHeight(wavePos[i3], wavePos[i3 + 2], time);
698
+ }
699
+ particles.geometry.attributes.position.needsUpdate = true;
700
+ const tPos = dropTrailParticles.geometry.attributes.position.array;
701
+ const tCol = dropTrailParticles.geometry.attributes.color.array;
702
+ const hPos = dropHeadParticles.geometry.attributes.position.array;
703
+ const hCol = dropHeadParticles.geometry.attributes.color.array;
704
+ let activeCount = 0;
705
+ const inactiveIndices = [];
706
+ for (let i = 0; i < dropCount; i++) {
707
+ const drop = rainDrops[i];
708
+ if (drop.active) {
709
+ activeCount++;
710
+ for (let j = trailLength - 1; j > 0; j--) drop.trail[j] = drop.trail[j - 1];
711
+ drop.trail[0] = drop.y;
712
+ drop.velocity = Math.max(drop.velocity * 0.995, 0.3);
713
+ drop.y += drop.velocity;
714
+ if (drop.opacity < 1) drop.opacity = Math.min(drop.opacity + 0.05, 1);
715
+ if (drop.y > drop.maxHeight) {
716
+ drop.opacity -= 0.03;
717
+ if (drop.opacity <= 0) {
718
+ drop.active = false;
719
+ drop.y = -200;
720
+ drop.trail.fill(-200);
721
+ inactiveIndices.push(i);
722
+ }
723
+ }
724
+ } else {
725
+ inactiveIndices.push(i);
726
+ }
727
+ }
728
+ if (time - lastSpawnTime >= 0.1 && inactiveIndices.length > 0) {
729
+ const spawnCount = Math.min(2 + Math.floor(Math.random() * 2), inactiveIndices.length);
730
+ const shuffled = [...inactiveIndices].sort(() => Math.random() - 0.5);
731
+ for (let n = 0; n < spawnCount; n++) activateDrop(rainDrops[shuffled[n]]);
732
+ lastSpawnTime = time;
733
+ }
734
+ const targetActive = 20;
735
+ const needActivate = Math.max(3, targetActive - activeCount);
736
+ const remaining = inactiveIndices.filter((i) => !rainDrops[i].active);
737
+ if (remaining.length > 0) {
738
+ const shuffled = [...remaining].sort(() => Math.random() - 0.5);
739
+ for (let n = 0; n < Math.min(needActivate, shuffled.length); n++) activateDrop(rainDrops[shuffled[n]]);
740
+ }
741
+ for (let i = 0; i < dropCount; i++) {
742
+ if (!rainDrops[i].active && Math.random() < 0.25) activateDrop(rainDrops[i]);
743
+ }
744
+ for (let i = 0; i < dropCount; i++) {
745
+ const drop = rainDrops[i];
746
+ const hi3 = i * 3;
747
+ hPos[hi3] = drop.x;
748
+ hPos[hi3 + 1] = drop.y;
749
+ hPos[hi3 + 2] = drop.z;
750
+ const brightness = drop.active ? 0.8 + Math.sin(time * 5 + i) * 0.2 : 0;
751
+ hCol[hi3] = 0.5 * brightness * drop.opacity;
752
+ hCol[hi3 + 1] = 1 * brightness * drop.opacity;
753
+ hCol[hi3 + 2] = 1 * brightness * drop.opacity;
754
+ for (let j = 0; j < trailLength; j++) {
755
+ const pi = (i * trailLength + j) * 3;
756
+ tPos[pi] = drop.x;
757
+ tPos[pi + 1] = drop.trail[j];
758
+ tPos[pi + 2] = drop.z;
759
+ const fade = 1 - j / trailLength;
760
+ tCol[pi] = 0.6 * fade * drop.opacity;
761
+ tCol[pi + 1] = 1 * fade * drop.opacity;
762
+ tCol[pi + 2] = 1 * fade * drop.opacity;
763
+ }
764
+ }
765
+ dropTrailParticles.geometry.attributes.position.needsUpdate = true;
766
+ dropTrailParticles.geometry.attributes.color.needsUpdate = true;
767
+ dropHeadParticles.geometry.attributes.position.needsUpdate = true;
768
+ dropHeadParticles.geometry.attributes.color.needsUpdate = true;
769
+ camera.position.x = Math.sin(time * 0.3) * 5;
770
+ camera.lookAt(0, 0, 0);
771
+ renderer.render(scene, camera);
772
+ animationRef.current = requestAnimationFrame(animate);
773
+ };
774
+ animate();
775
+ const handleResize = () => {
776
+ const w = container.clientWidth;
777
+ const h = container.clientHeight;
778
+ camera.aspect = w / h;
779
+ camera.updateProjectionMatrix();
780
+ renderer.setSize(w, h);
781
+ };
782
+ window.addEventListener("resize", handleResize);
783
+ return () => {
784
+ window.removeEventListener("resize", handleResize);
785
+ if (animationRef.current) cancelAnimationFrame(animationRef.current);
786
+ container.removeChild(renderer.domElement);
787
+ geometry.dispose();
788
+ material.dispose();
789
+ bokehGeometry.dispose();
790
+ bokehMaterial.dispose();
791
+ dropTrailGeometry.dispose();
792
+ dropTrailMaterial.dispose();
793
+ dropHeadGeometry.dispose();
794
+ dropHeadMaterial.dispose();
795
+ renderer.dispose();
796
+ };
797
+ }, [background]);
798
+ return /* @__PURE__ */ jsx(
799
+ "div",
800
+ {
801
+ ref: containerRef,
802
+ className,
803
+ style: {
804
+ position: "fixed",
805
+ top: 0,
806
+ left: 0,
807
+ width: "100%",
808
+ height: "100%",
809
+ zIndex: 1,
810
+ pointerEvents: "none",
811
+ background,
812
+ ...style
813
+ }
814
+ }
815
+ );
816
+ };
817
+ var ParticleWave_default = ParticleWave;
818
+ var ParticlesBackground = ({
819
+ theme: themeProp,
820
+ isDark: isDarkProp,
821
+ onLoaded,
822
+ className,
823
+ style
824
+ }) => {
825
+ var _a, _b;
826
+ const [init, setInit] = useState(false);
827
+ const ctx = useParticleThemeOptional();
828
+ const themeId = (_a = themeProp != null ? themeProp : ctx == null ? void 0 : ctx.themeId) != null ? _a : DEFAULT_THEME_ID;
829
+ const isDark = (_b = isDarkProp != null ? isDarkProp : ctx == null ? void 0 : ctx.isDark) != null ? _b : true;
830
+ const theme = getThemeById(themeId);
831
+ useEffect(() => {
832
+ initParticlesEngine(async (engine) => {
833
+ await loadSlim(engine);
834
+ }).then(() => {
835
+ setInit(true);
836
+ }).catch(() => {
837
+ setInit(true);
838
+ });
839
+ }, []);
840
+ const options = useMemo(() => theme.options(isDark), [theme, isDark]);
841
+ if (themeId === "none") return null;
842
+ if (theme.isThreeJS) {
843
+ return /* @__PURE__ */ jsx(
844
+ ParticleWave_default,
845
+ {
846
+ background: theme.backgroundGradient || theme.backgroundColor,
847
+ className,
848
+ style
849
+ }
850
+ );
851
+ }
852
+ if (!init) return null;
853
+ return /* @__PURE__ */ jsx(
854
+ Particles,
855
+ {
856
+ id: "rpb-tsparticles",
857
+ className,
858
+ style,
859
+ particlesLoaded: async (container) => onLoaded == null ? void 0 : onLoaded(container),
860
+ options
861
+ },
862
+ themeId
863
+ );
864
+ };
865
+ var ParticlesBackground_default = ParticlesBackground;
866
+ var ThemeSelector = ({
867
+ position = "bottom-right",
868
+ accentColor = "#3b82f6"
869
+ }) => {
870
+ const [open, setOpen] = useState(false);
871
+ const { themeId, setTheme } = useParticleTheme();
872
+ const currentTheme = getThemeById(themeId);
873
+ const positionStyles = {
874
+ "bottom-right": { bottom: 80, right: 16 },
875
+ "bottom-left": { bottom: 80, left: 16 },
876
+ "top-right": { top: 80, right: 16 },
877
+ "top-left": { top: 80, left: 16 }
878
+ };
879
+ const drawerSide = position.includes("right") ? "right" : "left";
880
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
881
+ /* @__PURE__ */ jsx(
882
+ "button",
883
+ {
884
+ onClick: () => setOpen(true),
885
+ title: "Theme Settings",
886
+ style: {
887
+ position: "fixed",
888
+ ...positionStyles[position],
889
+ zIndex: 9999,
890
+ width: 44,
891
+ height: 44,
892
+ borderRadius: 12,
893
+ display: "flex",
894
+ alignItems: "center",
895
+ justifyContent: "center",
896
+ background: "rgba(255, 255, 255, 0.7)",
897
+ backdropFilter: "blur(12px)",
898
+ WebkitBackdropFilter: "blur(12px)",
899
+ border: "1px solid rgba(255, 255, 255, 0.5)",
900
+ boxShadow: "0 4px 20px rgba(0, 0, 0, 0.08)",
901
+ cursor: "pointer",
902
+ fontSize: 20,
903
+ transition: "transform 0.3s, box-shadow 0.3s"
904
+ },
905
+ onMouseEnter: (e) => {
906
+ e.currentTarget.style.transform = "scale(1.05) translateY(-2px)";
907
+ },
908
+ onMouseLeave: (e) => {
909
+ e.currentTarget.style.transform = "";
910
+ },
911
+ children: currentTheme.icon
912
+ }
913
+ ),
914
+ open && /* @__PURE__ */ jsx(
915
+ "div",
916
+ {
917
+ onClick: () => setOpen(false),
918
+ style: {
919
+ position: "fixed",
920
+ inset: 0,
921
+ zIndex: 1e4,
922
+ background: "rgba(0, 0, 0, 0.3)",
923
+ transition: "opacity 0.3s"
924
+ }
925
+ }
926
+ ),
927
+ /* @__PURE__ */ jsxs(
928
+ "div",
929
+ {
930
+ style: {
931
+ position: "fixed",
932
+ top: 0,
933
+ [drawerSide]: 0,
934
+ width: 320,
935
+ maxWidth: "85vw",
936
+ height: "100vh",
937
+ zIndex: 10001,
938
+ background: "#fff",
939
+ boxShadow: "-4px 0 24px rgba(0, 0, 0, 0.12)",
940
+ transform: open ? "translateX(0)" : `translateX(${drawerSide === "right" ? "100%" : "-100%"})`,
941
+ transition: "transform 0.3s ease",
942
+ overflowY: "auto",
943
+ display: "flex",
944
+ flexDirection: "column"
945
+ },
946
+ children: [
947
+ /* @__PURE__ */ jsxs(
948
+ "div",
949
+ {
950
+ style: {
951
+ display: "flex",
952
+ alignItems: "center",
953
+ justifyContent: "space-between",
954
+ padding: "16px 20px",
955
+ borderBottom: "1px solid #f0f0f0"
956
+ },
957
+ children: [
958
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600, fontSize: 16 }, children: "Particle Theme" }),
959
+ /* @__PURE__ */ jsx(
960
+ "button",
961
+ {
962
+ onClick: () => setOpen(false),
963
+ style: {
964
+ background: "none",
965
+ border: "none",
966
+ fontSize: 20,
967
+ cursor: "pointer",
968
+ color: "#999",
969
+ padding: 4
970
+ },
971
+ children: "\xD7"
972
+ }
973
+ )
974
+ ]
975
+ }
976
+ ),
977
+ /* @__PURE__ */ jsx("div", { style: { padding: 16, display: "flex", flexDirection: "column", gap: 12 }, children: [...particleThemes, {
978
+ id: "none",
979
+ name: "None",
980
+ icon: "\u{1F6AB}",
981
+ description: "Disable particle effects"
982
+ }].map((theme) => {
983
+ const isActive = theme.id === themeId;
984
+ return /* @__PURE__ */ jsxs(
985
+ "div",
986
+ {
987
+ onClick: () => setTheme(theme.id),
988
+ style: {
989
+ padding: 16,
990
+ borderRadius: 12,
991
+ cursor: "pointer",
992
+ display: "flex",
993
+ alignItems: "center",
994
+ gap: 12,
995
+ transition: "all 0.2s",
996
+ border: `2px solid ${isActive ? accentColor : "transparent"}`,
997
+ background: isActive ? `${accentColor}10` : "#f5f5f5"
998
+ },
999
+ onMouseEnter: (e) => {
1000
+ if (!isActive) e.currentTarget.style.borderColor = "#e0e0e0";
1001
+ },
1002
+ onMouseLeave: (e) => {
1003
+ if (!isActive) e.currentTarget.style.borderColor = "transparent";
1004
+ },
1005
+ children: [
1006
+ /* @__PURE__ */ jsx(
1007
+ "div",
1008
+ {
1009
+ style: {
1010
+ width: 48,
1011
+ height: 48,
1012
+ borderRadius: 12,
1013
+ display: "flex",
1014
+ alignItems: "center",
1015
+ justifyContent: "center",
1016
+ fontSize: 24,
1017
+ background: isActive ? accentColor : "#e5e5e5",
1018
+ flexShrink: 0
1019
+ },
1020
+ children: theme.icon
1021
+ }
1022
+ ),
1023
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
1024
+ /* @__PURE__ */ jsx(
1025
+ "div",
1026
+ {
1027
+ style: {
1028
+ fontWeight: 600,
1029
+ fontSize: 14,
1030
+ color: isActive ? accentColor : "#1f2937"
1031
+ },
1032
+ children: theme.name
1033
+ }
1034
+ ),
1035
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "#9ca3af", marginTop: 2 }, children: theme.description })
1036
+ ] }),
1037
+ isActive && /* @__PURE__ */ jsx(
1038
+ "div",
1039
+ {
1040
+ style: {
1041
+ width: 24,
1042
+ height: 24,
1043
+ borderRadius: "50%",
1044
+ background: accentColor,
1045
+ display: "flex",
1046
+ alignItems: "center",
1047
+ justifyContent: "center",
1048
+ flexShrink: 0
1049
+ },
1050
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "#fff", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M5 13l4 4L19 7" }) })
1051
+ }
1052
+ )
1053
+ ]
1054
+ },
1055
+ theme.id
1056
+ );
1057
+ }) }),
1058
+ /* @__PURE__ */ jsx("div", { style: { padding: "0 16px 16px", marginTop: "auto" }, children: /* @__PURE__ */ jsx(
1059
+ "div",
1060
+ {
1061
+ style: {
1062
+ padding: 16,
1063
+ background: "#f9fafb",
1064
+ borderRadius: 12,
1065
+ fontSize: 13,
1066
+ color: "#6b7280",
1067
+ lineHeight: 1.6
1068
+ },
1069
+ children: "Your theme selection is automatically saved and will be remembered on your next visit."
1070
+ }
1071
+ ) })
1072
+ ]
1073
+ }
1074
+ )
1075
+ ] });
1076
+ };
1077
+ var ThemeSelector_default = ThemeSelector;
1078
+
1079
+ export { DEFAULT_COLORS, DEFAULT_THEME_ID, ParticleProvider, ParticleWave_default as ParticleWave, ParticlesBackground_default as ParticlesBackground, ThemeSelector_default as ThemeSelector, baseConfig, bubbleTheme, fireflyTheme, geometryTheme, getThemeById, particleThemes, snowTheme, starlineTheme, starsTheme, useParticleTheme, waveTheme };
1080
+ //# sourceMappingURL=index.mjs.map
1081
+ //# sourceMappingURL=index.mjs.map