particle-network-bg 0.0.2 → 0.0.3

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 CHANGED
@@ -88,7 +88,7 @@ function App() {
88
88
  }
89
89
  ```
90
90
 
91
- > **Note:** Config is applied on mount. The canvas resizes to the window.
91
+ > **Note:** Config is applied on mount. The canvas resizes to the window. When gradients are enabled, a background `<div>` is automatically created behind the canvas for smooth CSS-based gradient rendering.
92
92
 
93
93
  ## Configuration
94
94
 
@@ -124,9 +124,13 @@ function App() {
124
124
  | `gradientSpin` | boolean | false | **Linear:** `true` = rotate, `false` = flow continuously |
125
125
  | `gradientFlowAngle` | number | 45 | **Linear flow mode:** Direction of color flow (degrees) |
126
126
  | `gradientOrbitRadius` | number | 0.3 | **Radial:** Orbit radius for center movement (0–1) |
127
+ | `gradientDithering` | boolean | true | *(Deprecated)* No longer used with CSS gradients |
128
+ | `gradientSmoothStops` | number | 4 | *(Deprecated)* No longer used with CSS gradients |
127
129
 
128
130
  ## Gradient Examples
129
131
 
132
+ Gradients are rendered via CSS (not canvas) for smooth, band-free appearance. A background `<div>` is automatically created behind the canvas.
133
+
130
134
  ### Linear Gradient (Continuous Flow)
131
135
 
132
136
  ```js
@@ -28,7 +28,9 @@ var DEFAULT_CONFIG = {
28
28
  gradientRadius: 1,
29
29
  gradientSpin: false,
30
30
  gradientFlowAngle: 45,
31
- gradientOrbitRadius: 0.3
31
+ gradientOrbitRadius: 0.3,
32
+ gradientDithering: true,
33
+ gradientSmoothStops: 4
32
34
  };
33
35
  var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
34
36
  var ParticleNetwork = class {
@@ -42,6 +44,7 @@ var ParticleNetwork = class {
42
44
  this.gradientFlowOffset = 0;
43
45
  this.gradientCenter = { x: 0, y: 0 };
44
46
  this.smoothedMouseAngle = 0;
47
+ this.gradientDiv = null;
45
48
  this.canvas = canvas;
46
49
  const ctx = canvas.getContext("2d", { alpha: true });
47
50
  if (!ctx) {
@@ -49,6 +52,7 @@ var ParticleNetwork = class {
49
52
  }
50
53
  this.ctx = ctx;
51
54
  this.config = this.validateConfig({ ...DEFAULT_CONFIG, ...config });
55
+ this.createGradientDiv();
52
56
  this.boundHandleResize = this.handleResize.bind(this);
53
57
  this.boundHandleMouseMove = this.handleMouseMove.bind(this);
54
58
  this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
@@ -56,6 +60,11 @@ var ParticleNetwork = class {
56
60
  this.createParticles();
57
61
  this.setupEventListeners();
58
62
  }
63
+ createGradientDiv() {
64
+ this.gradientDiv = document.createElement("div");
65
+ this.gradientDiv.style.cssText = "position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:-1;";
66
+ this.canvas.parentElement?.insertBefore(this.gradientDiv, this.canvas);
67
+ }
59
68
  validateConfig(config) {
60
69
  const numericParams = [
61
70
  "particleCount",
@@ -75,7 +84,8 @@ var ParticleNetwork = class {
75
84
  "gradientAngle",
76
85
  "gradientRadius",
77
86
  "gradientFlowAngle",
78
- "gradientOrbitRadius"
87
+ "gradientOrbitRadius",
88
+ "gradientSmoothStops"
79
89
  ];
80
90
  for (const param of numericParams) {
81
91
  const val = config[param];
@@ -89,7 +99,8 @@ var ParticleNetwork = class {
89
99
  "depthEffectEnabled",
90
100
  "gradientEnabled",
91
101
  "gradientMouseReaction",
92
- "gradientSpin"
102
+ "gradientSpin",
103
+ "gradientDithering"
93
104
  ];
94
105
  for (const param of booleanParams) {
95
106
  if (typeof config[param] !== "boolean") {
@@ -148,6 +159,8 @@ var ParticleNetwork = class {
148
159
  window.removeEventListener("resize", this.boundHandleResize);
149
160
  this.canvas.removeEventListener("mousemove", this.boundHandleMouseMove);
150
161
  this.canvas.removeEventListener("mouseleave", this.boundHandleMouseLeave);
162
+ this.gradientDiv?.remove();
163
+ this.gradientDiv = null;
151
164
  this.stop();
152
165
  }
153
166
  handleMouseMove(e) {
@@ -277,100 +290,62 @@ var ParticleNetwork = class {
277
290
  }
278
291
  }
279
292
  drawBackground() {
280
- this.ctx.globalAlpha = this.config.backgroundOpacity;
281
- if (this.config.gradientEnabled) {
282
- const w = this.canvas.width;
283
- const h = this.canvas.height;
284
- const colors = this.config.gradientColors;
285
- const stops = this.config.gradientStops ?? colors.map((_, i) => i / (colors.length - 1));
286
- if (this.config.gradientType === "linear") {
287
- const baseAngleRad = this.config.gradientFlowAngle * Math.PI / 180;
288
- const diag = Math.sqrt(w * w + h * h);
289
- let angle;
290
- let offsetX = 0;
291
- let offsetY = 0;
292
- if (this.config.gradientSpin) {
293
- this.gradientAngle += this.config.gradientSpeed;
294
- angle = this.gradientAngle;
295
- } else {
296
- this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
297
- const t = this.gradientFlowOffset / (Math.PI * 2);
298
- offsetX = Math.cos(baseAngleRad) * t * diag;
299
- offsetY = Math.sin(baseAngleRad) * t * diag;
300
- angle = baseAngleRad;
301
- }
302
- const cos = Math.cos(angle);
303
- const sin = Math.sin(angle);
304
- const lineLen = this.config.gradientSpin ? diag / 2 : diag * 1.5;
305
- const cx = w / 2 + offsetX;
306
- const cy = h / 2 + offsetY;
307
- const x1 = cx - cos * lineLen;
308
- const y1 = cy - sin * lineLen;
309
- const x2 = cx + cos * lineLen;
310
- const y2 = cy + sin * lineLen;
311
- const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
312
- if (this.config.gradientSpin) {
313
- colors.forEach((c, i) => gradient.addColorStop(stops[i], c));
314
- } else {
315
- const extColors = [...colors, colors[0]];
316
- for (let rep = 0; rep < 3; rep++) {
317
- extColors.forEach((c, i) => {
318
- gradient.addColorStop((rep + i / (extColors.length - 1)) / 3, c);
319
- });
320
- }
321
- }
322
- this.ctx.fillStyle = gradient;
323
- } else {
324
- const cx = w / 2;
325
- const cy = h / 2;
326
- const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
327
- let targetX;
328
- let targetY;
293
+ if (this.config.gradientEnabled && this.gradientDiv) {
294
+ this.gradientDiv.style.display = "";
295
+ this.gradientDiv.style.opacity = String(this.config.backgroundOpacity);
296
+ this.updateGradientCSS();
297
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
298
+ } else {
299
+ if (this.gradientDiv) this.gradientDiv.style.display = "none";
300
+ this.ctx.globalAlpha = this.config.backgroundOpacity;
301
+ this.ctx.fillStyle = this.config.backgroundColor;
302
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
303
+ this.ctx.globalAlpha = 1;
304
+ }
305
+ }
306
+ updateGradientCSS() {
307
+ if (!this.gradientDiv) return;
308
+ const w = this.canvas.width;
309
+ const h = this.canvas.height;
310
+ const colors = this.config.gradientColors;
311
+ if (this.config.gradientType === "linear") {
312
+ if (this.config.gradientSpin) {
329
313
  this.gradientAngle += this.config.gradientSpeed;
330
- targetX = cx + Math.cos(this.gradientAngle) * orbitR;
331
- targetY = cy + Math.sin(this.gradientAngle) * orbitR;
332
- if (this.config.gradientMouseReaction && this.mousePosition) {
333
- const influence = this.config.gradientMouseInfluence;
334
- targetX = targetX + (this.mousePosition.x - targetX) * influence;
335
- targetY = targetY + (this.mousePosition.y - targetY) * influence;
336
- }
337
- const lerpFactor = 0.03;
338
- this.gradientCenter.x += (targetX - this.gradientCenter.x) * lerpFactor;
339
- this.gradientCenter.y += (targetY - this.gradientCenter.y) * lerpFactor;
340
- this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
341
- const t = this.gradientFlowOffset / (Math.PI * 2);
342
- const r = Math.max(w, h) * this.config.gradientRadius * 2;
343
- const gradient = this.ctx.createRadialGradient(
344
- this.gradientCenter.x,
345
- this.gradientCenter.y,
346
- 0,
347
- this.gradientCenter.x,
348
- this.gradientCenter.y,
349
- r
350
- );
351
- const extColors = [...colors, colors[0]];
352
- const entries = [];
353
- for (let rep = 0; rep < 3; rep++) {
354
- extColors.forEach((c, i) => {
355
- const base = (rep + i / (extColors.length - 1)) / 3;
356
- entries.push({ stop: (base + t) % 1, color: c });
357
- });
358
- }
359
- entries.sort((a, b) => a.stop - b.stop);
360
- if (entries[0].stop > 1e-3) {
361
- entries.unshift({ stop: 0, color: entries[entries.length - 1].color });
362
- }
363
- if (entries[entries.length - 1].stop < 0.999) {
364
- entries.push({ stop: 1, color: entries[0].color });
365
- }
366
- entries.forEach((e) => gradient.addColorStop(e.stop, e.color));
367
- this.ctx.fillStyle = gradient;
314
+ const deg = this.gradientAngle * 180 / Math.PI;
315
+ this.gradientDiv.style.background = `linear-gradient(${deg}deg, ${colors.join(", ")})`;
316
+ this.gradientDiv.style.backgroundSize = "";
317
+ this.gradientDiv.style.backgroundPosition = "";
318
+ } else {
319
+ const angle = this.config.gradientFlowAngle;
320
+ const cycle = [...colors, ...colors, ...colors, colors[0]];
321
+ this.gradientDiv.style.background = `linear-gradient(${angle}deg, ${cycle.join(", ")})`;
322
+ this.gradientDiv.style.backgroundSize = "300% 300%";
323
+ this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed * 0.5) % 100;
324
+ const pct = this.gradientFlowOffset;
325
+ this.gradientDiv.style.backgroundPosition = `${pct}% ${pct}%`;
368
326
  }
369
327
  } else {
370
- this.ctx.fillStyle = this.config.backgroundColor;
328
+ const cx = w / 2;
329
+ const cy = h / 2;
330
+ const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
331
+ this.gradientAngle += this.config.gradientSpeed;
332
+ let targetX = cx + Math.cos(this.gradientAngle) * orbitR;
333
+ let targetY = cy + Math.sin(this.gradientAngle) * orbitR;
334
+ if (this.config.gradientMouseReaction && this.mousePosition) {
335
+ const influence = this.config.gradientMouseInfluence;
336
+ targetX += (this.mousePosition.x - targetX) * influence;
337
+ targetY += (this.mousePosition.y - targetY) * influence;
338
+ }
339
+ this.gradientCenter.x += (targetX - this.gradientCenter.x) * 0.03;
340
+ this.gradientCenter.y += (targetY - this.gradientCenter.y) * 0.03;
341
+ const r = Math.max(w, h) * this.config.gradientRadius;
342
+ const cycle = [...colors, ...colors, colors[0]];
343
+ const step = r * 2 / (cycle.length - 1);
344
+ const stops = cycle.map((c, i) => `${c} ${i * step}px`).join(", ");
345
+ this.gradientDiv.style.background = `radial-gradient(circle at ${this.gradientCenter.x}px ${this.gradientCenter.y}px, ${stops})`;
346
+ this.gradientDiv.style.backgroundSize = "";
347
+ this.gradientDiv.style.backgroundPosition = "";
371
348
  }
372
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
373
- this.ctx.globalAlpha = 1;
374
349
  }
375
350
  hexToRgb(hex) {
376
351
  hex = hex.replace(/^#/, "");
package/dist/index.d.mts CHANGED
@@ -30,6 +30,8 @@ interface ParticleNetworkConfig {
30
30
  gradientSpin: boolean;
31
31
  gradientFlowAngle: number;
32
32
  gradientOrbitRadius: number;
33
+ gradientDithering: boolean;
34
+ gradientSmoothStops: number;
33
35
  }
34
36
  interface Particle {
35
37
  x: number;
@@ -54,10 +56,12 @@ declare class ParticleNetwork {
54
56
  private gradientFlowOffset;
55
57
  private gradientCenter;
56
58
  private smoothedMouseAngle;
59
+ private gradientDiv;
57
60
  private boundHandleResize;
58
61
  private boundHandleMouseMove;
59
62
  private boundHandleMouseLeave;
60
63
  constructor(canvas: HTMLCanvasElement, config?: Partial<ParticleNetworkConfig>);
64
+ private createGradientDiv;
61
65
  private validateConfig;
62
66
  private setupEventListeners;
63
67
  cleanup(): void;
@@ -69,6 +73,7 @@ declare class ParticleNetwork {
69
73
  private drawParticles;
70
74
  private drawConnections;
71
75
  private drawBackground;
76
+ private updateGradientCSS;
72
77
  private hexToRgb;
73
78
  stop(): void;
74
79
  start(): void;
package/dist/index.d.ts CHANGED
@@ -30,6 +30,8 @@ interface ParticleNetworkConfig {
30
30
  gradientSpin: boolean;
31
31
  gradientFlowAngle: number;
32
32
  gradientOrbitRadius: number;
33
+ gradientDithering: boolean;
34
+ gradientSmoothStops: number;
33
35
  }
34
36
  interface Particle {
35
37
  x: number;
@@ -54,10 +56,12 @@ declare class ParticleNetwork {
54
56
  private gradientFlowOffset;
55
57
  private gradientCenter;
56
58
  private smoothedMouseAngle;
59
+ private gradientDiv;
57
60
  private boundHandleResize;
58
61
  private boundHandleMouseMove;
59
62
  private boundHandleMouseLeave;
60
63
  constructor(canvas: HTMLCanvasElement, config?: Partial<ParticleNetworkConfig>);
64
+ private createGradientDiv;
61
65
  private validateConfig;
62
66
  private setupEventListeners;
63
67
  cleanup(): void;
@@ -69,6 +73,7 @@ declare class ParticleNetwork {
69
73
  private drawParticles;
70
74
  private drawConnections;
71
75
  private drawBackground;
76
+ private updateGradientCSS;
72
77
  private hexToRgb;
73
78
  stop(): void;
74
79
  start(): void;
package/dist/index.js CHANGED
@@ -52,7 +52,9 @@ var DEFAULT_CONFIG = {
52
52
  gradientRadius: 1,
53
53
  gradientSpin: false,
54
54
  gradientFlowAngle: 45,
55
- gradientOrbitRadius: 0.3
55
+ gradientOrbitRadius: 0.3,
56
+ gradientDithering: true,
57
+ gradientSmoothStops: 4
56
58
  };
57
59
  var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
58
60
  var ParticleNetwork = class {
@@ -66,6 +68,7 @@ var ParticleNetwork = class {
66
68
  this.gradientFlowOffset = 0;
67
69
  this.gradientCenter = { x: 0, y: 0 };
68
70
  this.smoothedMouseAngle = 0;
71
+ this.gradientDiv = null;
69
72
  this.canvas = canvas;
70
73
  const ctx = canvas.getContext("2d", { alpha: true });
71
74
  if (!ctx) {
@@ -73,6 +76,7 @@ var ParticleNetwork = class {
73
76
  }
74
77
  this.ctx = ctx;
75
78
  this.config = this.validateConfig({ ...DEFAULT_CONFIG, ...config });
79
+ this.createGradientDiv();
76
80
  this.boundHandleResize = this.handleResize.bind(this);
77
81
  this.boundHandleMouseMove = this.handleMouseMove.bind(this);
78
82
  this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
@@ -80,6 +84,11 @@ var ParticleNetwork = class {
80
84
  this.createParticles();
81
85
  this.setupEventListeners();
82
86
  }
87
+ createGradientDiv() {
88
+ this.gradientDiv = document.createElement("div");
89
+ this.gradientDiv.style.cssText = "position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:-1;";
90
+ this.canvas.parentElement?.insertBefore(this.gradientDiv, this.canvas);
91
+ }
83
92
  validateConfig(config) {
84
93
  const numericParams = [
85
94
  "particleCount",
@@ -99,7 +108,8 @@ var ParticleNetwork = class {
99
108
  "gradientAngle",
100
109
  "gradientRadius",
101
110
  "gradientFlowAngle",
102
- "gradientOrbitRadius"
111
+ "gradientOrbitRadius",
112
+ "gradientSmoothStops"
103
113
  ];
104
114
  for (const param of numericParams) {
105
115
  const val = config[param];
@@ -113,7 +123,8 @@ var ParticleNetwork = class {
113
123
  "depthEffectEnabled",
114
124
  "gradientEnabled",
115
125
  "gradientMouseReaction",
116
- "gradientSpin"
126
+ "gradientSpin",
127
+ "gradientDithering"
117
128
  ];
118
129
  for (const param of booleanParams) {
119
130
  if (typeof config[param] !== "boolean") {
@@ -172,6 +183,8 @@ var ParticleNetwork = class {
172
183
  window.removeEventListener("resize", this.boundHandleResize);
173
184
  this.canvas.removeEventListener("mousemove", this.boundHandleMouseMove);
174
185
  this.canvas.removeEventListener("mouseleave", this.boundHandleMouseLeave);
186
+ this.gradientDiv?.remove();
187
+ this.gradientDiv = null;
175
188
  this.stop();
176
189
  }
177
190
  handleMouseMove(e) {
@@ -301,100 +314,62 @@ var ParticleNetwork = class {
301
314
  }
302
315
  }
303
316
  drawBackground() {
304
- this.ctx.globalAlpha = this.config.backgroundOpacity;
305
- if (this.config.gradientEnabled) {
306
- const w = this.canvas.width;
307
- const h = this.canvas.height;
308
- const colors = this.config.gradientColors;
309
- const stops = this.config.gradientStops ?? colors.map((_, i) => i / (colors.length - 1));
310
- if (this.config.gradientType === "linear") {
311
- const baseAngleRad = this.config.gradientFlowAngle * Math.PI / 180;
312
- const diag = Math.sqrt(w * w + h * h);
313
- let angle;
314
- let offsetX = 0;
315
- let offsetY = 0;
316
- if (this.config.gradientSpin) {
317
- this.gradientAngle += this.config.gradientSpeed;
318
- angle = this.gradientAngle;
319
- } else {
320
- this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
321
- const t = this.gradientFlowOffset / (Math.PI * 2);
322
- offsetX = Math.cos(baseAngleRad) * t * diag;
323
- offsetY = Math.sin(baseAngleRad) * t * diag;
324
- angle = baseAngleRad;
325
- }
326
- const cos = Math.cos(angle);
327
- const sin = Math.sin(angle);
328
- const lineLen = this.config.gradientSpin ? diag / 2 : diag * 1.5;
329
- const cx = w / 2 + offsetX;
330
- const cy = h / 2 + offsetY;
331
- const x1 = cx - cos * lineLen;
332
- const y1 = cy - sin * lineLen;
333
- const x2 = cx + cos * lineLen;
334
- const y2 = cy + sin * lineLen;
335
- const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
336
- if (this.config.gradientSpin) {
337
- colors.forEach((c, i) => gradient.addColorStop(stops[i], c));
338
- } else {
339
- const extColors = [...colors, colors[0]];
340
- for (let rep = 0; rep < 3; rep++) {
341
- extColors.forEach((c, i) => {
342
- gradient.addColorStop((rep + i / (extColors.length - 1)) / 3, c);
343
- });
344
- }
345
- }
346
- this.ctx.fillStyle = gradient;
347
- } else {
348
- const cx = w / 2;
349
- const cy = h / 2;
350
- const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
351
- let targetX;
352
- let targetY;
317
+ if (this.config.gradientEnabled && this.gradientDiv) {
318
+ this.gradientDiv.style.display = "";
319
+ this.gradientDiv.style.opacity = String(this.config.backgroundOpacity);
320
+ this.updateGradientCSS();
321
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
322
+ } else {
323
+ if (this.gradientDiv) this.gradientDiv.style.display = "none";
324
+ this.ctx.globalAlpha = this.config.backgroundOpacity;
325
+ this.ctx.fillStyle = this.config.backgroundColor;
326
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
327
+ this.ctx.globalAlpha = 1;
328
+ }
329
+ }
330
+ updateGradientCSS() {
331
+ if (!this.gradientDiv) return;
332
+ const w = this.canvas.width;
333
+ const h = this.canvas.height;
334
+ const colors = this.config.gradientColors;
335
+ if (this.config.gradientType === "linear") {
336
+ if (this.config.gradientSpin) {
353
337
  this.gradientAngle += this.config.gradientSpeed;
354
- targetX = cx + Math.cos(this.gradientAngle) * orbitR;
355
- targetY = cy + Math.sin(this.gradientAngle) * orbitR;
356
- if (this.config.gradientMouseReaction && this.mousePosition) {
357
- const influence = this.config.gradientMouseInfluence;
358
- targetX = targetX + (this.mousePosition.x - targetX) * influence;
359
- targetY = targetY + (this.mousePosition.y - targetY) * influence;
360
- }
361
- const lerpFactor = 0.03;
362
- this.gradientCenter.x += (targetX - this.gradientCenter.x) * lerpFactor;
363
- this.gradientCenter.y += (targetY - this.gradientCenter.y) * lerpFactor;
364
- this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
365
- const t = this.gradientFlowOffset / (Math.PI * 2);
366
- const r = Math.max(w, h) * this.config.gradientRadius * 2;
367
- const gradient = this.ctx.createRadialGradient(
368
- this.gradientCenter.x,
369
- this.gradientCenter.y,
370
- 0,
371
- this.gradientCenter.x,
372
- this.gradientCenter.y,
373
- r
374
- );
375
- const extColors = [...colors, colors[0]];
376
- const entries = [];
377
- for (let rep = 0; rep < 3; rep++) {
378
- extColors.forEach((c, i) => {
379
- const base = (rep + i / (extColors.length - 1)) / 3;
380
- entries.push({ stop: (base + t) % 1, color: c });
381
- });
382
- }
383
- entries.sort((a, b) => a.stop - b.stop);
384
- if (entries[0].stop > 1e-3) {
385
- entries.unshift({ stop: 0, color: entries[entries.length - 1].color });
386
- }
387
- if (entries[entries.length - 1].stop < 0.999) {
388
- entries.push({ stop: 1, color: entries[0].color });
389
- }
390
- entries.forEach((e) => gradient.addColorStop(e.stop, e.color));
391
- this.ctx.fillStyle = gradient;
338
+ const deg = this.gradientAngle * 180 / Math.PI;
339
+ this.gradientDiv.style.background = `linear-gradient(${deg}deg, ${colors.join(", ")})`;
340
+ this.gradientDiv.style.backgroundSize = "";
341
+ this.gradientDiv.style.backgroundPosition = "";
342
+ } else {
343
+ const angle = this.config.gradientFlowAngle;
344
+ const cycle = [...colors, ...colors, ...colors, colors[0]];
345
+ this.gradientDiv.style.background = `linear-gradient(${angle}deg, ${cycle.join(", ")})`;
346
+ this.gradientDiv.style.backgroundSize = "300% 300%";
347
+ this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed * 0.5) % 100;
348
+ const pct = this.gradientFlowOffset;
349
+ this.gradientDiv.style.backgroundPosition = `${pct}% ${pct}%`;
392
350
  }
393
351
  } else {
394
- this.ctx.fillStyle = this.config.backgroundColor;
352
+ const cx = w / 2;
353
+ const cy = h / 2;
354
+ const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
355
+ this.gradientAngle += this.config.gradientSpeed;
356
+ let targetX = cx + Math.cos(this.gradientAngle) * orbitR;
357
+ let targetY = cy + Math.sin(this.gradientAngle) * orbitR;
358
+ if (this.config.gradientMouseReaction && this.mousePosition) {
359
+ const influence = this.config.gradientMouseInfluence;
360
+ targetX += (this.mousePosition.x - targetX) * influence;
361
+ targetY += (this.mousePosition.y - targetY) * influence;
362
+ }
363
+ this.gradientCenter.x += (targetX - this.gradientCenter.x) * 0.03;
364
+ this.gradientCenter.y += (targetY - this.gradientCenter.y) * 0.03;
365
+ const r = Math.max(w, h) * this.config.gradientRadius;
366
+ const cycle = [...colors, ...colors, colors[0]];
367
+ const step = r * 2 / (cycle.length - 1);
368
+ const stops = cycle.map((c, i) => `${c} ${i * step}px`).join(", ");
369
+ this.gradientDiv.style.background = `radial-gradient(circle at ${this.gradientCenter.x}px ${this.gradientCenter.y}px, ${stops})`;
370
+ this.gradientDiv.style.backgroundSize = "";
371
+ this.gradientDiv.style.backgroundPosition = "";
395
372
  }
396
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
397
- this.ctx.globalAlpha = 1;
398
373
  }
399
374
  hexToRgb(hex) {
400
375
  hex = hex.replace(/^#/, "");
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ParticleNetwork
3
- } from "./chunk-WD7BVW5Q.mjs";
3
+ } from "./chunk-35XTC6S6.mjs";
4
4
  export {
5
5
  ParticleNetwork
6
6
  };
package/dist/react.js CHANGED
@@ -56,7 +56,9 @@ var DEFAULT_CONFIG = {
56
56
  gradientRadius: 1,
57
57
  gradientSpin: false,
58
58
  gradientFlowAngle: 45,
59
- gradientOrbitRadius: 0.3
59
+ gradientOrbitRadius: 0.3,
60
+ gradientDithering: true,
61
+ gradientSmoothStops: 4
60
62
  };
61
63
  var HEX_COLOR_REGEX = /^#[0-9A-Fa-f]{6}$/;
62
64
  var ParticleNetwork = class {
@@ -70,6 +72,7 @@ var ParticleNetwork = class {
70
72
  this.gradientFlowOffset = 0;
71
73
  this.gradientCenter = { x: 0, y: 0 };
72
74
  this.smoothedMouseAngle = 0;
75
+ this.gradientDiv = null;
73
76
  this.canvas = canvas;
74
77
  const ctx = canvas.getContext("2d", { alpha: true });
75
78
  if (!ctx) {
@@ -77,6 +80,7 @@ var ParticleNetwork = class {
77
80
  }
78
81
  this.ctx = ctx;
79
82
  this.config = this.validateConfig({ ...DEFAULT_CONFIG, ...config });
83
+ this.createGradientDiv();
80
84
  this.boundHandleResize = this.handleResize.bind(this);
81
85
  this.boundHandleMouseMove = this.handleMouseMove.bind(this);
82
86
  this.boundHandleMouseLeave = this.handleMouseLeave.bind(this);
@@ -84,6 +88,11 @@ var ParticleNetwork = class {
84
88
  this.createParticles();
85
89
  this.setupEventListeners();
86
90
  }
91
+ createGradientDiv() {
92
+ this.gradientDiv = document.createElement("div");
93
+ this.gradientDiv.style.cssText = "position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:-1;";
94
+ this.canvas.parentElement?.insertBefore(this.gradientDiv, this.canvas);
95
+ }
87
96
  validateConfig(config) {
88
97
  const numericParams = [
89
98
  "particleCount",
@@ -103,7 +112,8 @@ var ParticleNetwork = class {
103
112
  "gradientAngle",
104
113
  "gradientRadius",
105
114
  "gradientFlowAngle",
106
- "gradientOrbitRadius"
115
+ "gradientOrbitRadius",
116
+ "gradientSmoothStops"
107
117
  ];
108
118
  for (const param of numericParams) {
109
119
  const val = config[param];
@@ -117,7 +127,8 @@ var ParticleNetwork = class {
117
127
  "depthEffectEnabled",
118
128
  "gradientEnabled",
119
129
  "gradientMouseReaction",
120
- "gradientSpin"
130
+ "gradientSpin",
131
+ "gradientDithering"
121
132
  ];
122
133
  for (const param of booleanParams) {
123
134
  if (typeof config[param] !== "boolean") {
@@ -176,6 +187,8 @@ var ParticleNetwork = class {
176
187
  window.removeEventListener("resize", this.boundHandleResize);
177
188
  this.canvas.removeEventListener("mousemove", this.boundHandleMouseMove);
178
189
  this.canvas.removeEventListener("mouseleave", this.boundHandleMouseLeave);
190
+ this.gradientDiv?.remove();
191
+ this.gradientDiv = null;
179
192
  this.stop();
180
193
  }
181
194
  handleMouseMove(e) {
@@ -305,100 +318,62 @@ var ParticleNetwork = class {
305
318
  }
306
319
  }
307
320
  drawBackground() {
308
- this.ctx.globalAlpha = this.config.backgroundOpacity;
309
- if (this.config.gradientEnabled) {
310
- const w = this.canvas.width;
311
- const h = this.canvas.height;
312
- const colors = this.config.gradientColors;
313
- const stops = this.config.gradientStops ?? colors.map((_, i) => i / (colors.length - 1));
314
- if (this.config.gradientType === "linear") {
315
- const baseAngleRad = this.config.gradientFlowAngle * Math.PI / 180;
316
- const diag = Math.sqrt(w * w + h * h);
317
- let angle;
318
- let offsetX = 0;
319
- let offsetY = 0;
320
- if (this.config.gradientSpin) {
321
- this.gradientAngle += this.config.gradientSpeed;
322
- angle = this.gradientAngle;
323
- } else {
324
- this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
325
- const t = this.gradientFlowOffset / (Math.PI * 2);
326
- offsetX = Math.cos(baseAngleRad) * t * diag;
327
- offsetY = Math.sin(baseAngleRad) * t * diag;
328
- angle = baseAngleRad;
329
- }
330
- const cos = Math.cos(angle);
331
- const sin = Math.sin(angle);
332
- const lineLen = this.config.gradientSpin ? diag / 2 : diag * 1.5;
333
- const cx = w / 2 + offsetX;
334
- const cy = h / 2 + offsetY;
335
- const x1 = cx - cos * lineLen;
336
- const y1 = cy - sin * lineLen;
337
- const x2 = cx + cos * lineLen;
338
- const y2 = cy + sin * lineLen;
339
- const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
340
- if (this.config.gradientSpin) {
341
- colors.forEach((c, i) => gradient.addColorStop(stops[i], c));
342
- } else {
343
- const extColors = [...colors, colors[0]];
344
- for (let rep = 0; rep < 3; rep++) {
345
- extColors.forEach((c, i) => {
346
- gradient.addColorStop((rep + i / (extColors.length - 1)) / 3, c);
347
- });
348
- }
349
- }
350
- this.ctx.fillStyle = gradient;
351
- } else {
352
- const cx = w / 2;
353
- const cy = h / 2;
354
- const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
355
- let targetX;
356
- let targetY;
321
+ if (this.config.gradientEnabled && this.gradientDiv) {
322
+ this.gradientDiv.style.display = "";
323
+ this.gradientDiv.style.opacity = String(this.config.backgroundOpacity);
324
+ this.updateGradientCSS();
325
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
326
+ } else {
327
+ if (this.gradientDiv) this.gradientDiv.style.display = "none";
328
+ this.ctx.globalAlpha = this.config.backgroundOpacity;
329
+ this.ctx.fillStyle = this.config.backgroundColor;
330
+ this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
331
+ this.ctx.globalAlpha = 1;
332
+ }
333
+ }
334
+ updateGradientCSS() {
335
+ if (!this.gradientDiv) return;
336
+ const w = this.canvas.width;
337
+ const h = this.canvas.height;
338
+ const colors = this.config.gradientColors;
339
+ if (this.config.gradientType === "linear") {
340
+ if (this.config.gradientSpin) {
357
341
  this.gradientAngle += this.config.gradientSpeed;
358
- targetX = cx + Math.cos(this.gradientAngle) * orbitR;
359
- targetY = cy + Math.sin(this.gradientAngle) * orbitR;
360
- if (this.config.gradientMouseReaction && this.mousePosition) {
361
- const influence = this.config.gradientMouseInfluence;
362
- targetX = targetX + (this.mousePosition.x - targetX) * influence;
363
- targetY = targetY + (this.mousePosition.y - targetY) * influence;
364
- }
365
- const lerpFactor = 0.03;
366
- this.gradientCenter.x += (targetX - this.gradientCenter.x) * lerpFactor;
367
- this.gradientCenter.y += (targetY - this.gradientCenter.y) * lerpFactor;
368
- this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed) % (Math.PI * 2);
369
- const t = this.gradientFlowOffset / (Math.PI * 2);
370
- const r = Math.max(w, h) * this.config.gradientRadius * 2;
371
- const gradient = this.ctx.createRadialGradient(
372
- this.gradientCenter.x,
373
- this.gradientCenter.y,
374
- 0,
375
- this.gradientCenter.x,
376
- this.gradientCenter.y,
377
- r
378
- );
379
- const extColors = [...colors, colors[0]];
380
- const entries = [];
381
- for (let rep = 0; rep < 3; rep++) {
382
- extColors.forEach((c, i) => {
383
- const base = (rep + i / (extColors.length - 1)) / 3;
384
- entries.push({ stop: (base + t) % 1, color: c });
385
- });
386
- }
387
- entries.sort((a, b) => a.stop - b.stop);
388
- if (entries[0].stop > 1e-3) {
389
- entries.unshift({ stop: 0, color: entries[entries.length - 1].color });
390
- }
391
- if (entries[entries.length - 1].stop < 0.999) {
392
- entries.push({ stop: 1, color: entries[0].color });
393
- }
394
- entries.forEach((e) => gradient.addColorStop(e.stop, e.color));
395
- this.ctx.fillStyle = gradient;
342
+ const deg = this.gradientAngle * 180 / Math.PI;
343
+ this.gradientDiv.style.background = `linear-gradient(${deg}deg, ${colors.join(", ")})`;
344
+ this.gradientDiv.style.backgroundSize = "";
345
+ this.gradientDiv.style.backgroundPosition = "";
346
+ } else {
347
+ const angle = this.config.gradientFlowAngle;
348
+ const cycle = [...colors, ...colors, ...colors, colors[0]];
349
+ this.gradientDiv.style.background = `linear-gradient(${angle}deg, ${cycle.join(", ")})`;
350
+ this.gradientDiv.style.backgroundSize = "300% 300%";
351
+ this.gradientFlowOffset = (this.gradientFlowOffset + this.config.gradientSpeed * 0.5) % 100;
352
+ const pct = this.gradientFlowOffset;
353
+ this.gradientDiv.style.backgroundPosition = `${pct}% ${pct}%`;
396
354
  }
397
355
  } else {
398
- this.ctx.fillStyle = this.config.backgroundColor;
356
+ const cx = w / 2;
357
+ const cy = h / 2;
358
+ const orbitR = Math.min(w, h) * this.config.gradientOrbitRadius;
359
+ this.gradientAngle += this.config.gradientSpeed;
360
+ let targetX = cx + Math.cos(this.gradientAngle) * orbitR;
361
+ let targetY = cy + Math.sin(this.gradientAngle) * orbitR;
362
+ if (this.config.gradientMouseReaction && this.mousePosition) {
363
+ const influence = this.config.gradientMouseInfluence;
364
+ targetX += (this.mousePosition.x - targetX) * influence;
365
+ targetY += (this.mousePosition.y - targetY) * influence;
366
+ }
367
+ this.gradientCenter.x += (targetX - this.gradientCenter.x) * 0.03;
368
+ this.gradientCenter.y += (targetY - this.gradientCenter.y) * 0.03;
369
+ const r = Math.max(w, h) * this.config.gradientRadius;
370
+ const cycle = [...colors, ...colors, colors[0]];
371
+ const step = r * 2 / (cycle.length - 1);
372
+ const stops = cycle.map((c, i) => `${c} ${i * step}px`).join(", ");
373
+ this.gradientDiv.style.background = `radial-gradient(circle at ${this.gradientCenter.x}px ${this.gradientCenter.y}px, ${stops})`;
374
+ this.gradientDiv.style.backgroundSize = "";
375
+ this.gradientDiv.style.backgroundPosition = "";
399
376
  }
400
- this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
401
- this.ctx.globalAlpha = 1;
402
377
  }
403
378
  hexToRgb(hex) {
404
379
  hex = hex.replace(/^#/, "");
package/dist/react.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  ParticleNetwork
3
- } from "./chunk-WD7BVW5Q.mjs";
3
+ } from "./chunk-35XTC6S6.mjs";
4
4
 
5
5
  // src/react.tsx
6
6
  import {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "particle-network-bg",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Interactive particle network animation for backgrounds",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",