jl-particle-interactive 0.2.0 → 0.2.2
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/README.md +211 -9
- package/dist/index.d.ts +6 -0
- package/dist/jl-particle-interactive.js +3 -0
- package/dist/jl-particle-interactive.umd.cjs +1 -1
- package/package.json +31 -5
package/README.md
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
|
-
# jl-particle-interactive
|
|
1
|
+
# jl-particle-interactive — React Canvas Particle Animations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/jl-particle-interactive)
|
|
4
|
+
[](https://www.npmjs.com/package/jl-particle-interactive)
|
|
5
|
+
[](https://bundlephobia.com/package/jl-particle-interactive)
|
|
6
|
+
[](https://github.com/cjorgeluis122333/jl-particles-interactive/blob/main/LICENSE)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
|
|
9
|
+
A canvas-based React library for rendering text and backgrounds as thousands of animated particles. Letters form from particle swarms, respond to magnetic hover, attract or repel on click, and backgrounds come alive with NET graphs, JELLYFISH glows, or pointer-following swarms. Built with zero runtime dependencies, full TypeScript support, and DPR-aware rendering for sharp output on retina displays.
|
|
10
|
+
|
|
11
|
+
> Requires React 18+. No global CSS. Zero runtime dependencies.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why jl-particle-interactive?
|
|
16
|
+
|
|
17
|
+
| Feature | jl-particle-interactive | tsparticles | particles.js |
|
|
18
|
+
|---|---|---|---|
|
|
19
|
+
| **Text that forms from particles** | ✓ (native, spatial coherence) | Plugin only (complex setup) | ✗ |
|
|
20
|
+
| **Spring physics + float noise** | ✓ | ✗ | ✗ |
|
|
21
|
+
| **Magnetic hover / click interact** | ✓ (attract & repel) | ✗ | ✗ |
|
|
22
|
+
| **Ready-made background presets** | ✓ NET, JELLYFISH, FOLLOW_POINTER | General engine (DIY) | General engine (DIY) |
|
|
23
|
+
| **Runtime dependencies** | **Zero** | 14+ packages | 0 (vanilla JS only) |
|
|
24
|
+
| **React integration** | Native React hooks | Wrapper package needed | Manual integration |
|
|
25
|
+
| **TypeScript** | Strict mode | Partial | ✗ |
|
|
26
|
+
| **DPR-aware (retina)** | ✓ | ✗ | ✗ |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Use cases
|
|
31
|
+
|
|
32
|
+
- **Hero section animated titles** — Text materializes from particle chaos on page load
|
|
33
|
+
- **Loading screens** — Words form and dissolve while content loads
|
|
34
|
+
- **Interactive word carousels** — Cycle through words; particles re-form smoothly
|
|
35
|
+
- **Animated backgrounds** — NET graph, JELLYFISH glow, or pointer-following swarm behind any content
|
|
4
36
|
|
|
5
37
|
---
|
|
6
38
|
|
|
@@ -10,7 +42,18 @@ Particle animations for React — text that comes alive and backgrounds that rea
|
|
|
10
42
|
npm install jl-particle-interactive
|
|
11
43
|
```
|
|
12
44
|
|
|
13
|
-
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Framework compatibility
|
|
48
|
+
|
|
49
|
+
| Framework | Supported |
|
|
50
|
+
|---|---|
|
|
51
|
+
| Vite + React | ✓ |
|
|
52
|
+
| Next.js (App Router & Pages) | ✓ (client components only — add `'use client'`) |
|
|
53
|
+
| Create React App | ✓ |
|
|
54
|
+
| Remix | ✓ (client-side only) |
|
|
55
|
+
| Astro | ✓ (inside `client:only` components) |
|
|
56
|
+
| TypeScript | ✓ (strict mode, declarations included) |
|
|
14
57
|
|
|
15
58
|
---
|
|
16
59
|
|
|
@@ -18,11 +61,12 @@ npm install jl-particle-interactive
|
|
|
18
61
|
|
|
19
62
|
| Feature | Description |
|
|
20
63
|
|---|---|
|
|
21
|
-
| **Text particles** | Thousands of particles that form any text string |
|
|
22
|
-
| **Magnetic hover** | Particles are attracted to the cursor on hover |
|
|
23
|
-
| **Click interactions** | Attract or repel particles on click/tap |
|
|
24
|
-
| **Animated backgrounds** | NET, JELLYFISH, and FOLLOW_POINTER modes |
|
|
25
|
-
| **
|
|
64
|
+
| **Text particles** | Thousands of canvas particles that form any text string using pixel-sampling with spatial coherence — letters look sharp at any size |
|
|
65
|
+
| **Magnetic hover** | Particles are attracted to the cursor on hover (~173px radius spring force) |
|
|
66
|
+
| **Click interactions** | Attract or repel particles on click/tap — particles flee or swarm toward the pointer |
|
|
67
|
+
| **Animated backgrounds** | NET (connected node graph), JELLYFISH (organic glow rings), and FOLLOW_POINTER (swarm) modes |
|
|
68
|
+
| **Spring physics** | Each particle uses spring + friction + float-noise physics — movement feels natural, never robotic |
|
|
69
|
+
| **Customizable** | Colors (hex or RGB palettes), shapes (circle, square, bean), density, speed, ease, and more |
|
|
26
70
|
|
|
27
71
|
---
|
|
28
72
|
|
|
@@ -102,7 +146,6 @@ A connected particle network moves behind your content.
|
|
|
102
146
|
background={{
|
|
103
147
|
name: 'NET',
|
|
104
148
|
color: '#4ecdc4',
|
|
105
|
-
lineDistance: 120,
|
|
106
149
|
density: 0.8,
|
|
107
150
|
}}
|
|
108
151
|
>
|
|
@@ -156,6 +199,149 @@ export default function Carousel() {
|
|
|
156
199
|
|
|
157
200
|
---
|
|
158
201
|
|
|
202
|
+
## Background guide
|
|
203
|
+
|
|
204
|
+
The `background` prop on `ParticleCanvas` supports four usage patterns:
|
|
205
|
+
|
|
206
|
+
1. No animated background
|
|
207
|
+
2. `FOLLOW_POINTER` swarm background
|
|
208
|
+
3. `NET` node-link graph background
|
|
209
|
+
4. `JELLYFISH` organic glow/swim background
|
|
210
|
+
|
|
211
|
+
### 1) No background engine
|
|
212
|
+
|
|
213
|
+
Use this when you only want text particles.
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
<ParticleCanvas background={{ name: 'NONE' }}>
|
|
217
|
+
<TextParticleEngine text="Only Text" />
|
|
218
|
+
</ParticleCanvas>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 2) FOLLOW_POINTER background
|
|
222
|
+
|
|
223
|
+
Particles form a swarm that follows the cursor smoothly.
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<ParticleCanvas
|
|
227
|
+
background={{
|
|
228
|
+
name: 'FOLLOW_POINTER',
|
|
229
|
+
orientation: 'diagonal',
|
|
230
|
+
density: 1,
|
|
231
|
+
shape: 'bean',
|
|
232
|
+
colors: ['#00d4ff', '#6ee7b7', '#facc15'],
|
|
233
|
+
colorMode: 'wave',
|
|
234
|
+
particleSpeed: 1,
|
|
235
|
+
pointerTrackingSpeed: 0.06,
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
<TextParticleEngine text="Follow" particleColor="255, 255, 255" />
|
|
239
|
+
</ParticleCanvas>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 3) NET background
|
|
243
|
+
|
|
244
|
+
Moving nodes connect with lines. Great for tech-style hero sections.
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
<ParticleCanvas
|
|
248
|
+
background={{
|
|
249
|
+
name: 'NET',
|
|
250
|
+
density: 0.9,
|
|
251
|
+
shape: 'circle',
|
|
252
|
+
colors: ['#7dd3fc', '#60a5fa'],
|
|
253
|
+
colorMode: 'mixed',
|
|
254
|
+
particleSpeed: 1,
|
|
255
|
+
pointerTrackingSpeed: 0.08,
|
|
256
|
+
}}
|
|
257
|
+
>
|
|
258
|
+
<TextParticleEngine text="Network" particleColor="255, 255, 255" />
|
|
259
|
+
</ParticleCanvas>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### 4) JELLYFISH background
|
|
263
|
+
|
|
264
|
+
Soft organic particle body with pulse/swimming motion.
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
<ParticleCanvas
|
|
268
|
+
background={{
|
|
269
|
+
name: 'JELLYFISH',
|
|
270
|
+
density: 1.1,
|
|
271
|
+
shape: 'bean',
|
|
272
|
+
colors: ['#f472b6', '#a78bfa', '#22d3ee'],
|
|
273
|
+
colorMode: 'wave',
|
|
274
|
+
particleSpeed: 1,
|
|
275
|
+
pointerTrackingSpeed: 0.02,
|
|
276
|
+
}}
|
|
277
|
+
>
|
|
278
|
+
<TextParticleEngine text="Jelly" particleColor="255, 255, 255" />
|
|
279
|
+
</ParticleCanvas>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Background with transparent stage
|
|
283
|
+
|
|
284
|
+
Use `backgroundColor="transparent"` to place particles over your own image/gradient layer.
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
<div style={{ background: 'linear-gradient(135deg, #0f172a, #111827)' }}>
|
|
288
|
+
<ParticleCanvas
|
|
289
|
+
height="70vh"
|
|
290
|
+
backgroundColor="transparent"
|
|
291
|
+
background={{ name: 'NET', density: 0.7, color: '#67e8f9' }}
|
|
292
|
+
>
|
|
293
|
+
<TextParticleEngine text="Overlay" backgroundColor="transparent" />
|
|
294
|
+
</ParticleCanvas>
|
|
295
|
+
</div>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### Full `BackgroundCanvas` type
|
|
299
|
+
|
|
300
|
+
```ts
|
|
301
|
+
type BackgroundModeName = 'NONE' | 'FOLLOW_POINTER' | 'NET' | 'JELLYFISH';
|
|
302
|
+
type ParticleOrientation = 'vertical' | 'horizontal' | 'diagonal';
|
|
303
|
+
|
|
304
|
+
interface BackgroundCanvas {
|
|
305
|
+
name: BackgroundModeName;
|
|
306
|
+
orientation?: ParticleOrientation;
|
|
307
|
+
density?: number;
|
|
308
|
+
color?: string;
|
|
309
|
+
colors?: string[];
|
|
310
|
+
colorMode?: 'wave' | 'mixed';
|
|
311
|
+
interactionRadius?: number;
|
|
312
|
+
lineDistance?: number;
|
|
313
|
+
shape?: 'circle' | 'square' | 'bean';
|
|
314
|
+
particleSpeed?: number;
|
|
315
|
+
pointerTrackingSpeed?: number;
|
|
316
|
+
}
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Option matrix (what applies to each mode)
|
|
320
|
+
|
|
321
|
+
| Option | FOLLOW_POINTER | NET | JELLYFISH | Notes |
|
|
322
|
+
|---|---|---|---|---|
|
|
323
|
+
| `name` | ✓ | ✓ | ✓ | Mode selector |
|
|
324
|
+
| `density` | ✓ | ✓ | ✓ | Particle count multiplier |
|
|
325
|
+
| `color` | ✓ | ✓ | ✓ | Single hex/HSL/CSS color |
|
|
326
|
+
| `colors` | ✓ | ✓ | ✓ | Palette override |
|
|
327
|
+
| `colorMode` | ✓ | ✓ | ✓ | `wave` (default) or `mixed` |
|
|
328
|
+
| `shape` | ✓ | ✓ | ✓ | `bean` default in FOLLOW_POINTER, `circle` in NET/JELLYFISH |
|
|
329
|
+
| `particleSpeed` | ✓ | ✓ | ✓ | Animation speed multiplier |
|
|
330
|
+
| `pointerTrackingSpeed` | ✓ | ✓ | ✓ | Cursor-follow/response smoothness |
|
|
331
|
+
| `orientation` | ✓ | — | — | Only FOLLOW_POINTER (`vertical` default) |
|
|
332
|
+
| `interactionRadius` | — | — | — | Declared in type, currently not applied in v0.2.1 |
|
|
333
|
+
| `lineDistance` | — | — | — | Declared in type, currently not applied in v0.2.1 |
|
|
334
|
+
|
|
335
|
+
### Practical presets
|
|
336
|
+
|
|
337
|
+
| Goal | Suggested config |
|
|
338
|
+
|---|---|
|
|
339
|
+
| Calm ambient hero | `JELLYFISH`, `density: 0.8`, `pointerTrackingSpeed: 0.015` |
|
|
340
|
+
| High-energy interactive background | `FOLLOW_POINTER`, `density: 1.2`, `particleSpeed: 1.3`, `shape: 'bean'` |
|
|
341
|
+
| Tech/network look | `NET`, `density: 0.9`, `colorMode: 'mixed'`, cool blue palette |
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
159
345
|
## API reference
|
|
160
346
|
|
|
161
347
|
### `<ParticleCanvas>`
|
|
@@ -169,6 +355,22 @@ export default function Carousel() {
|
|
|
169
355
|
| `className` | `string` | `''` | Additional CSS class |
|
|
170
356
|
| `style` | `CSSProperties` | — | Inline style overrides |
|
|
171
357
|
|
|
358
|
+
### `BackgroundCanvas`
|
|
359
|
+
|
|
360
|
+
| Prop | Type | Description |
|
|
361
|
+
|---|---|---|
|
|
362
|
+
| `name` | `'NONE' \| 'FOLLOW_POINTER' \| 'NET' \| 'JELLYFISH'` | Background mode |
|
|
363
|
+
| `orientation` | `'vertical' \| 'horizontal' \| 'diagonal'` | Direction style for `FOLLOW_POINTER` |
|
|
364
|
+
| `density` | `number` | Particle amount multiplier |
|
|
365
|
+
| `color` | `string` | Single background particle color |
|
|
366
|
+
| `colors` | `string[]` | Background particle palette |
|
|
367
|
+
| `colorMode` | `'wave' \| 'mixed'` | Palette propagation mode |
|
|
368
|
+
| `shape` | `'circle' \| 'square' \| 'bean'` | Particle drawing shape |
|
|
369
|
+
| `particleSpeed` | `number` | Background animation speed multiplier |
|
|
370
|
+
| `pointerTrackingSpeed` | `number` | Cursor tracking interpolation factor |
|
|
371
|
+
| `interactionRadius` | `number` | Reserved in type (not applied in v0.2.1) |
|
|
372
|
+
| `lineDistance` | `number` | Reserved in type (not applied in v0.2.1) |
|
|
373
|
+
|
|
172
374
|
### `<TextParticleEngine>`
|
|
173
375
|
|
|
174
376
|
| Prop | Type | Default | Description |
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ export { default as ParticleCanvas } from './components/ParticleCanvas';
|
|
|
2
2
|
export type { ParticleCanvasProps } from './components/ParticleCanvas';
|
|
3
3
|
export { default as TextParticleEngine } from './components/text/TextParticleEngine';
|
|
4
4
|
export type { TextParticleEngineProps } from './components/text/TextParticleEngine';
|
|
5
|
+
export { default as BackgroundParticleEngine } from './components/background/BackgroundParticleEngine';
|
|
6
|
+
export type { BackgroundParticleEngineProps } from './components/background/BackgroundParticleEngine';
|
|
7
|
+
export { default as NetParticleEngine } from './components/background/NetParticleEngine';
|
|
8
|
+
export type { NetParticleEngineProps } from './components/background/NetParticleEngine';
|
|
9
|
+
export { default as JellyfishParticleEngine } from './components/background/JellyfishParticleEngine';
|
|
10
|
+
export type { JellyfishParticleEngineProps } from './components/background/JellyfishParticleEngine';
|
|
5
11
|
export { useParticleInteraction, getMagnetTarget } from './hooks/useParticleInteraction';
|
|
6
12
|
export type { ClickMode } from './hooks/useParticleInteraction';
|
|
7
13
|
export { useTextParticles } from './hooks/text/useTextParticles';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(A,q){typeof exports=="object"&&typeof module<"u"?q(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],q):(A=typeof globalThis<"u"?globalThis:A||self,q(A.JlParticleInteractive={},A.ReactJsxRuntime,A.React))})(this,(function(A,q,f){"use strict";var lt=Object.defineProperty;var ht=(A,q,f)=>q in A?lt(A,q,{enumerable:!0,configurable:!0,writable:!0,value:f}):A[q]=f;var v=(A,q,f)=>ht(A,typeof q!="symbol"?q+"":q,f);function _(t,e,n,i){let s=e+t.baseX,m=n+t.baseY;s+=Math.sin(i*t.randomSpeed+t.baseY*.05)*15*t.z,m+=Math.cos(i*t.randomSpeed+t.baseX*.05)*15*t.z;const r=s-t.x,o=m-t.y,h=r*.06,g=o*.06;return{forceX:h,forceY:g}}function U(t,e,n,i,s,m){const r=i,o=s,h=t.x-r,g=t.y-o,d=Math.max(Math.sqrt(h*h+g*g),1);let c=0,a=0;const u=Math.sin(d*.02-m*3)*2.5*t.z;c+=h/d*u,a+=g/d*u;const E=Math.cos(d*.01-m*1+t.randomSpeed)*.8*t.z,T=-g/d,b=h/d;return c+=T*E,a+=b*E,{forceX:c,forceY:a,dxCenter:h,dyCenter:g,distToCenter:d}}function Q(t,e){let s=Math.min(e/400,1)*2.5+t.sizeBias*1;s<.4&&(s=0),t.scale+=(s-t.scale)*.15}function V(t,e,n,i,s,m){let r=0,o=0;s==="horizontal"?(r=1,o=0):s==="diagonal"?(r=-e/i,o=-n/i):(r=-n/i,o=e/i);const h=Math.atan2(o,r),g=t.sizeBias>.3?1:0,d=Math.max(0,Math.sin(m*.5+t.randomSpeed*10)),c=Math.sin(m*3+t.baseX*.1+t.randomSpeed)*.5*d*g;r=Math.cos(h+c),o=Math.sin(h+c);const a=Math.max(1-i/400,.1);s==="horizontal"?(t.dirX=t.dirX*(1-.2)+r*.2,t.dirY=t.dirY*(1-.2)+o*.2):(t.dirX=t.dirX*(1-a*.3)+r*a*.3,t.dirY=t.dirY*(1-a*.3)+o*a*.3);const l=Math.sqrt(t.dirX*t.dirX+t.dirY*t.dirY);l>0&&(t.dirX/=l,t.dirY/=l)}class Z{constructor(e,n,i){v(this,"x");v(this,"y");v(this,"baseX");v(this,"baseY");v(this,"z");v(this,"vx");v(this,"vy");v(this,"color");v(this,"targetColor",null);v(this,"colorDelay",0);v(this,"angleTarget");v(this,"randomSpeed");v(this,"sizeBias");v(this,"scale");v(this,"dirX");v(this,"dirY");v(this,"initialized",!1);this.x=e,this.y=n,this.baseX=e,this.baseY=n,this.z=Math.random()*1.5+.2,this.vx=0,this.vy=0,this.color=i,this.angleTarget=Math.random()*Math.PI*2,this.randomSpeed=Math.random()*2+1,this.sizeBias=Math.random(),this.scale=1,this.dirX=Math.cos(this.angleTarget),this.dirY=Math.sin(this.angleTarget)}update(e,n,i,s,m,r,o,h="vertical"){this.initialized||(this.x=i+this.baseX,this.y=s+this.baseY,this.initialized=!0);const{forceX:g,forceY:d,dxCenter:c,dyCenter:a,distToCenter:l}=U(this,e,n,i,s,o),{forceX:u,forceY:R}=_(this,i,s,o);this.vx+=g+u,this.vy+=d+R,this.vx*=.75,this.vy*=.75,this.x+=this.vx,this.y+=this.vy,Q(this,l),V(this,c,a,l,h,o),this.targetColor&&(this.colorDelay-=1,this.colorDelay<=0&&(this.color=this.targetColor,this.targetColor=null))}draw(e,n,i="bean"){if(this.scale<=.05)return;e.fillStyle=this.color;const s=Math.sqrt(this.baseX*this.baseX+this.baseY*this.baseY),m=Math.max(0,1-s/350);if(e.globalAlpha=Math.min(1,(.5+this.z*.5)*Math.min(this.scale,1)*m),!(e.globalAlpha<=.01))if(i==="circle"){const r=Math.max(.1,2*this.scale*this.z);e.beginPath(),e.arc(this.x,this.y,r,0,Math.PI*2),e.fill()}else if(i==="square"){const r=Math.max(.1,2*this.scale*this.z);e.fillRect(this.x-r,this.y-r,r*2,r*2)}else{const o=(6+this.sizeBias*6)*this.scale,h=Math.sin(n*3.5+this.baseX*.1+this.randomSpeed*5),g=.4+.6*((h+1)/2),d=o*.5*g,c=Math.max(.8,this.z*1.5*this.scale*(.8+.2*h)),a=Math.atan2(this.dirY,this.dirX);e.beginPath(),e.ellipse(this.x,this.y,d,c,a,0,Math.PI*2),e.fill()}}}function tt({config:t,backgroundColor:e}){const n=f.useRef(null),i=f.useRef(null),s=f.useRef([]),m=f.useRef(0),r=f.useRef({x:-1e3,y:-1e3,isDown:!1,active:!1}),o=f.useRef({x:0,y:0,initialized:!1});f.useEffect(()=>{const c=l=>{if(!n.current)return;const u=n.current.getBoundingClientRect();r.current.x=l.clientX-u.left,r.current.y=l.clientY-u.top,r.current.active=!0},a=()=>{r.current.active=!1};return window.addEventListener("pointermove",c),window.addEventListener("pointerleave",a),()=>{window.removeEventListener("pointermove",c),window.removeEventListener("pointerleave",a)}},[]);const h=f.useRef(t),g=f.useRef(null),d=f.useRef(0);return f.useEffect(()=>{h.current=t},[t]),f.useEffect(()=>{if(g.current&&(clearInterval(g.current),g.current=null),!t.colors||t.colors.length===0){const a=t.color||"#8B5CF6";s.current.forEach(l=>{l.targetColor=a,l.colorDelay=Math.random()*20});return}const c=a=>{const l=r.current.active?r.current.x:o.current.x,u=r.current.active?r.current.y:o.current.y;s.current.forEach(R=>{const E=R.x-l,T=R.y-u,b=Math.sqrt(E*E+T*T);R.targetColor=a,R.colorDelay=Math.max(0,b*.15)})};return t.colorMode==="mixed"?s.current.forEach((a,l)=>{a.targetColor=t.colors[l%t.colors.length],a.colorDelay=Math.random()*20}):(d.current=0,c(t.colors[0]),t.colors.length>1&&(g.current=setInterval(()=>{const a=t.colors;d.current=(d.current+1)%a.length,c(a[d.current])},3e3))),()=>{g.current&&clearInterval(g.current)}},[t.colors,t.color,t.colorMode]),f.useEffect(()=>{if(!n.current||!i.current||h.current.name==="NONE")return;const c=i.current,a=n.current,l=b=>{for(const C of b){const{width:y,height:w}=C.contentRect,P=window.devicePixelRatio||1;c.width=y*P,c.height=w*P,c.style.width=`${y}px`,c.style.height=`${w}px`;const S=c.getContext("2d");S&&S.scale(P,P),s.current=[];const z=Math.floor(350*(h.current.density??1)),M=h.current.colors,D=h.current.colorMode||"wave";for(let x=0;x<z;x++){const p=Math.sqrt(Math.random())*350,X=Math.random()*Math.PI*2,Y=Math.cos(X)*p,L=Math.sin(X)*p;let F;if(M&&M.length>0)F=D==="mixed"?M[x%M.length]:M[0];else if(h.current.color)F=h.current.color;else{const O=210+Math.max(0,Math.min(1,(Y+350)/700))*130+(Math.random()*15-7.5),$=75+Math.random()*25,N=60+Math.random()*15;F=`hsl(${O}, ${$}%, ${N}%)`}s.current.push(new Z(Y,L,F))}}},u=new ResizeObserver(l);u.observe(a);const R=c.getContext("2d");let E=0;const T=()=>{const b=a.getBoundingClientRect();if(R.clearRect(0,0,b.width,b.height),e!=="transparent"&&(R.fillStyle=e,R.fillRect(0,0,b.width,b.height)),o.current.initialized||(o.current.x=b.width/2,o.current.y=b.height/2,o.current.initialized=!0),h.current.name==="FOLLOW_POINTER"){let C=b.width/2,y=b.height/2;const w=r.current.active?r.current.x:null,P=r.current.active?r.current.y:null;w!==null&&P!==null&&(C=w,y=P);const S=h.current.pointerTrackingSpeed??.06;o.current.x+=(C-o.current.x)*S,o.current.y+=(y-o.current.y)*S;const I=h.current.particleSpeed??1;E+=.012*I;const z=s.current,M=18,D=M*M;for(let x=0;x<z.length;x++){const p=z[x];for(let X=x+1;X<z.length;X++){const Y=z[X],L=p.x-Y.x,F=p.y-Y.y,W=L*L+F*F;if(W<D&&W>0){const O=Math.sqrt(W),$=(M-O)/M,N=L/O*$*.8,B=F/O*$*.8;p.x+=N,p.y+=B,Y.x-=N,Y.y-=B,p.vx+=N*.1,p.vy+=B*.1,Y.vx-=N*.1,Y.vy-=B*.1}}}for(const x of z){x.update(w,P,o.current.x,o.current.y,b.width,b.height,E,h.current.orientation);const p=h.current.shape||"bean";x.draw(R,E,p)}}m.current=requestAnimationFrame(T)};return T(),()=>{u.disconnect(),cancelAnimationFrame(m.current)}},[e,t.density]),t.name==="NONE"?null:q.jsx("div",{ref:n,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden"},children:q.jsx("canvas",{ref:i,style:{display:"block",width:"100%",height:"100%"}})})}class et{constructor(e,n){v(this,"x");v(this,"y");v(this,"vx");v(this,"vy");v(this,"radius");v(this,"currentColor","#8B5CF6");v(this,"targetColor",null);v(this,"colorDelay",0);this.x=e,this.y=n;const i=Math.random()*Math.PI*2,s=Math.random()*.5+.1;this.vx=Math.cos(i)*s,this.vy=Math.sin(i)*s,this.radius=Math.random()*1.5+1}update(e,n,i,s,m=1,r=.06){if(this.x+=this.vx*m,this.y+=this.vy*m,this.x<0?(this.x=0,this.vx*=-1):this.x>e&&(this.x=e,this.vx*=-1),this.y<0?(this.y=0,this.vy*=-1):this.y>n&&(this.y=n,this.vy*=-1),i!==null&&s!==null){const o=i-this.x,h=s-this.y,g=o*o+h*h,d=150,c=d*d;if(g<c){const a=Math.sqrt(g),l=(d-a)/d,u=r/.06;this.x-=o/a*l*2*u,this.y-=h/a*l*2*u}}this.targetColor&&(this.colorDelay-=1*m,this.colorDelay<=0&&(this.currentColor=this.targetColor,this.targetColor=null))}draw(e,n="circle",i=0){if(e.fillStyle=this.currentColor,n==="square")e.fillRect(this.x-this.radius,this.y-this.radius,this.radius*2,this.radius*2);else if(n==="bean"){const s=Math.sin(i*.05+this.x*.01+this.y*.01),m=this.radius*2*(.8+.4*s),r=this.radius*(.8+.2*s),o=this.vx!==0||this.vy!==0?Math.atan2(this.vy,this.vx):0;e.beginPath(),e.ellipse(this.x,this.y,m,r,o,0,Math.PI*2),e.fill()}else e.beginPath(),e.arc(this.x,this.y,this.radius,0,Math.PI*2),e.fill()}}function nt({config:t,backgroundColor:e}){const n=f.useRef(null),i=f.useRef(null),s=f.useRef([]),m=f.useRef(0),r=f.useRef({x:-1e3,y:-1e3,active:!1});f.useEffect(()=>{const d=a=>{if(!n.current)return;const l=n.current.getBoundingClientRect();r.current.x=a.clientX-l.left,r.current.y=a.clientY-l.top,r.current.active=!0},c=()=>{r.current.active=!1};return window.addEventListener("pointermove",d),window.addEventListener("pointerleave",c),()=>{window.removeEventListener("pointermove",d),window.removeEventListener("pointerleave",c)}},[]);const o=f.useRef(t),h=f.useRef(0),g=f.useRef(null);return f.useEffect(()=>{o.current=t},[t]),f.useEffect(()=>{if(g.current&&(clearInterval(g.current),g.current=null),!t.colors||t.colors.length===0){const l=t.color||"#8B5CF6";s.current.forEach(u=>{u.targetColor=l,u.colorDelay=Math.random()*20});return}const d=r.current.active?r.current.x:n.current?n.current.clientWidth/2:0,c=r.current.active?r.current.y:n.current?n.current.clientHeight/2:0,a=l=>{s.current.forEach(u=>{const R=u.x-d,E=u.y-c,T=Math.sqrt(R*R+E*E);u.targetColor=l,u.colorDelay=Math.max(0,T*.2)})};return t.colorMode==="mixed"?s.current.forEach((l,u)=>{l.targetColor=t.colors[u%t.colors.length],l.colorDelay=Math.random()*20}):(h.current=0,a(t.colors[0]),t.colors.length>1&&(g.current=setInterval(()=>{const l=t.colors;h.current=(h.current+1)%l.length,a(l[h.current])},3e3))),()=>{g.current&&clearInterval(g.current)}},[t.colors,t.color,t.colorMode]),f.useEffect(()=>{if(!n.current||!i.current)return;const d=i.current,c=n.current,a=T=>{for(const b of T){const{width:C,height:y}=b.contentRect,w=window.devicePixelRatio||1;d.width=C*w,d.height=y*w,d.style.width=`${C}px`,d.style.height=`${y}px`;const P=d.getContext("2d");P&&P.scale(w,w),s.current=[];const S=C*y,I=o.current.density??1,z=Math.min(Math.floor(300*I),Math.floor(S/6e3*I)),M=o.current.colors,D=o.current.colorMode||"wave";for(let x=0;x<z;x++){const p=new et(Math.random()*C,Math.random()*y);M&&M.length>0?p.currentColor=D==="mixed"?M[x%M.length]:M[0]:o.current.color&&(p.currentColor=o.current.color),s.current.push(p)}}},l=new ResizeObserver(a);l.observe(c);const u=d.getContext("2d");let R=0;const E=()=>{R+=1;const T=c.getBoundingClientRect();u.clearRect(0,0,T.width,T.height),e!=="transparent"&&(u.fillStyle=e,u.fillRect(0,0,T.width,T.height));const b=r.current.active?r.current.x:null,C=r.current.active?r.current.y:null,y=s.current,w=o.current.shape||"circle",P=120,S=P*P,I=o.current.particleSpeed??1,z=o.current.pointerTrackingSpeed??.06;for(let M=0;M<y.length;M++){const D=y[M];D.update(T.width,T.height,b,C,I,z),u.globalAlpha=1,D.draw(u,w,R);for(let x=M+1;x<y.length;x++){const p=y[x],X=D.x-p.x,Y=D.y-p.y,L=X*X+Y*Y;if(L<S){const F=1-Math.sqrt(L)/P;u.beginPath(),u.moveTo(D.x,D.y),u.lineTo(p.x,p.y),u.strokeStyle=D.currentColor,u.globalAlpha=F*.5,u.lineWidth=1,u.stroke()}}}if(u.globalAlpha=1,b!==null&&C!==null)for(let x=0;x<y.length;x++){const p=y[x],X=p.x-b,Y=p.y-C,L=X*X+Y*Y;if(L<22500){const F=1-Math.sqrt(L)/150;u.beginPath(),u.moveTo(p.x,p.y),u.lineTo(b,C),u.strokeStyle=p.currentColor,u.globalAlpha=F*.8,u.lineWidth=1.5,u.stroke()}}u.globalAlpha=1,m.current=requestAnimationFrame(E)};return E(),()=>{l.disconnect(),cancelAnimationFrame(m.current)}},[e,t.density]),q.jsx("div",{ref:n,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden"},children:q.jsx("canvas",{ref:i,style:{display:"block",width:"100%",height:"100%"}})})}class rt{constructor(e,n,i,s){v(this,"x",0);v(this,"y",0);v(this,"baseX");v(this,"baseY");v(this,"vx",0);v(this,"vy",0);v(this,"color");v(this,"size");v(this,"angle");v(this,"dist");v(this,"spring");v(this,"friction");v(this,"targetColor",null);v(this,"colorDelay",0);this.baseX=e,this.baseY=n,this.color=i,this.size=s,this.angle=Math.atan2(n,e),this.dist=Math.sqrt(e*e+n*n);const m=Math.min(1,this.dist/350);this.spring=.15-m*.13,this.friction=.85+m*.1}update(e,n,i,s){const r=1+Math.sin(this.angle*3+i*1.2)*.05+Math.cos(this.angle*5-i*.6)*.03,o=this.dist*s*r,h=Math.cos(this.angle)*o,g=Math.sin(this.angle)*o,d=this.dist*.1*r,c=Math.sin(i*3+this.dist*.05)*d,a=e+h+Math.cos(this.angle+Math.PI/2)*c,l=n+g+Math.sin(this.angle+Math.PI/2)*c,u=a-this.x,R=l-this.y;this.vx+=u*this.spring,this.vy+=R*this.spring,this.vx*=this.friction,this.vy*=this.friction,this.x+=this.vx,this.y+=this.vy,this.targetColor&&(this.colorDelay-=1,this.colorDelay<=0&&(this.color=this.targetColor,this.targetColor=null))}draw(e,n,i){const s=Math.min(1,this.dist/350),m=1+(1-s)*(i-1)*1.5,r=this.size*m;e.globalAlpha=Math.max(.15,1-s*.7),e.fillStyle=this.color,e.beginPath(),n==="circle"?e.arc(this.x,this.y,r,0,Math.PI*2):n==="square"?e.rect(this.x-r,this.y-r,r*2,r*2):e.ellipse(this.x,this.y,r*1.5,r,0,0,Math.PI*2),e.fill()}}function st({config:t,backgroundColor:e}){const n=f.useRef(null),i=f.useRef(null),s=f.useRef([]),m=f.useRef(0),r=f.useRef({x:-1e3,y:-1e3,active:!1}),o=f.useRef({x:0,y:0,initialized:!1,angle:-Math.PI/2});f.useEffect(()=>{const c=a=>{if(!n.current)return;const l=n.current.getBoundingClientRect();r.current.x=a.clientX-l.left,r.current.y=a.clientY-l.top,r.current.active=!0};return window.addEventListener("pointermove",c),()=>{window.removeEventListener("pointermove",c)}},[]);const h=f.useRef(t),g=f.useRef(0),d=f.useRef(null);return f.useEffect(()=>{h.current=t},[t]),f.useEffect(()=>{if(d.current&&(clearInterval(d.current),d.current=null),!t.colors||t.colors.length===0){const a=t.color||"#8B5CF6";s.current.forEach(l=>{l.targetColor=a,l.colorDelay=Math.random()*20});return}const c=a=>{s.current.forEach(l=>{l.targetColor=a,l.colorDelay=Math.max(0,l.dist*.2)})};return t.colorMode==="mixed"?s.current.forEach((a,l)=>{a.targetColor=t.colors[l%t.colors.length],a.colorDelay=Math.max(0,a.dist*.2)+Math.random()*10}):(g.current=0,c(t.colors[0]),t.colors.length>1&&(d.current=setInterval(()=>{const a=t.colors;g.current=(g.current+1)%a.length,c(a[g.current])},3e3))),()=>{d.current&&clearInterval(d.current)}},[t.colors,t.color,t.colorMode]),f.useEffect(()=>{if(!n.current||!i.current)return;const c=i.current,a=n.current,l=b=>{for(const C of b){const{width:y,height:w}=C.contentRect,P=window.devicePixelRatio||1;c.width=y*P,c.height=w*P,c.style.width=`${y}px`,c.style.height=`${w}px`;const S=c.getContext("2d");S&&S.scale(P,P),s.current=[];const z=Math.floor(350*(h.current.density??1)),M=h.current.colors,D=h.current.colorMode||"wave";for(let x=0;x<z;x++){const p=Math.random()*Math.PI*2,X=Math.sqrt(Math.random())*350,Y=Math.cos(p)*X,L=Math.sin(p)*X;let F;if(M&&M.length>0)F=D==="mixed"?M[x%M.length]:M[0];else if(h.current.color)F=h.current.color;else{const $=260+Math.random()*60,N=70+Math.random()*30,B=60+Math.random()*20;F=`hsl(${$}, ${N}%, ${B}%)`}let W=2+Math.random()*2;X>200&&(W*=.6),X<80&&(W*=1.5);const O=new rt(Y,L,F,W);O.x=y/2,O.y=w/2,s.current.push(O)}}},u=new ResizeObserver(l);u.observe(a);const R=c.getContext("2d");let E=0;const T=()=>{const b=a.getBoundingClientRect();R.globalCompositeOperation="source-over",e==="transparent"?R.clearRect(0,0,b.width,b.height):(R.fillStyle=e,R.fillRect(0,0,b.width,b.height)),o.current.initialized||(o.current.x=b.width/2,o.current.y=b.height/2,o.current.initialized=!0);let C=b.width/2,y=b.height/2;const w=r.current.active?r.current.x:null,P=r.current.active?r.current.y:null;w!==null&&P!==null&&(C=w,y=P);const S=C-o.current.x,I=y-o.current.y,z=Math.sqrt(S*S+I*I);let M=0,D=0;z>1?(M=S/z,D=I/z,o.current.angle=Math.atan2(D,M)):(M=Math.cos(o.current.angle),D=Math.sin(o.current.angle));const x=h.current.particleSpeed??1;E+=.02*x;const p=E%4;let X=1,Y=1,L=0;if(p<1){const B=p;X=1-Math.sin(B*Math.PI)*.3,Y=1+Math.sin(B*Math.PI)*.15,L=Math.sin(B*Math.PI)*12}else{const B=(p-1)/3;X=.7+.3*Math.sin(B*Math.PI/2),Y=1,L=0}const F=h.current.pointerTrackingSpeed??.02;o.current.x+=S*F,o.current.y+=I*F,o.current.x+=M*L*x,o.current.y+=D*L*x;const O=200*(Y+(p<1?p*.15:0)),$=R.createRadialGradient(o.current.x,o.current.y,0,o.current.x,o.current.y,O);$.addColorStop(0,"rgba(80, 150, 255, 0.05)"),$.addColorStop(1,"rgba(80, 150, 255, 0)"),R.fillStyle=$,R.beginPath(),R.arc(o.current.x,o.current.y,O,0,Math.PI*2),R.fill();const N=s.current;R.globalCompositeOperation="source-over";for(const B of N){B.update(o.current.x,o.current.y,E,X);const at=h.current.shape||"circle";B.draw(R,at,Y)}m.current=requestAnimationFrame(T)};return T(),()=>{u.disconnect(),cancelAnimationFrame(m.current)}},[e,t.density]),q.jsx("div",{ref:n,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden",pointerEvents:"none"},children:q.jsx("canvas",{ref:i,style:{display:"block",width:"100%",height:"100%",pointerEvents:"none"}})})}const ot={position:"relative",border:"1px solid rgba(255, 255, 255, 0.1)",borderRadius:"1rem",overflow:"hidden",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",transition:"all 300ms ease-out"};function it({children:t,width:e="100%",height:n="60vh",backgroundColor:i="#050505",className:s="",style:m,background:r={name:"NONE"}}){return q.jsxs("div",{className:s,style:{...ot,width:e,height:n,backgroundColor:i,...m},children:[r.name==="FOLLOW_POINTER"&&q.jsx(tt,{config:r,backgroundColor:i}),r.name==="NET"&&q.jsx(nt,{config:r,backgroundColor:i}),r.name==="JELLYFISH"&&q.jsx(st,{config:r,backgroundColor:i}),q.jsx("div",{style:{position:"relative",zIndex:10,width:"100%",height:"100%"},children:t})]})}function k(t){const e=f.useRef({x:-1e3,y:-1e3,isDown:!1,active:!1});return f.useEffect(()=>{const n=t.current;if(!n)return;const i=o=>{const h=n.getBoundingClientRect();e.current.x=o.clientX-h.left,e.current.y=o.clientY-h.top,e.current.active=!0},s=()=>{e.current.active=!1},m=()=>{e.current.isDown=!0},r=()=>{e.current.isDown=!1};return n.addEventListener("pointermove",i),n.addEventListener("pointerleave",s),n.addEventListener("pointerdown",m),n.addEventListener("pointerup",r),n.style.touchAction="none",()=>{n.removeEventListener("pointermove",i),n.removeEventListener("pointerleave",s),n.removeEventListener("pointerdown",m),n.removeEventListener("pointerup",r)}},[t]),e}function H(t,e,n,i,s,m,r,o,h){if(s===null||m===null||!o&&h==="none")return{x:n,y:i};let g=n,d=i;const c=s-t,a=m-e,l=c*c+a*a,u=Math.sqrt(l);if(o&&!r&&l<3e4){const E=(3e4-l)/3e4;g+=c*E*.15,d+=a*E*.15}if(r&&h!=="none"){if(h==="attract"){if(l<3e4){const E=(3e4-l)/3e4;g+=c*E*.8,d+=a*E*.8}}else if(h==="repel"&&l<5e4&&u>0){const E=Math.pow(Math.max(0,5e4-l)/5e4,1.2);g-=c/u*E*400,d-=a/u*E*400}}return{x:g,y:d}}class J{constructor(e,n,i="255, 255, 255"){v(this,"x");v(this,"y");v(this,"vx");v(this,"vy");v(this,"targetX");v(this,"targetY");v(this,"baseColor");v(this,"opacity");v(this,"size");v(this,"sizeMultiplier");v(this,"friction");v(this,"ease");v(this,"easeMultiplier");v(this,"floatSpeed");v(this,"floatOffset");v(this,"randomSpeed");this.x=Math.random()*e,this.y=Math.random()*n,this.targetX=this.x,this.targetY=this.y,this.vx=0,this.vy=0,this.size=Math.random()*1.8+.5,this.sizeMultiplier=1,this.baseColor=Array.isArray(i)?i[Math.floor(Math.random()*i.length)]:i,this.opacity=.4+Math.random()*.6,this.friction=.82+Math.random()*.1,this.ease=.03+Math.random()*.05,this.easeMultiplier=1,this.floatSpeed=Math.random()*.02+.005,this.floatOffset=Math.random()*Math.PI*2,this.randomSpeed=Math.random()}update(e,n,i=null,s=null,m=!1,r=!0,o="none"){const{x:h,y:g}=H(this.x,this.y,this.targetX,this.targetY,i,s,m,r,o),d=h-this.x,c=g-this.y,a=n?0:Math.cos(e*.01+this.y*.01)*.5,l=n?0:Math.sin(e*.01+this.x*.01)*.5;this.vx+=d*(this.ease*this.easeMultiplier)+a,this.vy+=c*(this.ease*this.easeMultiplier)+l,this.vx*=this.friction,this.vy*=this.friction,this.x+=this.vx,this.y+=this.vy;const u=n?.2:2;this.x+=Math.cos(e*this.floatSpeed+this.floatOffset)*u,this.y+=Math.sin(e*this.floatSpeed+this.floatOffset)*u}draw(e,n="circle",i=0){e.fillStyle=`rgba(${this.baseColor}, ${this.opacity})`;const s=Math.max(.1,this.size*this.sizeMultiplier);if(n==="square")e.fillRect(this.x-s,this.y-s,s*2,s*2);else if(n==="bean"){const m=6+this.size*2*this.sizeMultiplier,r=Math.sin(i*.05+this.x*.01+this.randomSpeed*5),o=.4+.6*((r+1)/2),h=m*.5*o,g=Math.max(.8,s*1.5*(.8+.2*r)),d=this.vx!==0||this.vy!==0?Math.atan2(this.vy,this.vx):0;e.beginPath(),e.ellipse(this.x,this.y,h,g,d,0,Math.PI*2),e.fill()}else e.beginPath(),e.arc(this.x,this.y,s,0,Math.PI*2),e.fill()}}function G(t,e,n){if(e<=0||n<=0)return[];const i=document.createElement("canvas");i.width=e,i.height=n;const s=i.getContext("2d",{willReadFrequently:!0});if(!s)return[];s.clearRect(0,0,e,n);let m=Math.min(e,n)*.65;s.font=`bold ${m}px "Georgia", serif`;const r=s.measureText(t);r.width>e*.9&&(m=m*(e*.9)/r.width,s.font=`bold ${m}px "Georgia", serif`),s.fillStyle="white",s.textAlign="center",s.textBaseline="middle",s.fillText(t,e/2,n/2.05);const h=s.getImageData(0,0,e,n).data,g=[],d=window.innerWidth<600?6:8;for(let c=0;c<n;c+=d)for(let a=0;a<e;a+=d){const l=(c*e+a)*4;h[l+3]>128&&g.push({x:a+(Math.random()-.5)*4,y:c+(Math.random()-.5)*4})}return g}function K(t,e,n){const i=f.useRef(t);return f.useEffect(()=>{i.current=t},[t]),{getPixelsForText:G,updateTextTargets:(m,r,o)=>{var T,b;const h=r||((T=n.current)==null?void 0:T.offsetWidth)||window.innerWidth,g=o||((b=n.current)==null?void 0:b.offsetHeight)||window.innerHeight;if(!m){e.current.forEach(y=>{const w=50+Math.random()*(h-100),P=50+Math.random()*(g-100);(Math.abs(w-y.x)>20||Math.abs(P-y.y)>20)&&(y.vx+=(Math.random()-.5)*20,y.vy+=(Math.random()-.5)*20),y.targetX=w,y.targetY=P});return}const d=G(m,h,g);if(d.length===0)return;const c=h*.15,a=d.map(C=>({pt:C,key:C.x+(Math.random()-.5)*c}));a.sort((C,y)=>C.key-y.key);const l=a.map(C=>C.pt),u=e.current.map((C,y)=>({i:y,key:C.x+(Math.random()-.5)*c}));u.sort((C,y)=>C.key-y.key);const R=u.map(C=>C.i),E=Math.ceil(Math.sqrt(R.length));for(let C=0;C<R.length;C+=E){const y=Math.min(C+E,R.length),w=R.slice(C,y),P=[];for(let S=C;S<y;S++)P.push(l[S%l.length]);w.sort((S,I)=>e.current[S].y-e.current[I].y),P.sort((S,I)=>S.y-I.y);for(let S=0;S<w.length;S++){const I=w[S],z=e.current[I],M=P[S],D=M.x-z.x,x=M.y-z.y;if(Math.abs(D)>20||Math.abs(x)>20){z.vx+=(Math.random()-.5)*20,z.vy+=(Math.random()-.5)*20;const p=(Math.random()>.5?1:-1)*(Math.random()*10+5);z.vx+=Math.sign(x)*p,z.vy-=Math.sign(D)*p}z.targetX=M.x,z.targetY=M.y}}},textRef:i}}const j=t=>{if(!t||typeof t!="string")return"0, 0, 0";try{let e=0,n=0,i=0;if(t.length===4)e=parseInt(t[1]+t[1],16),n=parseInt(t[2]+t[2],16),i=parseInt(t[3]+t[3],16);else if(t.length===7)e=parseInt(t.substring(1,3),16),n=parseInt(t.substring(3,5),16),i=parseInt(t.substring(5,7),16);else return t;return`${e}, ${n}, ${i}`}catch{return"0, 0, 0"}};function ct({text:t,particleColor:e="255, 255, 255",particleSize:n=1,particleDensity:i=1,particleEase:s=1,isMagnet:m=!0,clickMode:r="none",particleShape:o="circle",backgroundColor:h="#050505"}){const g=f.useRef(null),d=f.useRef(null),c=f.useRef([]),a=f.useRef(0),l=f.useRef(0),u=k(d),R=f.useRef({isMagnet:m,clickMode:r,particleShape:o,backgroundColor:h});R.current={isMagnet:m,clickMode:r,particleShape:o,backgroundColor:h};const{updateTextTargets:E,textRef:T}=K(t,c,d),b=()=>Array.isArray(e)?e.map(y=>j(y)):j(e),C=(y,w,P=1)=>{const S=window.innerWidth<600?1500:3e3,I=Math.floor(S*P),z=[],M=b();for(let D=0;D<I;D++){const x=new J(y,w,M);x.sizeMultiplier=n,x.easeMultiplier=s,z.push(x)}c.current=z};return f.useEffect(()=>{if(c.current.length>0){const y=b();c.current.forEach(w=>{w.baseColor=Array.isArray(y)?y[Math.floor(Math.random()*y.length)]:y})}},[e]),f.useEffect(()=>{c.current.length>0&&c.current.forEach(y=>{y.sizeMultiplier=n})},[n]),f.useEffect(()=>{c.current.length>0&&c.current.forEach(y=>{y.easeMultiplier=s})},[s]),f.useEffect(()=>{if(c.current.length>0&&g.current&&d.current){const y=window.innerWidth<600?1500:3e3,w=Math.floor(y*i),P=c.current.length;if(w>P){const S=d.current.getBoundingClientRect(),I=b();for(let z=0;z<w-P;z++){const M=new J(S.width,S.height,I);M.sizeMultiplier=n,c.current.push(M)}E(t)}else w<P&&c.current.splice(w)}},[i]),f.useEffect(()=>{const y=d.current,w=g.current;if(!y||!w)return;const P=M=>{for(const D of M){const{width:x,height:p}=D.contentRect,X=window.devicePixelRatio||1;w.width=x*X,w.height=p*X,w.style.width=`${x}px`,w.style.height=`${p}px`;const Y=w.getContext("2d");Y&&Y.scale(X,X),c.current.length===0&&C(x,p,i),E(t,x,p)}},S=new ResizeObserver(P);S.observe(y);const I=w.getContext("2d"),z=()=>{l.current++;const M=y.getBoundingClientRect(),{isMagnet:D,clickMode:x,particleShape:p,backgroundColor:X}=R.current;if(X==="transparent")I.clearRect(0,0,M.width,M.height),I.globalCompositeOperation="source-over";else{const O=j(X);I.fillStyle=`rgba(${O}, 0.25)`,I.fillRect(0,0,M.width,M.height),I.globalCompositeOperation="screen"}const Y=T.current!=="",L=u.current.active?u.current.x:null,F=u.current.active?u.current.y:null,W=u.current.isDown;for(let O=0;O<c.current.length;O++){const $=c.current[O];$.update(l.current,!!Y,L,F,W,D,x),$.draw(I,p,l.current)}I.globalCompositeOperation="source-over",a.current=requestAnimationFrame(z)};return z(),()=>{S.disconnect(),cancelAnimationFrame(a.current)}},[]),f.useEffect(()=>{E(t)},[t]),q.jsx("div",{ref:d,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden"},children:q.jsx("canvas",{ref:g,style:{display:"block",width:"100%",height:"100%"}})})}A.ParticleCanvas=it,A.TextParticleEngine=ct,A.getMagnetTarget=H,A.useParticleInteraction=k,A.useTextParticles=K,Object.defineProperty(A,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(L,q){typeof exports=="object"&&typeof module<"u"?q(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],q):(L=typeof globalThis<"u"?globalThis:L||self,q(L.JlParticleInteractive={},L.ReactJsxRuntime,L.React))})(this,(function(L,q,f){"use strict";var lt=Object.defineProperty;var ht=(L,q,f)=>q in L?lt(L,q,{enumerable:!0,configurable:!0,writable:!0,value:f}):L[q]=f;var v=(L,q,f)=>ht(L,typeof q!="symbol"?q+"":q,f);function V(t,e,n,i){let s=e+t.baseX,m=n+t.baseY;s+=Math.sin(i*t.randomSpeed+t.baseY*.05)*15*t.z,m+=Math.cos(i*t.randomSpeed+t.baseX*.05)*15*t.z;const r=s-t.x,o=m-t.y,h=r*.06,g=o*.06;return{forceX:h,forceY:g}}function Z(t,e,n,i,s,m){const r=i,o=s,h=t.x-r,g=t.y-o,d=Math.max(Math.sqrt(h*h+g*g),1);let c=0,a=0;const u=Math.sin(d*.02-m*3)*2.5*t.z;c+=h/d*u,a+=g/d*u;const E=Math.cos(d*.01-m*1+t.randomSpeed)*.8*t.z,T=-g/d,b=h/d;return c+=T*E,a+=b*E,{forceX:c,forceY:a,dxCenter:h,dyCenter:g,distToCenter:d}}function tt(t,e){let s=Math.min(e/400,1)*2.5+t.sizeBias*1;s<.4&&(s=0),t.scale+=(s-t.scale)*.15}function et(t,e,n,i,s,m){let r=0,o=0;s==="horizontal"?(r=1,o=0):s==="diagonal"?(r=-e/i,o=-n/i):(r=-n/i,o=e/i);const h=Math.atan2(o,r),g=t.sizeBias>.3?1:0,d=Math.max(0,Math.sin(m*.5+t.randomSpeed*10)),c=Math.sin(m*3+t.baseX*.1+t.randomSpeed)*.5*d*g;r=Math.cos(h+c),o=Math.sin(h+c);const a=Math.max(1-i/400,.1);s==="horizontal"?(t.dirX=t.dirX*(1-.2)+r*.2,t.dirY=t.dirY*(1-.2)+o*.2):(t.dirX=t.dirX*(1-a*.3)+r*a*.3,t.dirY=t.dirY*(1-a*.3)+o*a*.3);const l=Math.sqrt(t.dirX*t.dirX+t.dirY*t.dirY);l>0&&(t.dirX/=l,t.dirY/=l)}class nt{constructor(e,n,i){v(this,"x");v(this,"y");v(this,"baseX");v(this,"baseY");v(this,"z");v(this,"vx");v(this,"vy");v(this,"color");v(this,"targetColor",null);v(this,"colorDelay",0);v(this,"angleTarget");v(this,"randomSpeed");v(this,"sizeBias");v(this,"scale");v(this,"dirX");v(this,"dirY");v(this,"initialized",!1);this.x=e,this.y=n,this.baseX=e,this.baseY=n,this.z=Math.random()*1.5+.2,this.vx=0,this.vy=0,this.color=i,this.angleTarget=Math.random()*Math.PI*2,this.randomSpeed=Math.random()*2+1,this.sizeBias=Math.random(),this.scale=1,this.dirX=Math.cos(this.angleTarget),this.dirY=Math.sin(this.angleTarget)}update(e,n,i,s,m,r,o,h="vertical"){this.initialized||(this.x=i+this.baseX,this.y=s+this.baseY,this.initialized=!0);const{forceX:g,forceY:d,dxCenter:c,dyCenter:a,distToCenter:l}=Z(this,e,n,i,s,o),{forceX:u,forceY:R}=V(this,i,s,o);this.vx+=g+u,this.vy+=d+R,this.vx*=.75,this.vy*=.75,this.x+=this.vx,this.y+=this.vy,tt(this,l),et(this,c,a,l,h,o),this.targetColor&&(this.colorDelay-=1,this.colorDelay<=0&&(this.color=this.targetColor,this.targetColor=null))}draw(e,n,i="bean"){if(this.scale<=.05)return;e.fillStyle=this.color;const s=Math.sqrt(this.baseX*this.baseX+this.baseY*this.baseY),m=Math.max(0,1-s/350);if(e.globalAlpha=Math.min(1,(.5+this.z*.5)*Math.min(this.scale,1)*m),!(e.globalAlpha<=.01))if(i==="circle"){const r=Math.max(.1,2*this.scale*this.z);e.beginPath(),e.arc(this.x,this.y,r,0,Math.PI*2),e.fill()}else if(i==="square"){const r=Math.max(.1,2*this.scale*this.z);e.fillRect(this.x-r,this.y-r,r*2,r*2)}else{const o=(6+this.sizeBias*6)*this.scale,h=Math.sin(n*3.5+this.baseX*.1+this.randomSpeed*5),g=.4+.6*((h+1)/2),d=o*.5*g,c=Math.max(.8,this.z*1.5*this.scale*(.8+.2*h)),a=Math.atan2(this.dirY,this.dirX);e.beginPath(),e.ellipse(this.x,this.y,d,c,a,0,Math.PI*2),e.fill()}}}function j({config:t,backgroundColor:e}){const n=f.useRef(null),i=f.useRef(null),s=f.useRef([]),m=f.useRef(0),r=f.useRef({x:-1e3,y:-1e3,isDown:!1,active:!1}),o=f.useRef({x:0,y:0,initialized:!1});f.useEffect(()=>{const c=l=>{if(!n.current)return;const u=n.current.getBoundingClientRect();r.current.x=l.clientX-u.left,r.current.y=l.clientY-u.top,r.current.active=!0},a=()=>{r.current.active=!1};return window.addEventListener("pointermove",c),window.addEventListener("pointerleave",a),()=>{window.removeEventListener("pointermove",c),window.removeEventListener("pointerleave",a)}},[]);const h=f.useRef(t),g=f.useRef(null),d=f.useRef(0);return f.useEffect(()=>{h.current=t},[t]),f.useEffect(()=>{if(g.current&&(clearInterval(g.current),g.current=null),!t.colors||t.colors.length===0){const a=t.color||"#8B5CF6";s.current.forEach(l=>{l.targetColor=a,l.colorDelay=Math.random()*20});return}const c=a=>{const l=r.current.active?r.current.x:o.current.x,u=r.current.active?r.current.y:o.current.y;s.current.forEach(R=>{const E=R.x-l,T=R.y-u,b=Math.sqrt(E*E+T*T);R.targetColor=a,R.colorDelay=Math.max(0,b*.15)})};return t.colorMode==="mixed"?s.current.forEach((a,l)=>{a.targetColor=t.colors[l%t.colors.length],a.colorDelay=Math.random()*20}):(d.current=0,c(t.colors[0]),t.colors.length>1&&(g.current=setInterval(()=>{const a=t.colors;d.current=(d.current+1)%a.length,c(a[d.current])},3e3))),()=>{g.current&&clearInterval(g.current)}},[t.colors,t.color,t.colorMode]),f.useEffect(()=>{if(!n.current||!i.current||h.current.name==="NONE")return;const c=i.current,a=n.current,l=b=>{for(const C of b){const{width:y,height:w}=C.contentRect,P=window.devicePixelRatio||1;c.width=y*P,c.height=w*P,c.style.width=`${y}px`,c.style.height=`${w}px`;const S=c.getContext("2d");S&&S.scale(P,P),s.current=[];const z=Math.floor(350*(h.current.density??1)),M=h.current.colors,D=h.current.colorMode||"wave";for(let x=0;x<z;x++){const p=Math.sqrt(Math.random())*350,X=Math.random()*Math.PI*2,Y=Math.cos(X)*p,A=Math.sin(X)*p;let F;if(M&&M.length>0)F=D==="mixed"?M[x%M.length]:M[0];else if(h.current.color)F=h.current.color;else{const B=210+Math.max(0,Math.min(1,(Y+350)/700))*130+(Math.random()*15-7.5),$=75+Math.random()*25,N=60+Math.random()*15;F=`hsl(${B}, ${$}%, ${N}%)`}s.current.push(new nt(Y,A,F))}}},u=new ResizeObserver(l);u.observe(a);const R=c.getContext("2d");let E=0;const T=()=>{const b=a.getBoundingClientRect();if(R.clearRect(0,0,b.width,b.height),e!=="transparent"&&(R.fillStyle=e,R.fillRect(0,0,b.width,b.height)),o.current.initialized||(o.current.x=b.width/2,o.current.y=b.height/2,o.current.initialized=!0),h.current.name==="FOLLOW_POINTER"){let C=b.width/2,y=b.height/2;const w=r.current.active?r.current.x:null,P=r.current.active?r.current.y:null;w!==null&&P!==null&&(C=w,y=P);const S=h.current.pointerTrackingSpeed??.06;o.current.x+=(C-o.current.x)*S,o.current.y+=(y-o.current.y)*S;const I=h.current.particleSpeed??1;E+=.012*I;const z=s.current,M=18,D=M*M;for(let x=0;x<z.length;x++){const p=z[x];for(let X=x+1;X<z.length;X++){const Y=z[X],A=p.x-Y.x,F=p.y-Y.y,W=A*A+F*F;if(W<D&&W>0){const B=Math.sqrt(W),$=(M-B)/M,N=A/B*$*.8,O=F/B*$*.8;p.x+=N,p.y+=O,Y.x-=N,Y.y-=O,p.vx+=N*.1,p.vy+=O*.1,Y.vx-=N*.1,Y.vy-=O*.1}}}for(const x of z){x.update(w,P,o.current.x,o.current.y,b.width,b.height,E,h.current.orientation);const p=h.current.shape||"bean";x.draw(R,E,p)}}m.current=requestAnimationFrame(T)};return T(),()=>{u.disconnect(),cancelAnimationFrame(m.current)}},[e,t.density]),t.name==="NONE"?null:q.jsx("div",{ref:n,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden"},children:q.jsx("canvas",{ref:i,style:{display:"block",width:"100%",height:"100%"}})})}class rt{constructor(e,n){v(this,"x");v(this,"y");v(this,"vx");v(this,"vy");v(this,"radius");v(this,"currentColor","#8B5CF6");v(this,"targetColor",null);v(this,"colorDelay",0);this.x=e,this.y=n;const i=Math.random()*Math.PI*2,s=Math.random()*.5+.1;this.vx=Math.cos(i)*s,this.vy=Math.sin(i)*s,this.radius=Math.random()*1.5+1}update(e,n,i,s,m=1,r=.06){if(this.x+=this.vx*m,this.y+=this.vy*m,this.x<0?(this.x=0,this.vx*=-1):this.x>e&&(this.x=e,this.vx*=-1),this.y<0?(this.y=0,this.vy*=-1):this.y>n&&(this.y=n,this.vy*=-1),i!==null&&s!==null){const o=i-this.x,h=s-this.y,g=o*o+h*h,d=150,c=d*d;if(g<c){const a=Math.sqrt(g),l=(d-a)/d,u=r/.06;this.x-=o/a*l*2*u,this.y-=h/a*l*2*u}}this.targetColor&&(this.colorDelay-=1*m,this.colorDelay<=0&&(this.currentColor=this.targetColor,this.targetColor=null))}draw(e,n="circle",i=0){if(e.fillStyle=this.currentColor,n==="square")e.fillRect(this.x-this.radius,this.y-this.radius,this.radius*2,this.radius*2);else if(n==="bean"){const s=Math.sin(i*.05+this.x*.01+this.y*.01),m=this.radius*2*(.8+.4*s),r=this.radius*(.8+.2*s),o=this.vx!==0||this.vy!==0?Math.atan2(this.vy,this.vx):0;e.beginPath(),e.ellipse(this.x,this.y,m,r,o,0,Math.PI*2),e.fill()}else e.beginPath(),e.arc(this.x,this.y,this.radius,0,Math.PI*2),e.fill()}}function H({config:t,backgroundColor:e}){const n=f.useRef(null),i=f.useRef(null),s=f.useRef([]),m=f.useRef(0),r=f.useRef({x:-1e3,y:-1e3,active:!1});f.useEffect(()=>{const d=a=>{if(!n.current)return;const l=n.current.getBoundingClientRect();r.current.x=a.clientX-l.left,r.current.y=a.clientY-l.top,r.current.active=!0},c=()=>{r.current.active=!1};return window.addEventListener("pointermove",d),window.addEventListener("pointerleave",c),()=>{window.removeEventListener("pointermove",d),window.removeEventListener("pointerleave",c)}},[]);const o=f.useRef(t),h=f.useRef(0),g=f.useRef(null);return f.useEffect(()=>{o.current=t},[t]),f.useEffect(()=>{if(g.current&&(clearInterval(g.current),g.current=null),!t.colors||t.colors.length===0){const l=t.color||"#8B5CF6";s.current.forEach(u=>{u.targetColor=l,u.colorDelay=Math.random()*20});return}const d=r.current.active?r.current.x:n.current?n.current.clientWidth/2:0,c=r.current.active?r.current.y:n.current?n.current.clientHeight/2:0,a=l=>{s.current.forEach(u=>{const R=u.x-d,E=u.y-c,T=Math.sqrt(R*R+E*E);u.targetColor=l,u.colorDelay=Math.max(0,T*.2)})};return t.colorMode==="mixed"?s.current.forEach((l,u)=>{l.targetColor=t.colors[u%t.colors.length],l.colorDelay=Math.random()*20}):(h.current=0,a(t.colors[0]),t.colors.length>1&&(g.current=setInterval(()=>{const l=t.colors;h.current=(h.current+1)%l.length,a(l[h.current])},3e3))),()=>{g.current&&clearInterval(g.current)}},[t.colors,t.color,t.colorMode]),f.useEffect(()=>{if(!n.current||!i.current)return;const d=i.current,c=n.current,a=T=>{for(const b of T){const{width:C,height:y}=b.contentRect,w=window.devicePixelRatio||1;d.width=C*w,d.height=y*w,d.style.width=`${C}px`,d.style.height=`${y}px`;const P=d.getContext("2d");P&&P.scale(w,w),s.current=[];const S=C*y,I=o.current.density??1,z=Math.min(Math.floor(300*I),Math.floor(S/6e3*I)),M=o.current.colors,D=o.current.colorMode||"wave";for(let x=0;x<z;x++){const p=new rt(Math.random()*C,Math.random()*y);M&&M.length>0?p.currentColor=D==="mixed"?M[x%M.length]:M[0]:o.current.color&&(p.currentColor=o.current.color),s.current.push(p)}}},l=new ResizeObserver(a);l.observe(c);const u=d.getContext("2d");let R=0;const E=()=>{R+=1;const T=c.getBoundingClientRect();u.clearRect(0,0,T.width,T.height),e!=="transparent"&&(u.fillStyle=e,u.fillRect(0,0,T.width,T.height));const b=r.current.active?r.current.x:null,C=r.current.active?r.current.y:null,y=s.current,w=o.current.shape||"circle",P=120,S=P*P,I=o.current.particleSpeed??1,z=o.current.pointerTrackingSpeed??.06;for(let M=0;M<y.length;M++){const D=y[M];D.update(T.width,T.height,b,C,I,z),u.globalAlpha=1,D.draw(u,w,R);for(let x=M+1;x<y.length;x++){const p=y[x],X=D.x-p.x,Y=D.y-p.y,A=X*X+Y*Y;if(A<S){const F=1-Math.sqrt(A)/P;u.beginPath(),u.moveTo(D.x,D.y),u.lineTo(p.x,p.y),u.strokeStyle=D.currentColor,u.globalAlpha=F*.5,u.lineWidth=1,u.stroke()}}}if(u.globalAlpha=1,b!==null&&C!==null)for(let x=0;x<y.length;x++){const p=y[x],X=p.x-b,Y=p.y-C,A=X*X+Y*Y;if(A<22500){const F=1-Math.sqrt(A)/150;u.beginPath(),u.moveTo(p.x,p.y),u.lineTo(b,C),u.strokeStyle=p.currentColor,u.globalAlpha=F*.8,u.lineWidth=1.5,u.stroke()}}u.globalAlpha=1,m.current=requestAnimationFrame(E)};return E(),()=>{l.disconnect(),cancelAnimationFrame(m.current)}},[e,t.density]),q.jsx("div",{ref:n,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden"},children:q.jsx("canvas",{ref:i,style:{display:"block",width:"100%",height:"100%"}})})}class st{constructor(e,n,i,s){v(this,"x",0);v(this,"y",0);v(this,"baseX");v(this,"baseY");v(this,"vx",0);v(this,"vy",0);v(this,"color");v(this,"size");v(this,"angle");v(this,"dist");v(this,"spring");v(this,"friction");v(this,"targetColor",null);v(this,"colorDelay",0);this.baseX=e,this.baseY=n,this.color=i,this.size=s,this.angle=Math.atan2(n,e),this.dist=Math.sqrt(e*e+n*n);const m=Math.min(1,this.dist/350);this.spring=.15-m*.13,this.friction=.85+m*.1}update(e,n,i,s){const r=1+Math.sin(this.angle*3+i*1.2)*.05+Math.cos(this.angle*5-i*.6)*.03,o=this.dist*s*r,h=Math.cos(this.angle)*o,g=Math.sin(this.angle)*o,d=this.dist*.1*r,c=Math.sin(i*3+this.dist*.05)*d,a=e+h+Math.cos(this.angle+Math.PI/2)*c,l=n+g+Math.sin(this.angle+Math.PI/2)*c,u=a-this.x,R=l-this.y;this.vx+=u*this.spring,this.vy+=R*this.spring,this.vx*=this.friction,this.vy*=this.friction,this.x+=this.vx,this.y+=this.vy,this.targetColor&&(this.colorDelay-=1,this.colorDelay<=0&&(this.color=this.targetColor,this.targetColor=null))}draw(e,n,i){const s=Math.min(1,this.dist/350),m=1+(1-s)*(i-1)*1.5,r=this.size*m;e.globalAlpha=Math.max(.15,1-s*.7),e.fillStyle=this.color,e.beginPath(),n==="circle"?e.arc(this.x,this.y,r,0,Math.PI*2):n==="square"?e.rect(this.x-r,this.y-r,r*2,r*2):e.ellipse(this.x,this.y,r*1.5,r,0,0,Math.PI*2),e.fill()}}function J({config:t,backgroundColor:e}){const n=f.useRef(null),i=f.useRef(null),s=f.useRef([]),m=f.useRef(0),r=f.useRef({x:-1e3,y:-1e3,active:!1}),o=f.useRef({x:0,y:0,initialized:!1,angle:-Math.PI/2});f.useEffect(()=>{const c=a=>{if(!n.current)return;const l=n.current.getBoundingClientRect();r.current.x=a.clientX-l.left,r.current.y=a.clientY-l.top,r.current.active=!0};return window.addEventListener("pointermove",c),()=>{window.removeEventListener("pointermove",c)}},[]);const h=f.useRef(t),g=f.useRef(0),d=f.useRef(null);return f.useEffect(()=>{h.current=t},[t]),f.useEffect(()=>{if(d.current&&(clearInterval(d.current),d.current=null),!t.colors||t.colors.length===0){const a=t.color||"#8B5CF6";s.current.forEach(l=>{l.targetColor=a,l.colorDelay=Math.random()*20});return}const c=a=>{s.current.forEach(l=>{l.targetColor=a,l.colorDelay=Math.max(0,l.dist*.2)})};return t.colorMode==="mixed"?s.current.forEach((a,l)=>{a.targetColor=t.colors[l%t.colors.length],a.colorDelay=Math.max(0,a.dist*.2)+Math.random()*10}):(g.current=0,c(t.colors[0]),t.colors.length>1&&(d.current=setInterval(()=>{const a=t.colors;g.current=(g.current+1)%a.length,c(a[g.current])},3e3))),()=>{d.current&&clearInterval(d.current)}},[t.colors,t.color,t.colorMode]),f.useEffect(()=>{if(!n.current||!i.current)return;const c=i.current,a=n.current,l=b=>{for(const C of b){const{width:y,height:w}=C.contentRect,P=window.devicePixelRatio||1;c.width=y*P,c.height=w*P,c.style.width=`${y}px`,c.style.height=`${w}px`;const S=c.getContext("2d");S&&S.scale(P,P),s.current=[];const z=Math.floor(350*(h.current.density??1)),M=h.current.colors,D=h.current.colorMode||"wave";for(let x=0;x<z;x++){const p=Math.random()*Math.PI*2,X=Math.sqrt(Math.random())*350,Y=Math.cos(p)*X,A=Math.sin(p)*X;let F;if(M&&M.length>0)F=D==="mixed"?M[x%M.length]:M[0];else if(h.current.color)F=h.current.color;else{const $=260+Math.random()*60,N=70+Math.random()*30,O=60+Math.random()*20;F=`hsl(${$}, ${N}%, ${O}%)`}let W=2+Math.random()*2;X>200&&(W*=.6),X<80&&(W*=1.5);const B=new st(Y,A,F,W);B.x=y/2,B.y=w/2,s.current.push(B)}}},u=new ResizeObserver(l);u.observe(a);const R=c.getContext("2d");let E=0;const T=()=>{const b=a.getBoundingClientRect();R.globalCompositeOperation="source-over",e==="transparent"?R.clearRect(0,0,b.width,b.height):(R.fillStyle=e,R.fillRect(0,0,b.width,b.height)),o.current.initialized||(o.current.x=b.width/2,o.current.y=b.height/2,o.current.initialized=!0);let C=b.width/2,y=b.height/2;const w=r.current.active?r.current.x:null,P=r.current.active?r.current.y:null;w!==null&&P!==null&&(C=w,y=P);const S=C-o.current.x,I=y-o.current.y,z=Math.sqrt(S*S+I*I);let M=0,D=0;z>1?(M=S/z,D=I/z,o.current.angle=Math.atan2(D,M)):(M=Math.cos(o.current.angle),D=Math.sin(o.current.angle));const x=h.current.particleSpeed??1;E+=.02*x;const p=E%4;let X=1,Y=1,A=0;if(p<1){const O=p;X=1-Math.sin(O*Math.PI)*.3,Y=1+Math.sin(O*Math.PI)*.15,A=Math.sin(O*Math.PI)*12}else{const O=(p-1)/3;X=.7+.3*Math.sin(O*Math.PI/2),Y=1,A=0}const F=h.current.pointerTrackingSpeed??.02;o.current.x+=S*F,o.current.y+=I*F,o.current.x+=M*A*x,o.current.y+=D*A*x;const B=200*(Y+(p<1?p*.15:0)),$=R.createRadialGradient(o.current.x,o.current.y,0,o.current.x,o.current.y,B);$.addColorStop(0,"rgba(80, 150, 255, 0.05)"),$.addColorStop(1,"rgba(80, 150, 255, 0)"),R.fillStyle=$,R.beginPath(),R.arc(o.current.x,o.current.y,B,0,Math.PI*2),R.fill();const N=s.current;R.globalCompositeOperation="source-over";for(const O of N){O.update(o.current.x,o.current.y,E,X);const at=h.current.shape||"circle";O.draw(R,at,Y)}m.current=requestAnimationFrame(T)};return T(),()=>{u.disconnect(),cancelAnimationFrame(m.current)}},[e,t.density]),q.jsx("div",{ref:n,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden",pointerEvents:"none"},children:q.jsx("canvas",{ref:i,style:{display:"block",width:"100%",height:"100%",pointerEvents:"none"}})})}const ot={position:"relative",border:"1px solid rgba(255, 255, 255, 0.1)",borderRadius:"1rem",overflow:"hidden",boxShadow:"0 25px 50px -12px rgba(0, 0, 0, 0.25)",transition:"all 300ms ease-out"};function it({children:t,width:e="100%",height:n="60vh",backgroundColor:i="#050505",className:s="",style:m,background:r={name:"NONE"}}){return q.jsxs("div",{className:s,style:{...ot,width:e,height:n,backgroundColor:i,...m},children:[r.name==="FOLLOW_POINTER"&&q.jsx(j,{config:r,backgroundColor:i}),r.name==="NET"&&q.jsx(H,{config:r,backgroundColor:i}),r.name==="JELLYFISH"&&q.jsx(J,{config:r,backgroundColor:i}),q.jsx("div",{style:{position:"relative",zIndex:10,width:"100%",height:"100%"},children:t})]})}function G(t){const e=f.useRef({x:-1e3,y:-1e3,isDown:!1,active:!1});return f.useEffect(()=>{const n=t.current;if(!n)return;const i=o=>{const h=n.getBoundingClientRect();e.current.x=o.clientX-h.left,e.current.y=o.clientY-h.top,e.current.active=!0},s=()=>{e.current.active=!1},m=()=>{e.current.isDown=!0},r=()=>{e.current.isDown=!1};return n.addEventListener("pointermove",i),n.addEventListener("pointerleave",s),n.addEventListener("pointerdown",m),n.addEventListener("pointerup",r),n.style.touchAction="none",()=>{n.removeEventListener("pointermove",i),n.removeEventListener("pointerleave",s),n.removeEventListener("pointerdown",m),n.removeEventListener("pointerup",r)}},[t]),e}function K(t,e,n,i,s,m,r,o,h){if(s===null||m===null||!o&&h==="none")return{x:n,y:i};let g=n,d=i;const c=s-t,a=m-e,l=c*c+a*a,u=Math.sqrt(l);if(o&&!r&&l<3e4){const E=(3e4-l)/3e4;g+=c*E*.15,d+=a*E*.15}if(r&&h!=="none"){if(h==="attract"){if(l<3e4){const E=(3e4-l)/3e4;g+=c*E*.8,d+=a*E*.8}}else if(h==="repel"&&l<5e4&&u>0){const E=Math.pow(Math.max(0,5e4-l)/5e4,1.2);g-=c/u*E*400,d-=a/u*E*400}}return{x:g,y:d}}class _{constructor(e,n,i="255, 255, 255"){v(this,"x");v(this,"y");v(this,"vx");v(this,"vy");v(this,"targetX");v(this,"targetY");v(this,"baseColor");v(this,"opacity");v(this,"size");v(this,"sizeMultiplier");v(this,"friction");v(this,"ease");v(this,"easeMultiplier");v(this,"floatSpeed");v(this,"floatOffset");v(this,"randomSpeed");this.x=Math.random()*e,this.y=Math.random()*n,this.targetX=this.x,this.targetY=this.y,this.vx=0,this.vy=0,this.size=Math.random()*1.8+.5,this.sizeMultiplier=1,this.baseColor=Array.isArray(i)?i[Math.floor(Math.random()*i.length)]:i,this.opacity=.4+Math.random()*.6,this.friction=.82+Math.random()*.1,this.ease=.03+Math.random()*.05,this.easeMultiplier=1,this.floatSpeed=Math.random()*.02+.005,this.floatOffset=Math.random()*Math.PI*2,this.randomSpeed=Math.random()}update(e,n,i=null,s=null,m=!1,r=!0,o="none"){const{x:h,y:g}=K(this.x,this.y,this.targetX,this.targetY,i,s,m,r,o),d=h-this.x,c=g-this.y,a=n?0:Math.cos(e*.01+this.y*.01)*.5,l=n?0:Math.sin(e*.01+this.x*.01)*.5;this.vx+=d*(this.ease*this.easeMultiplier)+a,this.vy+=c*(this.ease*this.easeMultiplier)+l,this.vx*=this.friction,this.vy*=this.friction,this.x+=this.vx,this.y+=this.vy;const u=n?.2:2;this.x+=Math.cos(e*this.floatSpeed+this.floatOffset)*u,this.y+=Math.sin(e*this.floatSpeed+this.floatOffset)*u}draw(e,n="circle",i=0){e.fillStyle=`rgba(${this.baseColor}, ${this.opacity})`;const s=Math.max(.1,this.size*this.sizeMultiplier);if(n==="square")e.fillRect(this.x-s,this.y-s,s*2,s*2);else if(n==="bean"){const m=6+this.size*2*this.sizeMultiplier,r=Math.sin(i*.05+this.x*.01+this.randomSpeed*5),o=.4+.6*((r+1)/2),h=m*.5*o,g=Math.max(.8,s*1.5*(.8+.2*r)),d=this.vx!==0||this.vy!==0?Math.atan2(this.vy,this.vx):0;e.beginPath(),e.ellipse(this.x,this.y,h,g,d,0,Math.PI*2),e.fill()}else e.beginPath(),e.arc(this.x,this.y,s,0,Math.PI*2),e.fill()}}function U(t,e,n){if(e<=0||n<=0)return[];const i=document.createElement("canvas");i.width=e,i.height=n;const s=i.getContext("2d",{willReadFrequently:!0});if(!s)return[];s.clearRect(0,0,e,n);let m=Math.min(e,n)*.65;s.font=`bold ${m}px "Georgia", serif`;const r=s.measureText(t);r.width>e*.9&&(m=m*(e*.9)/r.width,s.font=`bold ${m}px "Georgia", serif`),s.fillStyle="white",s.textAlign="center",s.textBaseline="middle",s.fillText(t,e/2,n/2.05);const h=s.getImageData(0,0,e,n).data,g=[],d=window.innerWidth<600?6:8;for(let c=0;c<n;c+=d)for(let a=0;a<e;a+=d){const l=(c*e+a)*4;h[l+3]>128&&g.push({x:a+(Math.random()-.5)*4,y:c+(Math.random()-.5)*4})}return g}function Q(t,e,n){const i=f.useRef(t);return f.useEffect(()=>{i.current=t},[t]),{getPixelsForText:U,updateTextTargets:(m,r,o)=>{var T,b;const h=r||((T=n.current)==null?void 0:T.offsetWidth)||window.innerWidth,g=o||((b=n.current)==null?void 0:b.offsetHeight)||window.innerHeight;if(!m){e.current.forEach(y=>{const w=50+Math.random()*(h-100),P=50+Math.random()*(g-100);(Math.abs(w-y.x)>20||Math.abs(P-y.y)>20)&&(y.vx+=(Math.random()-.5)*20,y.vy+=(Math.random()-.5)*20),y.targetX=w,y.targetY=P});return}const d=U(m,h,g);if(d.length===0)return;const c=h*.15,a=d.map(C=>({pt:C,key:C.x+(Math.random()-.5)*c}));a.sort((C,y)=>C.key-y.key);const l=a.map(C=>C.pt),u=e.current.map((C,y)=>({i:y,key:C.x+(Math.random()-.5)*c}));u.sort((C,y)=>C.key-y.key);const R=u.map(C=>C.i),E=Math.ceil(Math.sqrt(R.length));for(let C=0;C<R.length;C+=E){const y=Math.min(C+E,R.length),w=R.slice(C,y),P=[];for(let S=C;S<y;S++)P.push(l[S%l.length]);w.sort((S,I)=>e.current[S].y-e.current[I].y),P.sort((S,I)=>S.y-I.y);for(let S=0;S<w.length;S++){const I=w[S],z=e.current[I],M=P[S],D=M.x-z.x,x=M.y-z.y;if(Math.abs(D)>20||Math.abs(x)>20){z.vx+=(Math.random()-.5)*20,z.vy+=(Math.random()-.5)*20;const p=(Math.random()>.5?1:-1)*(Math.random()*10+5);z.vx+=Math.sign(x)*p,z.vy-=Math.sign(D)*p}z.targetX=M.x,z.targetY=M.y}}},textRef:i}}const k=t=>{if(!t||typeof t!="string")return"0, 0, 0";try{let e=0,n=0,i=0;if(t.length===4)e=parseInt(t[1]+t[1],16),n=parseInt(t[2]+t[2],16),i=parseInt(t[3]+t[3],16);else if(t.length===7)e=parseInt(t.substring(1,3),16),n=parseInt(t.substring(3,5),16),i=parseInt(t.substring(5,7),16);else return t;return`${e}, ${n}, ${i}`}catch{return"0, 0, 0"}};function ct({text:t,particleColor:e="255, 255, 255",particleSize:n=1,particleDensity:i=1,particleEase:s=1,isMagnet:m=!0,clickMode:r="none",particleShape:o="circle",backgroundColor:h="#050505"}){const g=f.useRef(null),d=f.useRef(null),c=f.useRef([]),a=f.useRef(0),l=f.useRef(0),u=G(d),R=f.useRef({isMagnet:m,clickMode:r,particleShape:o,backgroundColor:h});R.current={isMagnet:m,clickMode:r,particleShape:o,backgroundColor:h};const{updateTextTargets:E,textRef:T}=Q(t,c,d),b=()=>Array.isArray(e)?e.map(y=>k(y)):k(e),C=(y,w,P=1)=>{const S=window.innerWidth<600?1500:3e3,I=Math.floor(S*P),z=[],M=b();for(let D=0;D<I;D++){const x=new _(y,w,M);x.sizeMultiplier=n,x.easeMultiplier=s,z.push(x)}c.current=z};return f.useEffect(()=>{if(c.current.length>0){const y=b();c.current.forEach(w=>{w.baseColor=Array.isArray(y)?y[Math.floor(Math.random()*y.length)]:y})}},[e]),f.useEffect(()=>{c.current.length>0&&c.current.forEach(y=>{y.sizeMultiplier=n})},[n]),f.useEffect(()=>{c.current.length>0&&c.current.forEach(y=>{y.easeMultiplier=s})},[s]),f.useEffect(()=>{if(c.current.length>0&&g.current&&d.current){const y=window.innerWidth<600?1500:3e3,w=Math.floor(y*i),P=c.current.length;if(w>P){const S=d.current.getBoundingClientRect(),I=b();for(let z=0;z<w-P;z++){const M=new _(S.width,S.height,I);M.sizeMultiplier=n,c.current.push(M)}E(t)}else w<P&&c.current.splice(w)}},[i]),f.useEffect(()=>{const y=d.current,w=g.current;if(!y||!w)return;const P=M=>{for(const D of M){const{width:x,height:p}=D.contentRect,X=window.devicePixelRatio||1;w.width=x*X,w.height=p*X,w.style.width=`${x}px`,w.style.height=`${p}px`;const Y=w.getContext("2d");Y&&Y.scale(X,X),c.current.length===0&&C(x,p,i),E(t,x,p)}},S=new ResizeObserver(P);S.observe(y);const I=w.getContext("2d"),z=()=>{l.current++;const M=y.getBoundingClientRect(),{isMagnet:D,clickMode:x,particleShape:p,backgroundColor:X}=R.current;if(X==="transparent")I.clearRect(0,0,M.width,M.height),I.globalCompositeOperation="source-over";else{const B=k(X);I.fillStyle=`rgba(${B}, 0.25)`,I.fillRect(0,0,M.width,M.height),I.globalCompositeOperation="screen"}const Y=T.current!=="",A=u.current.active?u.current.x:null,F=u.current.active?u.current.y:null,W=u.current.isDown;for(let B=0;B<c.current.length;B++){const $=c.current[B];$.update(l.current,!!Y,A,F,W,D,x),$.draw(I,p,l.current)}I.globalCompositeOperation="source-over",a.current=requestAnimationFrame(z)};return z(),()=>{S.disconnect(),cancelAnimationFrame(a.current)}},[]),f.useEffect(()=>{E(t)},[t]),q.jsx("div",{ref:d,style:{position:"absolute",inset:0,zIndex:0,overflow:"hidden"},children:q.jsx("canvas",{ref:g,style:{display:"block",width:"100%",height:"100%"}})})}L.BackgroundParticleEngine=j,L.JellyfishParticleEngine=J,L.NetParticleEngine=H,L.ParticleCanvas=it,L.TextParticleEngine=ct,L.getMagnetTarget=K,L.useParticleInteraction=G,L.useTextParticles=Q,Object.defineProperty(L,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jl-particle-interactive",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
6
|
-
"author":
|
|
5
|
+
"description": "Canvas-based React library for particle text animations and interactive backgrounds — spring physics, magnetic hover, click attract/repel, NET/JELLYFISH modes. TypeScript. Zero dependencies.",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "cjorgeluis122333",
|
|
8
|
+
"url": "https://github.com/cjorgeluis122333"
|
|
9
|
+
},
|
|
7
10
|
"license": "MIT",
|
|
11
|
+
"homepage": "https://cjorgeluis122333.github.io/jl-particles-interactive",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/cjorgeluis122333/jl-particles-interactive"
|
|
15
|
+
},
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/cjorgeluis122333/jl-particles-interactive/issues"
|
|
18
|
+
},
|
|
8
19
|
"type": "module",
|
|
9
20
|
"main": "./dist/jl-particle-interactive.umd.cjs",
|
|
10
21
|
"module": "./dist/jl-particle-interactive.js",
|
|
@@ -17,7 +28,8 @@
|
|
|
17
28
|
}
|
|
18
29
|
},
|
|
19
30
|
"files": [
|
|
20
|
-
"dist"
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md"
|
|
21
33
|
],
|
|
22
34
|
"scripts": {
|
|
23
35
|
"build": "vite build",
|
|
@@ -29,7 +41,21 @@
|
|
|
29
41
|
"animation",
|
|
30
42
|
"canvas",
|
|
31
43
|
"interactive",
|
|
32
|
-
"text"
|
|
44
|
+
"text",
|
|
45
|
+
"particle-text",
|
|
46
|
+
"text-animation",
|
|
47
|
+
"particle-background",
|
|
48
|
+
"canvas-animation",
|
|
49
|
+
"react-particles",
|
|
50
|
+
"magnetic",
|
|
51
|
+
"hover-effect",
|
|
52
|
+
"cursor-effect",
|
|
53
|
+
"spring-physics",
|
|
54
|
+
"hero",
|
|
55
|
+
"hero-section",
|
|
56
|
+
"typescript",
|
|
57
|
+
"zero-dependencies",
|
|
58
|
+
"particle-network"
|
|
33
59
|
],
|
|
34
60
|
"peerDependencies": {
|
|
35
61
|
"react": ">=18.0.0",
|