wavedance 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,741 @@
1
+ 'use strict';
2
+
3
+ // src/core/color.ts
4
+ var HEX_SHORT = /^#([0-9a-fA-F]{3})$/;
5
+ var HEX_LONG = /^#([0-9a-fA-F]{6})$/;
6
+ function parseHexColor(hex) {
7
+ const trimmed = hex.trim();
8
+ const shortMatch = HEX_SHORT.exec(trimmed);
9
+ if (shortMatch) {
10
+ const [, value] = shortMatch;
11
+ return {
12
+ r: Number.parseInt(value[0] + value[0], 16),
13
+ g: Number.parseInt(value[1] + value[1], 16),
14
+ b: Number.parseInt(value[2] + value[2], 16)
15
+ };
16
+ }
17
+ const longMatch = HEX_LONG.exec(trimmed);
18
+ if (longMatch) {
19
+ const [, value] = longMatch;
20
+ return {
21
+ r: Number.parseInt(value.slice(0, 2), 16),
22
+ g: Number.parseInt(value.slice(2, 4), 16),
23
+ b: Number.parseInt(value.slice(4, 6), 16)
24
+ };
25
+ }
26
+ throw new Error(`Invalid hex color: "${hex}". Expected #rgb or #rrggbb.`);
27
+ }
28
+ function normalizeHexColor(hex) {
29
+ const trimmed = hex.trim().toLowerCase();
30
+ parseHexColor(trimmed);
31
+ return trimmed;
32
+ }
33
+
34
+ // src/core/config.ts
35
+ var DEFAULT_GAP = 10;
36
+ var DEFAULT_DOT_SIZE = 1;
37
+ function resolveGap(gap) {
38
+ if (gap === void 0) {
39
+ return { x: DEFAULT_GAP, y: DEFAULT_GAP };
40
+ }
41
+ if (typeof gap === "number") {
42
+ return { x: gap, y: gap };
43
+ }
44
+ return { x: gap.x, y: gap.y };
45
+ }
46
+ function resolveConfig(config = {}) {
47
+ const gap = resolveGap(config.gap);
48
+ return {
49
+ dotSize: config.dotSize ?? DEFAULT_DOT_SIZE,
50
+ gap,
51
+ foreground: normalizeHexColor(config.foreground ?? "#7c7c7c"),
52
+ background: normalizeHexColor(config.background ?? "#161616"),
53
+ animation: config.animation ?? "wave",
54
+ wave: {
55
+ scale: config.wave?.scale ?? 4e-3,
56
+ speed: config.wave?.speed ?? 3e-4,
57
+ threshold: config.wave?.threshold ?? 0.15,
58
+ softness: config.wave?.softness ?? 0.5,
59
+ seed: config.wave?.seed ?? 42
60
+ },
61
+ random: {
62
+ speed: config.random?.speed ?? 0.8,
63
+ minOpacity: config.random?.minOpacity ?? 0.05,
64
+ maxOpacity: config.random?.maxOpacity ?? 1,
65
+ seed: config.random?.seed ?? 42
66
+ },
67
+ devicePixelRatio: config.devicePixelRatio ?? (typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1),
68
+ maxDots: config.maxDots ?? 1e5,
69
+ respectReducedMotion: config.respectReducedMotion ?? true
70
+ };
71
+ }
72
+
73
+ // src/animations/none.ts
74
+ function computeNoneField(intensities, count) {
75
+ for (let i = 0; i < count; i++) {
76
+ intensities[i] = 1;
77
+ }
78
+ }
79
+
80
+ // src/core/rng.ts
81
+ var SeededRng = class {
82
+ state;
83
+ constructor(seed) {
84
+ this.state = seed >>> 0;
85
+ }
86
+ next() {
87
+ this.state += 1831565813;
88
+ let t = this.state;
89
+ t = Math.imul(t ^ t >>> 15, t | 1);
90
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
91
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
92
+ }
93
+ range(min, max) {
94
+ return min + this.next() * (max - min);
95
+ }
96
+ };
97
+
98
+ // src/animations/random.ts
99
+ var RandomAnimation = class {
100
+ options;
101
+ targets;
102
+ rng;
103
+ initialized = false;
104
+ constructor(maxDots, options) {
105
+ this.options = options;
106
+ this.targets = new Float32Array(maxDots);
107
+ this.rng = new SeededRng(options.seed);
108
+ }
109
+ updateOptions(options) {
110
+ Object.assign(this.options, options);
111
+ }
112
+ pickTarget() {
113
+ const { minOpacity, maxOpacity } = this.options;
114
+ return this.rng.range(minOpacity, maxOpacity);
115
+ }
116
+ compute(grid, intensities, deltaMs) {
117
+ const { speed } = this.options;
118
+ const step = speed * (deltaMs / 1e3);
119
+ const count = grid.count;
120
+ if (!this.initialized) {
121
+ for (let i = 0; i < count; i++) {
122
+ intensities[i] = this.pickTarget();
123
+ this.targets[i] = this.pickTarget();
124
+ }
125
+ this.initialized = true;
126
+ return;
127
+ }
128
+ for (let i = 0; i < count; i++) {
129
+ const current = intensities[i];
130
+ const target = this.targets[i];
131
+ const diff = target - current;
132
+ if (Math.abs(diff) <= step) {
133
+ intensities[i] = target;
134
+ this.targets[i] = this.pickTarget();
135
+ } else {
136
+ intensities[i] = current + Math.sign(diff) * step;
137
+ }
138
+ }
139
+ }
140
+ reset() {
141
+ this.initialized = false;
142
+ }
143
+ };
144
+
145
+ // src/core/noise.ts
146
+ var SimplexNoise = class _SimplexNoise {
147
+ perm;
148
+ permMod12;
149
+ static grad3 = new Float32Array([
150
+ 1,
151
+ 1,
152
+ 0,
153
+ -1,
154
+ 1,
155
+ 0,
156
+ 1,
157
+ -1,
158
+ 0,
159
+ -1,
160
+ -1,
161
+ 0,
162
+ 1,
163
+ 0,
164
+ 1,
165
+ -1,
166
+ 0,
167
+ 1,
168
+ 1,
169
+ 0,
170
+ -1,
171
+ -1,
172
+ 0,
173
+ -1,
174
+ 0,
175
+ 1,
176
+ 1,
177
+ 0,
178
+ -1,
179
+ 1,
180
+ 0,
181
+ 1,
182
+ -1,
183
+ 0,
184
+ -1,
185
+ -1
186
+ ]);
187
+ constructor(seed = 0) {
188
+ const source = new Uint8Array(256);
189
+ for (let i = 0; i < 256; i++) {
190
+ source[i] = i;
191
+ }
192
+ let state = (seed ^ 2654435769) >>> 0;
193
+ if (state === 0) {
194
+ state = 1;
195
+ }
196
+ const random = () => {
197
+ state = state * 1664525 + 1013904223 >>> 0;
198
+ return state / 4294967296;
199
+ };
200
+ for (let i = 255; i > 0; i--) {
201
+ const j = Math.floor(random() * (i + 1));
202
+ const tmp = source[i];
203
+ source[i] = source[j];
204
+ source[j] = tmp;
205
+ }
206
+ this.perm = new Uint8Array(512);
207
+ this.permMod12 = new Uint8Array(512);
208
+ for (let i = 0; i < 512; i++) {
209
+ this.perm[i] = source[i & 255];
210
+ this.permMod12[i] = this.perm[i] % 12;
211
+ }
212
+ }
213
+ /** Returns noise in approximately [-1, 1]. */
214
+ noise3(x, y, z) {
215
+ const grad3 = _SimplexNoise.grad3;
216
+ const perm = this.perm;
217
+ const permMod12 = this.permMod12;
218
+ const F3 = 1 / 3;
219
+ const G3 = 1 / 6;
220
+ const s = (x + y + z) * F3;
221
+ const i = Math.floor(x + s);
222
+ const j = Math.floor(y + s);
223
+ const k = Math.floor(z + s);
224
+ const t = (i + j + k) * G3;
225
+ const x0 = x - (i - t);
226
+ const y0 = y - (j - t);
227
+ const z0 = z - (k - t);
228
+ let i1;
229
+ let j1;
230
+ let k1;
231
+ let i2;
232
+ let j2;
233
+ let k2;
234
+ if (x0 >= y0) {
235
+ if (y0 >= z0) {
236
+ i1 = 1;
237
+ j1 = 0;
238
+ k1 = 0;
239
+ i2 = 1;
240
+ j2 = 1;
241
+ k2 = 0;
242
+ } else if (x0 >= z0) {
243
+ i1 = 1;
244
+ j1 = 0;
245
+ k1 = 0;
246
+ i2 = 1;
247
+ j2 = 0;
248
+ k2 = 1;
249
+ } else {
250
+ i1 = 0;
251
+ j1 = 0;
252
+ k1 = 1;
253
+ i2 = 1;
254
+ j2 = 0;
255
+ k2 = 1;
256
+ }
257
+ } else if (y0 < z0) {
258
+ i1 = 0;
259
+ j1 = 0;
260
+ k1 = 1;
261
+ i2 = 0;
262
+ j2 = 1;
263
+ k2 = 1;
264
+ } else if (x0 < z0) {
265
+ i1 = 0;
266
+ j1 = 1;
267
+ k1 = 0;
268
+ i2 = 0;
269
+ j2 = 1;
270
+ k2 = 1;
271
+ } else {
272
+ i1 = 0;
273
+ j1 = 1;
274
+ k1 = 0;
275
+ i2 = 1;
276
+ j2 = 1;
277
+ k2 = 0;
278
+ }
279
+ const x1 = x0 - i1 + G3;
280
+ const y1 = y0 - j1 + G3;
281
+ const z1 = z0 - k1 + G3;
282
+ const x2 = x0 - i2 + 2 * G3;
283
+ const y2 = y0 - j2 + 2 * G3;
284
+ const z2 = z0 - k2 + 2 * G3;
285
+ const x3 = x0 - 1 + 3 * G3;
286
+ const y3 = y0 - 1 + 3 * G3;
287
+ const z3 = z0 - 1 + 3 * G3;
288
+ const ii = i & 255;
289
+ const jj = j & 255;
290
+ const kk = k & 255;
291
+ let n0 = 0;
292
+ let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
293
+ if (t0 >= 0) {
294
+ const gi0 = permMod12[ii + perm[jj + perm[kk]]] * 3;
295
+ t0 *= t0;
296
+ n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0 + grad3[gi0 + 2] * z0);
297
+ }
298
+ let n1 = 0;
299
+ let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
300
+ if (t1 >= 0) {
301
+ const gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]] * 3;
302
+ t1 *= t1;
303
+ n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1 + grad3[gi1 + 2] * z1);
304
+ }
305
+ let n2 = 0;
306
+ let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
307
+ if (t2 >= 0) {
308
+ const gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]] * 3;
309
+ t2 *= t2;
310
+ n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2 + grad3[gi2 + 2] * z2);
311
+ }
312
+ let n3 = 0;
313
+ let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
314
+ if (t3 >= 0) {
315
+ const gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]] * 3;
316
+ t3 *= t3;
317
+ n3 = t3 * t3 * (grad3[gi3] * x3 + grad3[gi3 + 1] * y3 + grad3[gi3 + 2] * z3);
318
+ }
319
+ return 32 * (n0 + n1 + n2 + n3);
320
+ }
321
+ /** Returns noise in approximately [0, 1]. */
322
+ noise3Normalized(x, y, z) {
323
+ return this.noise3(x, y, z) * 0.5 + 0.5;
324
+ }
325
+ };
326
+ function smoothstep(edge0, edge1, x) {
327
+ const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
328
+ return t * t * (3 - 2 * t);
329
+ }
330
+
331
+ // src/animations/wave.ts
332
+ var WaveAnimation = class {
333
+ noise;
334
+ options;
335
+ constructor(options) {
336
+ this.options = { ...options };
337
+ this.noise = new SimplexNoise(options.seed);
338
+ }
339
+ updateOptions(options) {
340
+ if (options.seed !== this.options.seed) {
341
+ this.noise = new SimplexNoise(options.seed);
342
+ }
343
+ this.options = { ...options };
344
+ }
345
+ compute(grid, intensities, time) {
346
+ const { scale, speed, threshold, softness } = this.options;
347
+ const t = time * speed;
348
+ const edge1 = threshold + softness;
349
+ for (let i = 0; i < grid.count; i++) {
350
+ const nx = grid.x[i] * scale;
351
+ const ny = grid.y[i] * scale;
352
+ const noiseValue = this.noise.noise3Normalized(nx, ny, t);
353
+ intensities[i] = smoothstep(threshold, edge1, noiseValue);
354
+ }
355
+ }
356
+ };
357
+
358
+ // src/core/field.ts
359
+ var FieldComputer = class {
360
+ intensities;
361
+ wave;
362
+ random;
363
+ animation;
364
+ constructor(maxDots, config) {
365
+ this.intensities = new Float32Array(maxDots);
366
+ this.animation = config.animation;
367
+ this.wave = new WaveAnimation(config.wave);
368
+ this.random = new RandomAnimation(maxDots, config.random);
369
+ }
370
+ get buffer() {
371
+ return this.intensities;
372
+ }
373
+ updateConfig(config) {
374
+ if (config.animation !== this.animation) {
375
+ this.animation = config.animation;
376
+ if (config.animation === "random") {
377
+ this.random.reset();
378
+ }
379
+ }
380
+ this.wave.updateOptions(config.wave);
381
+ this.random.updateOptions(config.random);
382
+ }
383
+ compute(grid, time, deltaMs) {
384
+ const count = grid.count;
385
+ switch (this.animation) {
386
+ case "none":
387
+ computeNoneField(this.intensities, count);
388
+ break;
389
+ case "wave":
390
+ this.wave.compute(grid, this.intensities, time);
391
+ break;
392
+ case "random":
393
+ this.random.compute(grid, this.intensities, deltaMs);
394
+ break;
395
+ }
396
+ return this.intensities;
397
+ }
398
+ };
399
+
400
+ // src/core/grid.ts
401
+ function buildGrid(options) {
402
+ const { width, height, dotSize, gap, dpr, maxDots } = options;
403
+ const cellX = (dotSize + gap.x) * dpr;
404
+ const cellY = (dotSize + gap.y) * dpr;
405
+ const dotRadius = dotSize * dpr / 2;
406
+ const cols = Math.max(1, Math.floor(width * dpr / cellX));
407
+ const rows = Math.max(1, Math.floor(height * dpr / cellY));
408
+ let count = cols * rows;
409
+ if (count > maxDots) {
410
+ const scale = Math.sqrt(maxDots / count);
411
+ const scaledCols = Math.max(1, Math.floor(cols * scale));
412
+ const scaledRows = Math.max(1, Math.floor(rows * scale));
413
+ count = scaledCols * scaledRows;
414
+ const x2 = new Float32Array(count);
415
+ const y2 = new Float32Array(count);
416
+ const offsetX2 = (width * dpr - (scaledCols - 1) * cellX) / 2 + dotRadius;
417
+ const offsetY2 = (height * dpr - (scaledRows - 1) * cellY) / 2 + dotRadius;
418
+ let index2 = 0;
419
+ for (let row = 0; row < scaledRows; row++) {
420
+ for (let col = 0; col < scaledCols; col++) {
421
+ x2[index2] = offsetX2 + col * cellX;
422
+ y2[index2] = offsetY2 + row * cellY;
423
+ index2++;
424
+ }
425
+ }
426
+ return {
427
+ count,
428
+ cols: scaledCols,
429
+ rows: scaledRows,
430
+ x: x2,
431
+ y: y2,
432
+ width,
433
+ height,
434
+ dpr
435
+ };
436
+ }
437
+ const x = new Float32Array(count);
438
+ const y = new Float32Array(count);
439
+ const offsetX = (width * dpr - (cols - 1) * cellX) / 2 + dotRadius;
440
+ const offsetY = (height * dpr - (rows - 1) * cellY) / 2 + dotRadius;
441
+ let index = 0;
442
+ for (let row = 0; row < rows; row++) {
443
+ for (let col = 0; col < cols; col++) {
444
+ x[index] = offsetX + col * cellX;
445
+ y[index] = offsetY + row * cellY;
446
+ index++;
447
+ }
448
+ }
449
+ return {
450
+ count,
451
+ cols,
452
+ rows,
453
+ x,
454
+ y,
455
+ width,
456
+ height,
457
+ dpr
458
+ };
459
+ }
460
+
461
+ // src/render/canvas2d.ts
462
+ var BUCKET_COUNT = 32;
463
+ var Canvas2DRenderer = class {
464
+ canvas = null;
465
+ ctx = null;
466
+ buckets = Array.from(
467
+ { length: BUCKET_COUNT },
468
+ () => new Int32Array(0)
469
+ );
470
+ bucketCounts = new Int32Array(BUCKET_COUNT);
471
+ foregroundRgb = { r: 255, g: 255, b: 255 };
472
+ lastForeground = "";
473
+ init(container) {
474
+ this.canvas = document.createElement("canvas");
475
+ this.canvas.style.display = "block";
476
+ this.canvas.style.width = "100%";
477
+ this.canvas.style.height = "100%";
478
+ this.canvas.style.pointerEvents = "none";
479
+ const ctx = this.canvas.getContext("2d", { alpha: false });
480
+ if (!ctx) {
481
+ throw new Error("Failed to get 2D canvas context");
482
+ }
483
+ this.ctx = ctx;
484
+ container.appendChild(this.canvas);
485
+ }
486
+ resize(width, height, dpr) {
487
+ if (!this.canvas) return;
488
+ const pixelWidth = Math.max(1, Math.floor(width * dpr));
489
+ const pixelHeight = Math.max(1, Math.floor(height * dpr));
490
+ this.canvas.width = pixelWidth;
491
+ this.canvas.height = pixelHeight;
492
+ this.canvas.style.width = `${width}px`;
493
+ this.canvas.style.height = `${height}px`;
494
+ }
495
+ draw(grid, intensities, options) {
496
+ const ctx = this.ctx;
497
+ const canvas = this.canvas;
498
+ if (!ctx || !canvas) return;
499
+ const { dotSize, foreground, background, dpr } = options;
500
+ const count = grid.count;
501
+ const dotPixelSize = Math.max(1, dotSize * dpr);
502
+ if (foreground !== this.lastForeground) {
503
+ this.foregroundRgb = parseHexColor(foreground);
504
+ this.lastForeground = foreground;
505
+ }
506
+ ctx.fillStyle = background;
507
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
508
+ this.bucketDots(count, intensities);
509
+ const { r, g, b } = this.foregroundRgb;
510
+ const rgb = `rgb(${r},${g},${b})`;
511
+ ctx.fillStyle = rgb;
512
+ for (let bucket = 0; bucket < BUCKET_COUNT; bucket++) {
513
+ const bucketSize = this.bucketCounts[bucket];
514
+ if (bucketSize === 0) continue;
515
+ const alpha = (bucket + 0.5) / BUCKET_COUNT;
516
+ if (alpha < 0.02) continue;
517
+ ctx.globalAlpha = alpha;
518
+ const indices = this.buckets[bucket];
519
+ for (let i = 0; i < bucketSize; i++) {
520
+ const idx = indices[i];
521
+ const x = grid.x[idx] - dotPixelSize / 2;
522
+ const y = grid.y[idx] - dotPixelSize / 2;
523
+ ctx.fillRect(x, y, dotPixelSize, dotPixelSize);
524
+ }
525
+ }
526
+ ctx.globalAlpha = 1;
527
+ }
528
+ bucketDots(count, intensities) {
529
+ this.bucketCounts.fill(0);
530
+ for (let i = 0; i < count; i++) {
531
+ const intensity = intensities[i];
532
+ if (intensity <= 0) continue;
533
+ const bucket = Math.min(BUCKET_COUNT - 1, Math.floor(intensity * BUCKET_COUNT));
534
+ const bucketArray = this.buckets[bucket];
535
+ const bucketIndex = this.bucketCounts[bucket];
536
+ if (bucketIndex >= bucketArray.length) {
537
+ const newSize = bucketArray.length === 0 ? 64 : bucketArray.length * 2;
538
+ const grown = new Int32Array(newSize);
539
+ grown.set(bucketArray);
540
+ this.buckets[bucket] = grown;
541
+ }
542
+ this.buckets[bucket][bucketIndex] = i;
543
+ this.bucketCounts[bucket]++;
544
+ }
545
+ }
546
+ destroy() {
547
+ if (this.canvas?.parentElement) {
548
+ this.canvas.parentElement.removeChild(this.canvas);
549
+ }
550
+ this.canvas = null;
551
+ this.ctx = null;
552
+ }
553
+ getCanvas() {
554
+ return this.canvas;
555
+ }
556
+ };
557
+
558
+ // src/wavedance.ts
559
+ function prefersReducedMotion() {
560
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
561
+ return false;
562
+ }
563
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
564
+ }
565
+ function createWavedance(container, config = {}) {
566
+ if (!container) {
567
+ throw new Error("createWavedance requires a container element");
568
+ }
569
+ let resolved = resolveConfig(config);
570
+ const renderer = new Canvas2DRenderer();
571
+ renderer.init(container);
572
+ let grid = null;
573
+ const field = new FieldComputer(resolved.maxDots, resolved);
574
+ let rafId = 0;
575
+ let running = false;
576
+ let visible = true;
577
+ let inView = true;
578
+ let startTime = performance.now();
579
+ let lastFrameTime = startTime;
580
+ let resizeObserver = null;
581
+ let intersectionObserver = null;
582
+ let motionMediaQuery = null;
583
+ let motionChangeHandler = null;
584
+ let visibilityHandler = null;
585
+ let resizeTimer = 0;
586
+ const shouldAnimate = () => {
587
+ if (resolved.animation === "none") return false;
588
+ if (resolved.respectReducedMotion && prefersReducedMotion()) return false;
589
+ return visible && inView;
590
+ };
591
+ const rebuildGrid = () => {
592
+ const rect = container.getBoundingClientRect();
593
+ const width = Math.max(1, rect.width);
594
+ const height = Math.max(1, rect.height);
595
+ const dpr = resolved.devicePixelRatio;
596
+ grid = buildGrid({
597
+ width,
598
+ height,
599
+ dotSize: resolved.dotSize,
600
+ gap: resolved.gap,
601
+ dpr,
602
+ maxDots: resolved.maxDots
603
+ });
604
+ renderer.resize(width, height, dpr);
605
+ };
606
+ const drawFrame = (time) => {
607
+ if (!grid) return;
608
+ const deltaMs = time - lastFrameTime;
609
+ lastFrameTime = time;
610
+ const intensities = field.compute(grid, time - startTime, deltaMs);
611
+ renderer.draw(grid, intensities, {
612
+ dotSize: resolved.dotSize,
613
+ foreground: resolved.foreground,
614
+ background: resolved.background,
615
+ dpr: resolved.devicePixelRatio
616
+ });
617
+ };
618
+ const loop = (time) => {
619
+ if (!running) return;
620
+ if (shouldAnimate()) {
621
+ drawFrame(time);
622
+ } else if (grid) {
623
+ drawFrame(time);
624
+ }
625
+ rafId = requestAnimationFrame(loop);
626
+ };
627
+ const start = () => {
628
+ if (running) return;
629
+ running = true;
630
+ startTime = performance.now();
631
+ lastFrameTime = startTime;
632
+ rafId = requestAnimationFrame(loop);
633
+ };
634
+ const stop = () => {
635
+ running = false;
636
+ if (rafId) {
637
+ cancelAnimationFrame(rafId);
638
+ rafId = 0;
639
+ }
640
+ };
641
+ const scheduleResize = () => {
642
+ if (resizeTimer) {
643
+ window.clearTimeout(resizeTimer);
644
+ }
645
+ resizeTimer = window.setTimeout(() => {
646
+ rebuildGrid();
647
+ if (grid) {
648
+ drawFrame(performance.now());
649
+ }
650
+ }, 100);
651
+ };
652
+ rebuildGrid();
653
+ start();
654
+ if (typeof ResizeObserver !== "undefined") {
655
+ resizeObserver = new ResizeObserver(scheduleResize);
656
+ resizeObserver.observe(container);
657
+ }
658
+ if (typeof IntersectionObserver !== "undefined") {
659
+ intersectionObserver = new IntersectionObserver(
660
+ (entries) => {
661
+ inView = entries.some((entry) => entry.isIntersecting);
662
+ },
663
+ { root: null, threshold: 0 }
664
+ );
665
+ intersectionObserver.observe(container);
666
+ }
667
+ if (typeof window !== "undefined") {
668
+ visibilityHandler = () => {
669
+ visible = document.visibilityState === "visible";
670
+ };
671
+ document.addEventListener("visibilitychange", visibilityHandler);
672
+ if (typeof window.matchMedia === "function") {
673
+ motionMediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
674
+ motionChangeHandler = () => {
675
+ if (grid) {
676
+ drawFrame(performance.now());
677
+ }
678
+ };
679
+ motionMediaQuery.addEventListener("change", motionChangeHandler);
680
+ }
681
+ }
682
+ return {
683
+ update(partial) {
684
+ const merged = {
685
+ ...resolved,
686
+ ...partial,
687
+ wave: partial.wave ? { ...resolved.wave, ...partial.wave } : resolved.wave,
688
+ random: partial.random ? { ...resolved.random, ...partial.random } : resolved.random
689
+ };
690
+ resolved = resolveConfig(merged);
691
+ field.updateConfig(resolved);
692
+ if (partial.dotSize !== void 0 || partial.gap !== void 0 || partial.devicePixelRatio !== void 0 || partial.maxDots !== void 0) {
693
+ rebuildGrid();
694
+ }
695
+ if (grid) {
696
+ drawFrame(performance.now());
697
+ }
698
+ },
699
+ destroy() {
700
+ stop();
701
+ if (resizeTimer) {
702
+ window.clearTimeout(resizeTimer);
703
+ resizeTimer = 0;
704
+ }
705
+ resizeObserver?.disconnect();
706
+ resizeObserver = null;
707
+ intersectionObserver?.disconnect();
708
+ intersectionObserver = null;
709
+ if (visibilityHandler) {
710
+ document.removeEventListener("visibilitychange", visibilityHandler);
711
+ visibilityHandler = null;
712
+ }
713
+ if (motionMediaQuery && motionChangeHandler) {
714
+ motionMediaQuery.removeEventListener("change", motionChangeHandler);
715
+ motionMediaQuery = null;
716
+ motionChangeHandler = null;
717
+ }
718
+ renderer.destroy();
719
+ grid = null;
720
+ },
721
+ getConfig() {
722
+ return {
723
+ ...resolved,
724
+ gap: { ...resolved.gap },
725
+ wave: { ...resolved.wave },
726
+ random: { ...resolved.random }
727
+ };
728
+ }
729
+ };
730
+ }
731
+
732
+ exports.Canvas2DRenderer = Canvas2DRenderer;
733
+ exports.SimplexNoise = SimplexNoise;
734
+ exports.buildGrid = buildGrid;
735
+ exports.createWavedance = createWavedance;
736
+ exports.normalizeHexColor = normalizeHexColor;
737
+ exports.parseHexColor = parseHexColor;
738
+ exports.resolveConfig = resolveConfig;
739
+ exports.smoothstep = smoothstep;
740
+ //# sourceMappingURL=index.cjs.map
741
+ //# sourceMappingURL=index.cjs.map