@uibit/particles 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,687 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { html } from 'lit';
8
+ import { customElement, UIBitElement, ResizeController, LoopController } from '@uibit/core';
9
+ import { property, query } from 'lit/decorators.js';
10
+ import { styles } from './styles';
11
+ /**
12
+ * An interactive canvas effect element. Creates dynamic background visuals
13
+ * across many modes: particles, abstract art, grids, flow fields, and more.
14
+ *
15
+ * @cssprop [--uibit-particles-color=#6b7280] - Space or comma-separated list of colors
16
+ * @cssprop [--uibit-particles-line-color=#e5e7eb] - Color of connecting lines
17
+ * @cssprop [--uibit-particles-opacity=1] - Opacity of the canvas overlay
18
+ * @cssprop [--uibit-particles-min-size=0.0625rem] - Minimum particle size
19
+ * @cssprop [--uibit-particles-max-size=0.1875rem] - Maximum particle size
20
+ */
21
+ let Particles = class Particles extends UIBitElement {
22
+ constructor() {
23
+ super(...arguments);
24
+ /** Number of particles / elements in the field */
25
+ this.count = 50;
26
+ /** Speed multiplier */
27
+ this.speed = 1;
28
+ /**
29
+ * Animation mode.
30
+ * 'float' | 'snow' | 'rain' | 'neural' | 'matrix' |
31
+ * 'wave' | 'vortex' | 'smoke' | 'bubbles' | 'grid' | 'aurora' | 'noise' | 'rings'
32
+ */
33
+ this.mode = 'float';
34
+ /** Draw constellation lines between nearby particles */
35
+ this.connect = false;
36
+ /** Max distance for connecting lines */
37
+ this.connectDistance = 100;
38
+ /** Hover interaction: 'repel' | 'attract' | 'grab' | 'none' */
39
+ this.hoverEffect = 'repel';
40
+ /** Radius of the mouse hover interaction zone */
41
+ this.interactiveRadius = 100;
42
+ this._particles = [];
43
+ this._pulses = [];
44
+ this._mouse = { x: null, y: null };
45
+ this._ctx = null;
46
+ this._lineColor = '#e5e7eb';
47
+ this._colorCheckTick = 0;
48
+ this._matrixChars = [];
49
+ this._time = 0;
50
+ this._resize = new ResizeController(this, {
51
+ callback: (entry) => {
52
+ const { width, height } = entry.contentRect;
53
+ this._resizeCanvas(width, height);
54
+ }
55
+ });
56
+ this._loop = new LoopController(this, {
57
+ autoStart: false,
58
+ callback: () => this._updateAndDraw()
59
+ });
60
+ }
61
+ static { this.styles = styles; }
62
+ firstUpdated() {
63
+ if (this._canvas && typeof this._canvas.getContext === 'function') {
64
+ this._ctx = this._canvas.getContext('2d');
65
+ }
66
+ this._setupMouseListeners();
67
+ this._initParticles();
68
+ this._loop.start();
69
+ }
70
+ updated(changedProperties) {
71
+ super.updated(changedProperties);
72
+ if (changedProperties.has('count') || changedProperties.has('mode')) {
73
+ this._initParticles();
74
+ }
75
+ }
76
+ _resizeCanvas(width, height) {
77
+ if (!this._canvas)
78
+ return;
79
+ const dpr = window.devicePixelRatio || 1;
80
+ this._canvas.width = width * dpr;
81
+ this._canvas.height = height * dpr;
82
+ this._canvas.style.width = `${width}px`;
83
+ this._canvas.style.height = `${height}px`;
84
+ if (this._ctx) {
85
+ this._ctx.scale(dpr, dpr);
86
+ }
87
+ this._initParticles();
88
+ }
89
+ _setupMouseListeners() {
90
+ this.listen(this, 'pointermove', (e) => {
91
+ const rect = this.getBoundingClientRect();
92
+ this._mouse.x = e.clientX - rect.left;
93
+ this._mouse.y = e.clientY - rect.top;
94
+ });
95
+ this.listen(this, 'pointerleave', () => {
96
+ this._mouse.x = null;
97
+ this._mouse.y = null;
98
+ });
99
+ }
100
+ _parseColors() {
101
+ const raw = this.getCssPropertyValue('--uibit-particles-color') || '#6b7280';
102
+ const colors = raw.split(/[\s,]+/).map(c => c.trim()).filter(c => c.length > 0);
103
+ return colors.length > 0 ? colors : ['#6b7280'];
104
+ }
105
+ _parseSize(prop, defaultValue) {
106
+ const val = this.getCssPropertyValue(prop);
107
+ if (!val)
108
+ return defaultValue;
109
+ const num = parseFloat(val);
110
+ if (isNaN(num))
111
+ return defaultValue;
112
+ if (val.includes('rem'))
113
+ return num * 16;
114
+ return num;
115
+ }
116
+ _initParticles() {
117
+ if (!this._canvas)
118
+ return;
119
+ const width = this._canvas.clientWidth || this.clientWidth || 300;
120
+ const height = this._canvas.clientHeight || this.clientHeight || 150;
121
+ const minSize = this._parseSize('--uibit-particles-min-size', 1);
122
+ const maxSize = this._parseSize('--uibit-particles-max-size', 3);
123
+ const colors = this._parseColors();
124
+ this._particles = [];
125
+ this._pulses = [];
126
+ this._matrixChars = [];
127
+ this._time = 0;
128
+ for (let i = 0; i < this.count; i++) {
129
+ // Aurora only needs a few bands regardless of count
130
+ if (this.mode === 'aurora' && i >= 8)
131
+ break;
132
+ const baseRadius = Math.random() * (maxSize - minSize) + minSize;
133
+ const color = colors[Math.floor(Math.random() * colors.length)];
134
+ const baseSpeed = Math.random() * 0.5 + 0.5;
135
+ let x = Math.random() * width;
136
+ let y = Math.random() * height;
137
+ let vx = 0, vy = 0;
138
+ let radius = baseRadius;
139
+ let originX;
140
+ let originY;
141
+ let angle;
142
+ let angularVelocity;
143
+ let orbitRadius;
144
+ let amplitude;
145
+ let opacity;
146
+ let life;
147
+ let maxLife;
148
+ let phase;
149
+ let prevX;
150
+ let prevY;
151
+ if (this.mode === 'float') {
152
+ vx = (Math.random() - 0.5) * 1.5;
153
+ vy = (Math.random() - 0.5) * 1.5;
154
+ }
155
+ else if (this.mode === 'snow') {
156
+ vx = (Math.random() - 0.5) * 0.4;
157
+ vy = Math.random() * 0.8 + 0.4;
158
+ }
159
+ else if (this.mode === 'rain') {
160
+ vx = -0.5 - Math.random() * 0.5;
161
+ vy = Math.random() * 4 + 4;
162
+ }
163
+ else if (this.mode === 'neural') {
164
+ originX = Math.random() * width;
165
+ originY = Math.random() * height;
166
+ x = originX;
167
+ y = originY;
168
+ vx = (Math.random() - 0.5) * 0.3;
169
+ vy = (Math.random() - 0.5) * 0.3;
170
+ }
171
+ else if (this.mode === 'matrix') {
172
+ vy = Math.random() * 1.5 + 1.5;
173
+ this._matrixChars.push(Math.random() < 0.5 ? '0' : '1');
174
+ }
175
+ else if (this.mode === 'wave') {
176
+ const numRows = Math.max(2, Math.min(8, Math.floor(this.count / 8) + 2));
177
+ const rowIdx = i % numRows;
178
+ const rowHeight = height / numRows;
179
+ originY = (rowIdx + 0.5) * rowHeight;
180
+ amplitude = rowHeight * 0.35;
181
+ angle = Math.random() * Math.PI * 2;
182
+ vx = Math.random() * 0.8 + 0.3;
183
+ vy = Math.random() * 0.008 + 0.004;
184
+ y = originY;
185
+ }
186
+ else if (this.mode === 'vortex') {
187
+ const maxOrbit = Math.min(width, height) * 0.44;
188
+ orbitRadius = Math.sqrt(Math.random()) * maxOrbit + 5;
189
+ angle = Math.random() * Math.PI * 2;
190
+ const dir = Math.random() < 0.75 ? 1 : -1;
191
+ angularVelocity = dir * (30 / Math.max(orbitRadius, 5));
192
+ const cx = width / 2, cy = height / 2;
193
+ x = cx + orbitRadius * Math.cos(angle);
194
+ y = cy + orbitRadius * Math.sin(angle);
195
+ }
196
+ else if (this.mode === 'smoke') {
197
+ const smokeRadius = 4 + Math.random() * 10;
198
+ radius = smokeRadius;
199
+ amplitude = smokeRadius; // remember initial radius for reset
200
+ maxLife = 220 + Math.random() * 160;
201
+ life = Math.random() * maxLife;
202
+ opacity = life / maxLife;
203
+ vx = (Math.random() - 0.5) * 0.35;
204
+ vy = -(Math.random() * 0.4 + 0.15);
205
+ x = width * 0.2 + Math.random() * width * 0.6;
206
+ y = height - (life * Math.abs(vy));
207
+ }
208
+ else if (this.mode === 'bubbles') {
209
+ radius = 6 + Math.random() * 22;
210
+ vx = (Math.random() - 0.5) * 0.4;
211
+ vy = -(Math.random() * 0.5 + 0.2);
212
+ opacity = 0.3 + Math.random() * 0.4;
213
+ y = height + Math.random() * height;
214
+ }
215
+ else if (this.mode === 'grid') {
216
+ const cols = Math.max(2, Math.round(Math.sqrt(this.count * (width / height))));
217
+ const rows = Math.max(2, Math.ceil(this.count / cols));
218
+ const cellW = width / cols;
219
+ const cellH = height / rows;
220
+ const col = i % cols;
221
+ const row = Math.floor(i / cols);
222
+ originX = (col + 0.5) * cellW;
223
+ originY = (row + 0.5) * cellH;
224
+ x = originX;
225
+ y = originY;
226
+ phase = Math.random() * Math.PI * 2;
227
+ }
228
+ else if (this.mode === 'aurora') {
229
+ const bandCount = Math.min(this.count, 8);
230
+ originY = height * 0.08 + (i / bandCount) * height * 0.78;
231
+ amplitude = 22 + Math.random() * 55;
232
+ angle = Math.random() * Math.PI * 2;
233
+ vy = Math.random() * 0.007 + 0.003;
234
+ vx = Math.random() * 0.002 + 0.001;
235
+ y = originY;
236
+ x = 0;
237
+ }
238
+ else if (this.mode === 'noise') {
239
+ prevX = x;
240
+ prevY = y;
241
+ }
242
+ else if (this.mode === 'rings') {
243
+ maxLife = 45 + Math.random() * 85;
244
+ radius = Math.random() * maxLife;
245
+ vx = 0.5 + Math.random() * 1.3;
246
+ }
247
+ this._particles.push({
248
+ x, y, vx, vy, radius, color, baseSpeed,
249
+ originX, originY, angle, angularVelocity, orbitRadius,
250
+ amplitude, opacity, life, maxLife, phase, prevX, prevY,
251
+ });
252
+ }
253
+ }
254
+ _updateAndDraw() {
255
+ const ctx = this._ctx;
256
+ const canvas = this._canvas;
257
+ if (!ctx || !canvas)
258
+ return;
259
+ const width = canvas.clientWidth;
260
+ const height = canvas.clientHeight;
261
+ const t = ++this._time;
262
+ ctx.clearRect(0, 0, width, height);
263
+ if (this._colorCheckTick % 30 === 0) {
264
+ this._lineColor = this.getCssPropertyValue('--uibit-particles-line-color') || '#e5e7eb';
265
+ const colors = this._parseColors();
266
+ for (const p of this._particles) {
267
+ if (p && p.color && !colors.includes(p.color)) {
268
+ p.color = colors[Math.floor(Math.random() * colors.length)];
269
+ }
270
+ }
271
+ }
272
+ this._colorCheckTick++;
273
+ const particles = this._particles;
274
+ const mouse = this._mouse;
275
+ const speed = this.speed;
276
+ const hoverEffect = this.hoverEffect;
277
+ const interactiveRadius = this.interactiveRadius;
278
+ const connectDist = this.connectDistance;
279
+ // ── Neural: draw lines + pulses before dots ──────────────────────────────
280
+ if (this.mode === 'neural') {
281
+ ctx.strokeStyle = this._lineColor;
282
+ if (Math.random() < 0.03 * speed && particles.length > 1) {
283
+ const p1 = particles[Math.floor(Math.random() * particles.length)];
284
+ if (p1) {
285
+ const neighbors = [];
286
+ for (const p2 of particles) {
287
+ if (p2 && p2 !== p1) {
288
+ const dx = p1.x - p2.x, dy = p1.y - p2.y;
289
+ if (Math.sqrt(dx * dx + dy * dy) < connectDist)
290
+ neighbors.push(p2);
291
+ }
292
+ }
293
+ if (neighbors.length > 0) {
294
+ const p2 = neighbors[Math.floor(Math.random() * neighbors.length)];
295
+ if (p2) {
296
+ this._pulses.push({ startX: p1.x, startY: p1.y, endX: p2.x, endY: p2.y, progress: 0, speed: 0.02 + Math.random() * 0.03 });
297
+ }
298
+ }
299
+ }
300
+ }
301
+ for (let i = 0; i < particles.length; i++) {
302
+ const p1 = particles[i];
303
+ if (!p1)
304
+ continue;
305
+ for (let j = i + 1; j < particles.length; j++) {
306
+ const p2 = particles[j];
307
+ if (!p2)
308
+ continue;
309
+ const dx = p1.x - p2.x, dy = p1.y - p2.y;
310
+ const dist = Math.sqrt(dx * dx + dy * dy);
311
+ if (dist < connectDist) {
312
+ const alpha = (1 - dist / connectDist) * 0.4;
313
+ ctx.beginPath();
314
+ ctx.moveTo(p1.x, p1.y);
315
+ ctx.lineTo(p2.x, p2.y);
316
+ ctx.lineWidth = alpha * 0.7;
317
+ ctx.globalAlpha = alpha;
318
+ ctx.stroke();
319
+ ctx.globalAlpha = 1;
320
+ }
321
+ }
322
+ }
323
+ ctx.fillStyle = this._lineColor;
324
+ for (let j = this._pulses.length - 1; j >= 0; j--) {
325
+ const pulse = this._pulses[j];
326
+ if (!pulse)
327
+ continue;
328
+ pulse.progress += pulse.speed * speed;
329
+ if (pulse.progress >= 1) {
330
+ this._pulses.splice(j, 1);
331
+ continue;
332
+ }
333
+ const px = pulse.startX + (pulse.endX - pulse.startX) * pulse.progress;
334
+ const py = pulse.startY + (pulse.endY - pulse.startY) * pulse.progress;
335
+ ctx.beginPath();
336
+ ctx.arc(px, py, 2, 0, Math.PI * 2);
337
+ ctx.fill();
338
+ }
339
+ }
340
+ // ── Aurora: draw banded curtains, no individual dots ─────────────────────
341
+ if (this.mode === 'aurora') {
342
+ for (const p of particles) {
343
+ if (!p)
344
+ continue;
345
+ p.angle = (p.angle || 0) + (p.vy || 0.005) * speed;
346
+ if (p.originY !== undefined) {
347
+ p.y = p.originY + Math.sin((p.angle || 0) * 0.4) * 18;
348
+ }
349
+ const amp = p.amplitude || 30;
350
+ const ph = p.angle || 0;
351
+ const steps = Math.max(20, Math.ceil(width / 12));
352
+ const stepW = width / steps;
353
+ ctx.save();
354
+ ctx.filter = 'blur(20px)';
355
+ ctx.globalAlpha = 0.38;
356
+ ctx.fillStyle = p.color || '#6b7280';
357
+ ctx.beginPath();
358
+ ctx.moveTo(0, p.y + amp * Math.sin(ph));
359
+ for (let s = 1; s <= steps; s++) {
360
+ const sx = s * stepW;
361
+ ctx.lineTo(sx, p.y + amp * Math.sin(sx * 0.009 + ph));
362
+ }
363
+ for (let s = steps; s >= 0; s--) {
364
+ const sx = s * stepW;
365
+ ctx.lineTo(sx, p.y + amp * 1.7 * Math.sin(sx * 0.007 + ph + 1.1));
366
+ }
367
+ ctx.closePath();
368
+ ctx.fill();
369
+ ctx.restore();
370
+ }
371
+ return;
372
+ }
373
+ // ── Per-particle update + draw ────────────────────────────────────────────
374
+ for (let i = 0; i < particles.length; i++) {
375
+ const p = particles[i];
376
+ if (!p)
377
+ continue;
378
+ // ── Update ──
379
+ if (this.mode === 'neural') {
380
+ p.x += p.vx * speed;
381
+ p.y += p.vy * speed;
382
+ if (p.originX !== undefined && p.originY !== undefined) {
383
+ const dx = p.x - p.originX, dy = p.y - p.originY;
384
+ const dist = Math.sqrt(dx * dx + dy * dy);
385
+ if (dist > 30) {
386
+ p.vx -= dx * 0.002;
387
+ p.vy -= dy * 0.002;
388
+ }
389
+ }
390
+ if (p.x < 0)
391
+ p.x = width;
392
+ if (p.x > width)
393
+ p.x = 0;
394
+ if (p.y < 0)
395
+ p.y = height;
396
+ if (p.y > height)
397
+ p.y = 0;
398
+ }
399
+ else if (this.mode === 'wave') {
400
+ p.x += (p.vx || 1) * speed;
401
+ p.angle = (p.angle || 0) + (p.vy || 0.005) * speed;
402
+ if (p.x > width)
403
+ p.x = 0;
404
+ if (p.x < 0)
405
+ p.x = width;
406
+ if (p.originY !== undefined) {
407
+ p.y = p.originY + (p.amplitude || 20) * Math.sin(p.x * 0.018 + (p.angle || 0));
408
+ }
409
+ }
410
+ else if (this.mode === 'vortex') {
411
+ if (p.orbitRadius !== undefined && p.angularVelocity !== undefined) {
412
+ p.angle = (p.angle || 0) + p.angularVelocity * speed * 0.02;
413
+ const cx = width / 2, cy = height / 2;
414
+ p.x = cx + p.orbitRadius * Math.cos(p.angle || 0);
415
+ p.y = cy + p.orbitRadius * Math.sin(p.angle || 0);
416
+ }
417
+ }
418
+ else if (this.mode === 'smoke') {
419
+ p.life = (p.life || 0) - speed;
420
+ if ((p.life || 0) <= 0) {
421
+ p.life = p.maxLife || 220;
422
+ p.x = width * 0.2 + Math.random() * width * 0.6;
423
+ p.y = height;
424
+ p.radius = p.amplitude || 5;
425
+ p.vx = (Math.random() - 0.5) * 0.35;
426
+ p.vy = -(Math.random() * 0.4 + 0.15);
427
+ }
428
+ p.opacity = Math.max(0, (p.life || 0) / (p.maxLife || 220));
429
+ p.vx += (Math.random() - 0.5) * 0.04;
430
+ p.vx *= 0.97;
431
+ p.x += p.vx * speed;
432
+ p.y += (p.vy || -0.3) * speed;
433
+ p.radius += 0.07 * speed;
434
+ }
435
+ else if (this.mode === 'bubbles') {
436
+ p.y += (p.vy || -0.4) * speed;
437
+ p.x += (p.vx || 0) * speed;
438
+ if (p.y < -(p.radius + 20)) {
439
+ p.y = height + p.radius + Math.random() * 80;
440
+ p.x = Math.random() * width;
441
+ }
442
+ }
443
+ else if (this.mode === 'grid') {
444
+ if (p.originX !== undefined && p.originY !== undefined) {
445
+ p.x += (p.originX - p.x) * 0.12;
446
+ p.y += (p.originY - p.y) * 0.12;
447
+ }
448
+ }
449
+ else if (this.mode === 'noise') {
450
+ p.prevX = p.x;
451
+ p.prevY = p.y;
452
+ const flowAngle = Math.sin(p.x * 0.008 + t * 0.008) * Math.PI * 2 +
453
+ Math.cos(p.y * 0.008 + t * 0.006) * Math.PI;
454
+ p.vx = Math.cos(flowAngle) * 1.6;
455
+ p.vy = Math.sin(flowAngle) * 1.6;
456
+ p.x += p.vx * speed;
457
+ p.y += p.vy * speed;
458
+ if (p.x < 0 || p.x > width || p.y < 0 || p.y > height) {
459
+ p.x = Math.random() * width;
460
+ p.y = Math.random() * height;
461
+ p.prevX = p.x;
462
+ p.prevY = p.y;
463
+ }
464
+ }
465
+ else if (this.mode === 'rings') {
466
+ p.radius += (p.vx || 1) * speed;
467
+ if (p.radius > (p.maxLife || 80)) {
468
+ p.radius = 0;
469
+ p.x = Math.random() * width;
470
+ p.y = Math.random() * height;
471
+ }
472
+ }
473
+ else {
474
+ // float, snow, rain, matrix
475
+ p.x += p.vx * speed;
476
+ p.y += p.vy * speed;
477
+ if (this.mode === 'snow') {
478
+ if (p.y > height) {
479
+ p.y = 0;
480
+ p.x = Math.random() * width;
481
+ }
482
+ if (p.x < 0)
483
+ p.x = width;
484
+ if (p.x > width)
485
+ p.x = 0;
486
+ }
487
+ else if (this.mode === 'rain') {
488
+ if (p.y > height || p.x < 0) {
489
+ p.y = 0;
490
+ p.x = Math.random() * width;
491
+ }
492
+ }
493
+ else if (this.mode === 'matrix') {
494
+ if (p.y > height) {
495
+ p.y = 0;
496
+ p.x = Math.random() * width;
497
+ }
498
+ if (Math.random() < 0.05)
499
+ this._matrixChars[i] = Math.random() < 0.5 ? '0' : '1';
500
+ }
501
+ else {
502
+ if (p.x < 0)
503
+ p.x = width;
504
+ if (p.x > width)
505
+ p.x = 0;
506
+ if (p.y < 0)
507
+ p.y = height;
508
+ if (p.y > height)
509
+ p.y = 0;
510
+ }
511
+ }
512
+ // ── Mouse interaction ──
513
+ if (mouse.x !== null && mouse.y !== null && hoverEffect !== 'none') {
514
+ const dx = p.x - mouse.x;
515
+ const dy = p.y - mouse.y;
516
+ const dist = Math.sqrt(dx * dx + dy * dy);
517
+ if (dist < interactiveRadius && dist > 0.01) {
518
+ const force = (interactiveRadius - dist) / interactiveRadius;
519
+ if (hoverEffect === 'repel') {
520
+ p.x += (dx / dist) * force * (this.mode === 'grid' ? interactiveRadius * 0.35 : 1.8);
521
+ p.y += (dy / dist) * force * (this.mode === 'grid' ? interactiveRadius * 0.35 : 1.8);
522
+ }
523
+ else if (hoverEffect === 'attract') {
524
+ p.x -= (dx / dist) * force * (this.mode === 'grid' ? interactiveRadius * 0.35 : 1.8);
525
+ p.y -= (dy / dist) * force * (this.mode === 'grid' ? interactiveRadius * 0.35 : 1.8);
526
+ }
527
+ else if (hoverEffect === 'grab') {
528
+ ctx.beginPath();
529
+ ctx.moveTo(p.x, p.y);
530
+ ctx.lineTo(mouse.x, mouse.y);
531
+ ctx.strokeStyle = this._lineColor;
532
+ ctx.lineWidth = force * 0.8;
533
+ ctx.stroke();
534
+ }
535
+ }
536
+ }
537
+ // ── Draw ──
538
+ if (this.mode === 'rain') {
539
+ ctx.beginPath();
540
+ ctx.moveTo(p.x, p.y);
541
+ ctx.lineTo(p.x - p.vx * 1.5, p.y - p.vy * 1.5);
542
+ ctx.strokeStyle = p.color || '#6b7280';
543
+ ctx.lineWidth = p.radius * 0.6;
544
+ ctx.stroke();
545
+ }
546
+ else if (this.mode === 'matrix') {
547
+ const char = this._matrixChars[i] || '0';
548
+ ctx.font = `bold ${p.radius * 6 + 7}px monospace`;
549
+ ctx.fillStyle = p.color || '#6b7280';
550
+ ctx.fillText(char, p.x, p.y);
551
+ }
552
+ else if (this.mode === 'smoke') {
553
+ ctx.save();
554
+ ctx.globalAlpha = (p.opacity || 0) * 0.3;
555
+ ctx.beginPath();
556
+ ctx.arc(p.x, p.y, Math.max(0.1, p.radius), 0, Math.PI * 2);
557
+ ctx.fillStyle = p.color || '#6b7280';
558
+ ctx.fill();
559
+ ctx.restore();
560
+ }
561
+ else if (this.mode === 'bubbles') {
562
+ const yFade = Math.max(0, Math.min(1, p.y / height));
563
+ ctx.save();
564
+ ctx.globalAlpha = yFade * (p.opacity || 0.5);
565
+ ctx.beginPath();
566
+ ctx.arc(p.x, p.y, Math.max(0.1, p.radius), 0, Math.PI * 2);
567
+ ctx.strokeStyle = p.color || '#6b7280';
568
+ ctx.lineWidth = 1.2;
569
+ ctx.stroke();
570
+ ctx.globalAlpha = yFade * (p.opacity || 0.5) * 0.07;
571
+ ctx.fillStyle = p.color || '#6b7280';
572
+ ctx.fill();
573
+ ctx.restore();
574
+ }
575
+ else if (this.mode === 'grid') {
576
+ const cx = width / 2, cy = height / 2;
577
+ const dx = (p.originX || p.x) - cx;
578
+ const dy = (p.originY || p.y) - cy;
579
+ const dist = Math.sqrt(dx * dx + dy * dy);
580
+ const pulse = Math.sin(dist * 0.04 - t * 0.03 * speed + (p.phase || 0)) * 0.5 + 0.5;
581
+ const drawR = p.radius * (0.4 + pulse * 1.3);
582
+ ctx.save();
583
+ ctx.globalAlpha = 0.2 + pulse * 0.6;
584
+ ctx.beginPath();
585
+ ctx.arc(p.x, p.y, Math.max(0.1, drawR), 0, Math.PI * 2);
586
+ ctx.fillStyle = p.color || '#6b7280';
587
+ ctx.fill();
588
+ ctx.restore();
589
+ }
590
+ else if (this.mode === 'noise') {
591
+ ctx.beginPath();
592
+ ctx.moveTo(p.prevX !== undefined ? p.prevX : p.x, p.prevY !== undefined ? p.prevY : p.y);
593
+ ctx.lineTo(p.x, p.y);
594
+ ctx.strokeStyle = p.color || '#6b7280';
595
+ ctx.lineWidth = 0.9;
596
+ ctx.globalAlpha = 0.38;
597
+ ctx.stroke();
598
+ ctx.globalAlpha = 1;
599
+ }
600
+ else if (this.mode === 'rings') {
601
+ const maxR = p.maxLife || 80;
602
+ const alpha = Math.max(0, 1 - p.radius / maxR);
603
+ ctx.save();
604
+ ctx.globalAlpha = alpha * 0.6;
605
+ ctx.beginPath();
606
+ ctx.arc(p.x, p.y, Math.max(0.1, p.radius), 0, Math.PI * 2);
607
+ ctx.strokeStyle = p.color || '#6b7280';
608
+ ctx.lineWidth = 1.5;
609
+ ctx.stroke();
610
+ ctx.restore();
611
+ }
612
+ else {
613
+ // float, snow, neural, wave, vortex → standard dot
614
+ ctx.beginPath();
615
+ ctx.arc(p.x, p.y, Math.max(0.1, p.radius), 0, Math.PI * 2);
616
+ ctx.fillStyle = p.color || '#6b7280';
617
+ ctx.fill();
618
+ }
619
+ }
620
+ // ── Constellation lines (float + generic) ────────────────────────────────
621
+ if (this.connect && this.mode !== 'neural') {
622
+ ctx.strokeStyle = this._lineColor;
623
+ for (let i = 0; i < particles.length; i++) {
624
+ const p1 = particles[i];
625
+ if (!p1)
626
+ continue;
627
+ for (let j = i + 1; j < particles.length; j++) {
628
+ const p2 = particles[j];
629
+ if (!p2)
630
+ continue;
631
+ const dx = p1.x - p2.x, dy = p1.y - p2.y;
632
+ const dist = Math.sqrt(dx * dx + dy * dy);
633
+ if (dist < connectDist) {
634
+ const alpha = 1 - dist / connectDist;
635
+ ctx.beginPath();
636
+ ctx.moveTo(p1.x, p1.y);
637
+ ctx.lineTo(p2.x, p2.y);
638
+ ctx.lineWidth = alpha * 0.5;
639
+ ctx.globalAlpha = alpha;
640
+ ctx.stroke();
641
+ ctx.globalAlpha = 1;
642
+ }
643
+ }
644
+ }
645
+ }
646
+ }
647
+ render() {
648
+ return html `
649
+ <div class="container" part="container">
650
+ <canvas part="canvas"></canvas>
651
+ <div class="content" part="content">
652
+ <slot></slot>
653
+ </div>
654
+ </div>
655
+ `;
656
+ }
657
+ };
658
+ __decorate([
659
+ property({ type: Number })
660
+ ], Particles.prototype, "count", void 0);
661
+ __decorate([
662
+ property({ type: Number })
663
+ ], Particles.prototype, "speed", void 0);
664
+ __decorate([
665
+ property({ type: String })
666
+ ], Particles.prototype, "mode", void 0);
667
+ __decorate([
668
+ property({ type: Boolean })
669
+ ], Particles.prototype, "connect", void 0);
670
+ __decorate([
671
+ property({ type: Number, attribute: 'connect-distance' })
672
+ ], Particles.prototype, "connectDistance", void 0);
673
+ __decorate([
674
+ property({ type: String, attribute: 'hover-effect' })
675
+ ], Particles.prototype, "hoverEffect", void 0);
676
+ __decorate([
677
+ property({ type: Number, attribute: 'interactive-radius' })
678
+ ], Particles.prototype, "interactiveRadius", void 0);
679
+ __decorate([
680
+ query('canvas')
681
+ ], Particles.prototype, "_canvas", void 0);
682
+ Particles = __decorate([
683
+ customElement('uibit-particles')
684
+ ], Particles);
685
+ export { Particles };
686
+ export default Particles;
687
+ //# sourceMappingURL=particles.js.map