@sarmal/core 0.22.0 → 0.24.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.
Files changed (75) hide show
  1. package/dist/auto-init.cjs +158 -139
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.js +157 -138
  4. package/dist/auto-init.js.map +1 -1
  5. package/dist/curves/artemis2.cjs +7 -10
  6. package/dist/curves/artemis2.d.cts +1 -1
  7. package/dist/curves/artemis2.d.ts +1 -1
  8. package/dist/curves/artemis2.js +6 -9
  9. package/dist/curves/astroid.cjs +4 -4
  10. package/dist/curves/astroid.d.cts +1 -1
  11. package/dist/curves/astroid.d.ts +1 -1
  12. package/dist/curves/astroid.js +3 -3
  13. package/dist/curves/deltoid.cjs +4 -4
  14. package/dist/curves/deltoid.d.cts +1 -1
  15. package/dist/curves/deltoid.d.ts +1 -1
  16. package/dist/curves/deltoid.js +3 -3
  17. package/dist/curves/epicycloid3.cjs +4 -4
  18. package/dist/curves/epicycloid3.d.cts +1 -1
  19. package/dist/curves/epicycloid3.d.ts +1 -1
  20. package/dist/curves/epicycloid3.js +3 -3
  21. package/dist/curves/epitrochoid7.cjs +5 -5
  22. package/dist/curves/epitrochoid7.d.cts +1 -1
  23. package/dist/curves/epitrochoid7.d.ts +1 -1
  24. package/dist/curves/epitrochoid7.js +4 -4
  25. package/dist/curves/index.cjs +40 -53
  26. package/dist/curves/index.d.cts +29 -29
  27. package/dist/curves/index.d.ts +29 -29
  28. package/dist/curves/index.js +40 -69
  29. package/dist/curves/lame.cjs +5 -6
  30. package/dist/curves/lame.d.cts +1 -1
  31. package/dist/curves/lame.d.ts +1 -1
  32. package/dist/curves/lame.js +4 -5
  33. package/dist/curves/lissajous32.cjs +4 -4
  34. package/dist/curves/lissajous32.d.cts +1 -1
  35. package/dist/curves/lissajous32.d.ts +1 -1
  36. package/dist/curves/lissajous32.js +3 -3
  37. package/dist/curves/lissajous43.cjs +4 -4
  38. package/dist/curves/lissajous43.d.cts +1 -1
  39. package/dist/curves/lissajous43.d.ts +1 -1
  40. package/dist/curves/lissajous43.js +3 -3
  41. package/dist/curves/rose3.cjs +4 -4
  42. package/dist/curves/rose3.d.cts +1 -1
  43. package/dist/curves/rose3.d.ts +1 -1
  44. package/dist/curves/rose3.js +3 -3
  45. package/dist/curves/rose5.cjs +4 -4
  46. package/dist/curves/rose5.d.cts +1 -1
  47. package/dist/curves/rose5.d.ts +1 -1
  48. package/dist/curves/rose5.js +3 -3
  49. package/dist/curves/rose52.cjs +5 -5
  50. package/dist/curves/rose52.d.cts +1 -1
  51. package/dist/curves/rose52.d.ts +1 -1
  52. package/dist/curves/rose52.js +4 -4
  53. package/dist/curves/star.cjs +5 -8
  54. package/dist/curves/star.d.cts +1 -1
  55. package/dist/curves/star.d.ts +1 -1
  56. package/dist/curves/star.js +4 -7
  57. package/dist/curves/star4.cjs +5 -8
  58. package/dist/curves/star4.d.cts +1 -1
  59. package/dist/curves/star4.d.ts +1 -1
  60. package/dist/curves/star4.js +4 -7
  61. package/dist/curves/star7.cjs +5 -8
  62. package/dist/curves/star7.d.cts +1 -1
  63. package/dist/curves/star7.d.ts +1 -1
  64. package/dist/curves/star7.js +4 -7
  65. package/dist/index.cjs +150 -135
  66. package/dist/index.cjs.map +1 -1
  67. package/dist/index.d.cts +33 -74
  68. package/dist/index.d.ts +33 -74
  69. package/dist/index.js +150 -155
  70. package/dist/index.js.map +1 -1
  71. package/dist/types-CknrlCAf.d.cts +314 -0
  72. package/dist/types-CknrlCAf.d.ts +314 -0
  73. package/package.json +1 -1
  74. package/dist/types-DVerJ9cl.d.cts +0 -321
  75. package/dist/types-DVerJ9cl.d.ts +0 -321
package/dist/auto-init.js CHANGED
@@ -61,13 +61,13 @@ function resolveCurve(curveDef) {
61
61
  period,
62
62
  speed,
63
63
  skeleton: curveDef.skeleton,
64
- skeletonFn: curveDef.skeletonFn,
64
+ skeletonFn: curveDef.skeletonFn
65
65
  };
66
66
  }
67
67
  function createEngine(curveDef, trailLength = 120) {
68
68
  if (!Number.isFinite(trailLength) || trailLength <= 0) {
69
69
  throw new RangeError(
70
- `[sarmal] trailLength must be a positive finite number, got ${trailLength}`,
70
+ `[sarmal] trailLength must be a positive finite number, got ${trailLength}`
71
71
  );
72
72
  }
73
73
  let curve = resolveCurve(curveDef);
@@ -108,7 +108,7 @@ function createEngine(curveDef, trailLength = 120) {
108
108
  actualTime += deltaTime;
109
109
  if (morphCurveB !== null && _morphAlpha !== null) {
110
110
  const a = curve.fn(t, actualTime, EMPTY_PARAMS);
111
- const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
111
+ const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
112
112
  const b = morphCurveB.fn(tB, actualTime, EMPTY_PARAMS);
113
113
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
114
114
  } else {
@@ -135,14 +135,14 @@ function createEngine(curveDef, trailLength = 120) {
135
135
  trail.clear();
136
136
  },
137
137
  jump(newT, { clearTrail = false } = {}) {
138
- t = ((newT % curve.period) + curve.period) % curve.period;
138
+ t = (newT % curve.period + curve.period) % curve.period;
139
139
  if (clearTrail) {
140
140
  trail.clear();
141
141
  }
142
142
  },
143
143
  seek(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
144
144
  const advance = curve.speed * step;
145
- const target = ((targetT % curve.period) + curve.period) % curve.period;
145
+ const target = (targetT % curve.period + curve.period) % curve.period;
146
146
  const targetTime = target / curve.speed;
147
147
  t = target;
148
148
  actualTime = targetTime;
@@ -151,7 +151,7 @@ function createEngine(curveDef, trailLength = 120) {
151
151
  const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
152
152
  for (let i = count - 1; i >= 0; i--) {
153
153
  const sampleT = target - i * advance;
154
- const wrappedT = ((sampleT % curve.period) + curve.period) % curve.period;
154
+ const wrappedT = (sampleT % curve.period + curve.period) % curve.period;
155
155
  const time = targetTime - i * step;
156
156
  const point = curve.fn(wrappedT, time, EMPTY_PARAMS);
157
157
  trail.push(point.x, point.y);
@@ -168,16 +168,13 @@ function createEngine(curveDef, trailLength = 120) {
168
168
  ...frozenB,
169
169
  fn: (sampleT, time, params) => {
170
170
  const a = frozenA.fn(sampleT, time, params);
171
- const tB =
172
- frozenStrategy === "normalized"
173
- ? (sampleT / frozenA.period) * frozenB.period
174
- : sampleT;
171
+ const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
175
172
  const b = frozenB.fn(tB, time, params);
176
173
  return {
177
174
  x: a.x + (b.x - a.x) * frozenAlpha,
178
- y: a.y + (b.y - a.y) * frozenAlpha,
175
+ y: a.y + (b.y - a.y) * frozenAlpha
179
176
  };
180
- },
177
+ }
181
178
  };
182
179
  }
183
180
  _morphStrategy = strategy;
@@ -190,7 +187,7 @@ function createEngine(curveDef, trailLength = 120) {
190
187
  completeMorph() {
191
188
  if (morphCurveB !== null) {
192
189
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
193
- t = (t / curve.period) * morphCurveB.period;
190
+ t = t / curve.period * morphCurveB.period;
194
191
  }
195
192
  curve = morphCurveB;
196
193
  }
@@ -202,22 +199,19 @@ function createEngine(curveDef, trailLength = 120) {
202
199
  const points = new Array(steps);
203
200
  if (morphCurveB !== null && _morphAlpha !== null) {
204
201
  for (let i = 0; i < steps; i++) {
205
- const sampleT = (i / (steps - 1)) * curve.period;
202
+ const sampleT = i / (steps - 1) * curve.period;
206
203
  const a = sampleSkeleton(curve, sampleT);
207
- const tB =
208
- _morphStrategy === "normalized"
209
- ? (sampleT / curve.period) * morphCurveB.period
210
- : sampleT;
204
+ const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
211
205
  const b = sampleSkeleton(morphCurveB, tB);
212
206
  points[i] = {
213
207
  x: a.x + (b.x - a.x) * _morphAlpha,
214
- y: a.y + (b.y - a.y) * _morphAlpha,
208
+ y: a.y + (b.y - a.y) * _morphAlpha
215
209
  };
216
210
  }
217
211
  return points;
218
212
  }
219
213
  for (let i = 0; i < steps; i++) {
220
- const sampleT = (i / (steps - 1)) * curve.period;
214
+ const sampleT = i / (steps - 1) * curve.period;
221
215
  points[i] = sampleSkeleton(curve, sampleT);
222
216
  }
223
217
  return points;
@@ -259,7 +253,7 @@ function createEngine(curveDef, trailLength = 120) {
259
253
  _speedTransition.reject(new Error("Speed transition cancelled"));
260
254
  _speedTransition = null;
261
255
  }
262
- },
256
+ }
263
257
  };
264
258
  }
265
259
 
@@ -298,15 +292,7 @@ function computeNormal(trail, i) {
298
292
  const tangent = computeTangent(trail, i);
299
293
  return { x: -tangent.y, y: tangent.x };
300
294
  }
301
- function computeTrailQuad(
302
- trail,
303
- i,
304
- trailCount,
305
- toX,
306
- toY,
307
- minWidth = TRAIL_MIN_WIDTH,
308
- maxWidth = TRAIL_MAX_WIDTH,
309
- ) {
295
+ function computeTrailQuad(trail, i, trailCount, toX, toY, minWidth = TRAIL_MIN_WIDTH, maxWidth = TRAIL_MAX_WIDTH) {
310
296
  const progress = i / (trailCount - 1);
311
297
  const nextProgress = (i + 1) / (trailCount - 1);
312
298
  const opacity = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
@@ -330,16 +316,13 @@ function computeTrailQuad(
330
316
  r1x: nx - n1.x * w1,
331
317
  r1y: ny - n1.y * w1,
332
318
  opacity,
333
- progress,
319
+ progress
334
320
  };
335
321
  }
336
322
  function computeBoundaries(pts, logicalWidth, logicalHeight) {
337
323
  if (pts.length === 0) return null;
338
324
  const first = pts[0];
339
- let minX = first.x,
340
- maxX = first.x,
341
- minY = first.y,
342
- maxY = first.y;
325
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
343
326
  for (const p of pts) {
344
327
  if (p.x < minX) {
345
328
  minX = p.x;
@@ -358,7 +341,7 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
358
341
  const h = maxY - minY;
359
342
  if (w === 0 && h === 0) {
360
343
  throw new Error(
361
- "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
344
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
362
345
  );
363
346
  }
364
347
  const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
@@ -369,12 +352,12 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
369
352
  scaleXProportional,
370
353
  scaleYProportional,
371
354
  scaleXMinPadding,
372
- scaleYMinPadding,
355
+ scaleYMinPadding
373
356
  );
374
357
  return {
375
358
  scale,
376
359
  offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
377
- offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
360
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale
378
361
  };
379
362
  }
380
363
  function enginePassthroughs(engine) {
@@ -384,17 +367,17 @@ function enginePassthroughs(engine) {
384
367
  setSpeed: engine.setSpeed,
385
368
  getSpeed: engine.getSpeed,
386
369
  resetSpeed: engine.resetSpeed,
387
- setSpeedOver: engine.setSpeedOver,
370
+ setSpeedOver: engine.setSpeedOver
388
371
  };
389
372
  }
390
373
  function hexToRgb(hex) {
391
374
  const n = parseInt(hex.slice(1), 16);
392
- return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
375
+ return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
393
376
  }
394
377
  var lerpRgb = (a, b, t) => ({
395
378
  r: Math.round(a.r + (b.r - a.r) * t),
396
379
  g: Math.round(a.g + (b.g - a.g) * t),
397
- b: Math.round(a.b + (b.b - a.b) * t),
380
+ b: Math.round(a.b + (b.b - a.b) * t)
398
381
  });
399
382
  function getPaletteColor(palette, position, timeOffset = 0) {
400
383
  if (palette.length === 0) {
@@ -403,7 +386,7 @@ function getPaletteColor(palette, position, timeOffset = 0) {
403
386
  if (palette.length === 1) {
404
387
  return hexToRgb(palette[0]);
405
388
  }
406
- const cyclePos = (((position + timeOffset) % 1) + 1) % 1;
389
+ const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
407
390
  const scaled = cyclePos * palette.length;
408
391
  const idx = Math.floor(scaled);
409
392
  const t = scaled - idx;
@@ -418,7 +401,7 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
418
401
  "headColor",
419
402
  "skeletonColor",
420
403
  "trailStyle",
421
- "headRadius",
404
+ "headRadius"
422
405
  ]);
423
406
  function validateRenderOptions(partial) {
424
407
  for (const key of Object.keys(partial)) {
@@ -446,7 +429,7 @@ function assertTrailColor(value) {
446
429
  if (typeof value === "string") {
447
430
  if (!HEX_COLOR_RE.test(value)) {
448
431
  throw new TypeError(
449
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`,
432
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
450
433
  );
451
434
  }
452
435
  return;
@@ -454,21 +437,21 @@ function assertTrailColor(value) {
454
437
  if (Array.isArray(value)) {
455
438
  if (value.length < 2) {
456
439
  throw new RangeError(
457
- `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`,
440
+ `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`
458
441
  );
459
442
  }
460
443
  for (let i = 0; i < value.length; i++) {
461
444
  const entry = value[i];
462
445
  if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
463
446
  throw new TypeError(
464
- `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`,
447
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
465
448
  );
466
449
  }
467
450
  }
468
451
  return;
469
452
  }
470
453
  throw new TypeError(
471
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`,
454
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
472
455
  );
473
456
  }
474
457
  function assertHeadColor(value) {
@@ -477,7 +460,7 @@ function assertHeadColor(value) {
477
460
  }
478
461
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
479
462
  throw new TypeError(
480
- `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`,
463
+ `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
481
464
  );
482
465
  }
483
466
  }
@@ -487,26 +470,26 @@ function assertSkeletonColor(value) {
487
470
  }
488
471
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
489
472
  throw new TypeError(
490
- `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`,
473
+ `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
491
474
  );
492
475
  }
493
476
  }
494
477
  function assertTrailStyle(value) {
495
478
  if (!TRAIL_STYLES.includes(value)) {
496
479
  throw new RangeError(
497
- `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`,
480
+ `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`
498
481
  );
499
482
  }
500
483
  }
501
484
  function assertHeadRadius(value) {
502
485
  if (typeof value !== "number") {
503
486
  throw new TypeError(
504
- `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`,
487
+ `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`
505
488
  );
506
489
  }
507
490
  if (!Number.isFinite(value) || value <= 0) {
508
491
  throw new TypeError(
509
- `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`,
492
+ `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`
510
493
  );
511
494
  }
512
495
  }
@@ -528,23 +511,23 @@ function resolveHeadColor(trailColor, trailStyle) {
528
511
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
529
512
  if (trailStyle === "default" && Array.isArray(trailColor)) {
530
513
  console.warn(
531
- '[sarmal] trailColor is an array but trailStyle is "default"; only the first color will be used. Pass a gradient trailStyle to use the whole palette.',
514
+ '[sarmal] trailColor is an array but trailStyle is "default"; only the first color will be used. Pass a gradient trailStyle to use the whole palette.'
532
515
  );
533
516
  return;
534
517
  }
535
518
  if (trailStyle !== "default" && typeof trailColor === "string") {
536
519
  console.warn(
537
- `[sarmal] trailColor is a single color but trailStyle is "${trailStyle}"; the trail will render as a solid color. Pass an array of hex colors to use a real gradient.`,
520
+ `[sarmal] trailColor is a single color but trailStyle is "${trailStyle}"; the trail will render as a solid color. Pass an array of hex colors to use a real gradient.`
538
521
  );
539
522
  }
540
523
  }
541
- var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
542
524
 
543
525
  // src/renderer.ts
526
+ var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
544
527
  var WHITE_HEX = "#ffffff";
545
528
  function hexToRgbComponents(hex) {
546
529
  const n = parseInt(hex.slice(1), 16);
547
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
530
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
548
531
  }
549
532
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
550
533
  target.style.width = `${logicalWidth}px`;
@@ -588,6 +571,7 @@ function createRenderer(options) {
588
571
  let offsetY = 0;
589
572
  let animationId = null;
590
573
  let lastTime = 0;
574
+ let pausedByVisibility = false;
591
575
  let morphResolve = null;
592
576
  let morphReject = null;
593
577
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -670,7 +654,7 @@ function createRenderer(options) {
670
654
  i,
671
655
  trailCount,
672
656
  toX,
673
- toY,
657
+ toY
674
658
  );
675
659
  if (trailStyle === "default") {
676
660
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
@@ -781,6 +765,7 @@ function createRenderer(options) {
781
765
  cancelAnimationFrame(animationId);
782
766
  animationId = null;
783
767
  }
768
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
784
769
  if (morphReject !== null) {
785
770
  morphReject(new Error("Instance destroyed during morph"));
786
771
  morphResolve = null;
@@ -834,16 +819,38 @@ function createRenderer(options) {
834
819
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
835
820
  warnIfTrailColorMismatch(trailColor, trailStyle);
836
821
  }
837
- },
822
+ }
838
823
  };
839
- if (shouldAutoStart) {
824
+ const pauseOnHidden = options.pauseOnHidden !== false;
825
+ function handleVisibilityChange() {
826
+ if (document.hidden) {
827
+ if (animationId !== null) {
828
+ instance.pause();
829
+ pausedByVisibility = true;
830
+ }
831
+ } else {
832
+ if (pausedByVisibility) {
833
+ pausedByVisibility = false;
834
+ instance.play();
835
+ }
836
+ }
837
+ }
838
+ if (pauseOnHidden) {
839
+ document.addEventListener("visibilitychange", handleVisibilityChange);
840
+ }
841
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHidden && document.hidden);
842
+ if (actuallyAutoStart) {
840
843
  instance.play();
844
+ } else if (shouldAutoStart) {
845
+ pausedByVisibility = true;
841
846
  }
842
847
  return instance;
843
848
  }
844
849
 
845
850
  // src/renderer-svg.ts
846
851
  var EMPTY_PARAMS2 = {};
852
+ var SVG_DEFAULT_HEAD_RADIUS = 0.5;
853
+ var SKELETON_STROKE_WIDTH_PX = 1.5;
847
854
  var HIGH_TRAIL_LENGTH_THRESHOLD = 5e3;
848
855
  function pointsToPathString(pts, scale, offsetX, offsetY) {
849
856
  if (pts.length < 2) {
@@ -862,7 +869,7 @@ function sampleCurveSkeleton(curveDef) {
862
869
  const samples = Math.ceil(period * 50);
863
870
  const pts = Array.from({ length: samples });
864
871
  for (let i = 0; i < samples; i++) {
865
- const t = (i / (samples - 1)) * period;
872
+ const t = i / (samples - 1) * period;
866
873
  pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
867
874
  }
868
875
  return pts;
@@ -875,7 +882,7 @@ function createSVGRenderer(options) {
875
882
  const poolSize = engine.trailLength;
876
883
  if (poolSize > HIGH_TRAIL_LENGTH_THRESHOLD) {
877
884
  console.warn(
878
- `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`,
885
+ `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`
879
886
  );
880
887
  }
881
888
  let trailStyle = options.trailStyle ?? "default";
@@ -883,15 +890,21 @@ function createSVGRenderer(options) {
883
890
  let skeletonColor = options.skeletonColor ?? "#ffffff";
884
891
  let userHeadColor = options.headColor ?? null;
885
892
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
886
- let headRadius = options.headRadius ?? 1.5;
893
+ let headRadius;
887
894
  let trailSolid = resolveTrailMainColor(trailColor);
888
895
  let trailPalette = resolveTrailPalette(trailColor);
889
896
  const ariaLabel = options.ariaLabel ?? "Loading";
890
897
  warnIfTrailColorMismatch(trailColor, trailStyle);
891
898
  const viewSize = 100;
892
- const svgTrailMinWidth = 0.25;
893
- const svgTrailMaxWidth = 1.25;
894
- const svgSkeletonStrokeWidth = "0.75";
899
+ function getContainerPixelSize() {
900
+ const rect = container.getBoundingClientRect();
901
+ return rect.width && rect.height ? Math.min(rect.width, rect.height) : 200;
902
+ }
903
+ const containerPx = getContainerPixelSize();
904
+ const svgTrailMinWidth = TRAIL_MIN_WIDTH * viewSize / containerPx;
905
+ const svgTrailMaxWidth = TRAIL_MAX_WIDTH * viewSize / containerPx;
906
+ const svgSkeletonStrokeWidth = String(SKELETON_STROKE_WIDTH_PX * viewSize / containerPx);
907
+ headRadius = options.headRadius ?? SVG_DEFAULT_HEAD_RADIUS;
895
908
  container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
896
909
  container.setAttribute("role", "img");
897
910
  container.setAttribute("aria-label", ariaLabel);
@@ -978,7 +991,7 @@ function createSVGRenderer(options) {
978
991
  px,
979
992
  py,
980
993
  svgTrailMinWidth,
981
- svgTrailMaxWidth,
994
+ svgTrailMaxWidth
982
995
  );
983
996
  const d = `M${l0x.toFixed(2)} ${l0y.toFixed(2)} L${l1x.toFixed(2)} ${l1y.toFixed(2)} L${r1x.toFixed(2)} ${r1y.toFixed(2)} L${r0x.toFixed(2)} ${r0y.toFixed(2)} Z`;
984
997
  trailPaths[i].setAttribute("d", d);
@@ -1005,8 +1018,8 @@ function createSVGRenderer(options) {
1005
1018
  }
1006
1019
  let animationId = null;
1007
1020
  let lastTime = 0;
1008
- const prefersReducedMotion =
1009
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1021
+ let pausedByVisibility = false;
1022
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1010
1023
  let morphResolve = null;
1011
1024
  let morphReject = null;
1012
1025
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -1024,7 +1037,7 @@ function createSVGRenderer(options) {
1024
1037
  skeletonPathA.setAttribute("visibility", "visible");
1025
1038
  skeletonPathA.setAttribute(
1026
1039
  "stroke-opacity",
1027
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
1040
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
1028
1041
  );
1029
1042
  }
1030
1043
  if (morphPathBBuilt) {
@@ -1096,6 +1109,7 @@ function createSVGRenderer(options) {
1096
1109
  cancelAnimationFrame(animationId);
1097
1110
  animationId = null;
1098
1111
  }
1112
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
1099
1113
  if (morphReject !== null) {
1100
1114
  morphReject(new Error("Instance destroyed during morph"));
1101
1115
  morphResolve = null;
@@ -1177,10 +1191,30 @@ function createSVGRenderer(options) {
1177
1191
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1178
1192
  warnIfTrailColorMismatch(trailColor, trailStyle);
1179
1193
  }
1180
- },
1194
+ }
1181
1195
  };
1182
- if (shouldAutoStart) {
1196
+ const pauseOnHidden = options.pauseOnHidden !== false;
1197
+ function handleVisibilityChange() {
1198
+ if (document.hidden) {
1199
+ if (animationId !== null) {
1200
+ instance.pause();
1201
+ pausedByVisibility = true;
1202
+ }
1203
+ } else {
1204
+ if (pausedByVisibility) {
1205
+ pausedByVisibility = false;
1206
+ instance.play();
1207
+ }
1208
+ }
1209
+ }
1210
+ if (pauseOnHidden) {
1211
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1212
+ }
1213
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHidden && document.hidden);
1214
+ if (actuallyAutoStart) {
1183
1215
  instance.play();
1216
+ } else if (shouldAutoStart) {
1217
+ pausedByVisibility = true;
1184
1218
  }
1185
1219
  return instance;
1186
1220
  }
@@ -1193,22 +1227,19 @@ function createSarmalSVG(container, curveDef, options) {
1193
1227
  // src/curves/artemis2.ts
1194
1228
  var TWO_PI2 = Math.PI * 2;
1195
1229
  function artemis2Fn(t, _time, _params) {
1196
- const a = 0.35,
1197
- b = 0.15,
1198
- ox = 0.175;
1199
- const s = Math.sin(t),
1200
- c = Math.cos(t);
1230
+ const a = 0.35, b = 0.15, ox = 0.175;
1231
+ const s = Math.sin(t), c = Math.cos(t);
1201
1232
  const denom = 1 + s * s;
1202
1233
  return {
1203
- x: (c * (1 + a * c)) / denom - ox,
1204
- y: (s * c * (1 + b * c)) / denom,
1234
+ x: c * (1 + a * c) / denom - ox,
1235
+ y: s * c * (1 + b * c) / denom
1205
1236
  };
1206
1237
  }
1207
1238
  var artemis2 = {
1208
1239
  name: "Artemis II",
1209
1240
  fn: artemis2Fn,
1210
1241
  period: TWO_PI2,
1211
- speed: 0.7,
1242
+ speed: 0.7
1212
1243
  };
1213
1244
 
1214
1245
  // src/curves/astroid.ts
@@ -1218,14 +1249,14 @@ function astroidFn(t, _time, _params) {
1218
1249
  const s = Math.sin(t);
1219
1250
  return {
1220
1251
  x: c * c * c,
1221
- y: s * s * s,
1252
+ y: s * s * s
1222
1253
  };
1223
1254
  }
1224
1255
  var astroid = {
1225
1256
  name: "Astroid",
1226
1257
  fn: astroidFn,
1227
1258
  period: TWO_PI3,
1228
- speed: 1.1,
1259
+ speed: 1.1
1229
1260
  };
1230
1261
 
1231
1262
  // src/curves/deltoid.ts
@@ -1233,14 +1264,14 @@ var TWO_PI4 = Math.PI * 2;
1233
1264
  function deltoidFn(t, _time, _params) {
1234
1265
  return {
1235
1266
  x: 2 * Math.cos(t) + Math.cos(2 * t),
1236
- y: 2 * Math.sin(t) - Math.sin(2 * t),
1267
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
1237
1268
  };
1238
1269
  }
1239
1270
  var deltoid = {
1240
1271
  name: "Deltoid",
1241
1272
  fn: deltoidFn,
1242
1273
  period: TWO_PI4,
1243
- speed: 0.9,
1274
+ speed: 0.9
1244
1275
  };
1245
1276
 
1246
1277
  // src/curves/epicycloid3.ts
@@ -1248,14 +1279,14 @@ var TWO_PI5 = Math.PI * 2;
1248
1279
  function epicycloid3Fn(t, _time, _params) {
1249
1280
  return {
1250
1281
  x: 4 * Math.cos(t) - Math.cos(4 * t),
1251
- y: 4 * Math.sin(t) - Math.sin(4 * t),
1282
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
1252
1283
  };
1253
1284
  }
1254
1285
  var epicycloid3 = {
1255
1286
  name: "Epicycloid (n=3)",
1256
1287
  fn: epicycloid3Fn,
1257
1288
  period: TWO_PI5,
1258
- speed: 0.75,
1289
+ speed: 0.75
1259
1290
  };
1260
1291
 
1261
1292
  // src/curves/epitrochoid7.ts
@@ -1264,14 +1295,14 @@ function epitrochoid7Fn(t, _time, _params) {
1264
1295
  const d = 1 + 0.55 * Math.sin(t * 0.5);
1265
1296
  return {
1266
1297
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1267
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1298
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1268
1299
  };
1269
1300
  }
1270
1301
  function epitrochoid7SkeletonFn(t) {
1271
1302
  const d = 1.275;
1272
1303
  return {
1273
1304
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1274
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1305
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1275
1306
  };
1276
1307
  }
1277
1308
  var epitrochoid7 = {
@@ -1279,7 +1310,7 @@ var epitrochoid7 = {
1279
1310
  fn: epitrochoid7Fn,
1280
1311
  period: TWO_PI6,
1281
1312
  speed: 1.4,
1282
- skeletonFn: epitrochoid7SkeletonFn,
1313
+ skeletonFn: epitrochoid7SkeletonFn
1283
1314
  };
1284
1315
 
1285
1316
  // src/curves/lissajous32.ts
@@ -1288,7 +1319,7 @@ function lissajous32Fn(t, time, _params) {
1288
1319
  const phi = time * 0.45;
1289
1320
  return {
1290
1321
  x: Math.sin(3 * t + phi),
1291
- y: Math.sin(2 * t),
1322
+ y: Math.sin(2 * t)
1292
1323
  };
1293
1324
  }
1294
1325
  var lissajous32 = {
@@ -1296,7 +1327,7 @@ var lissajous32 = {
1296
1327
  fn: lissajous32Fn,
1297
1328
  period: TWO_PI7,
1298
1329
  speed: 2,
1299
- skeleton: "live",
1330
+ skeleton: "live"
1300
1331
  };
1301
1332
 
1302
1333
  // src/curves/lissajous43.ts
@@ -1305,7 +1336,7 @@ function lissajous43Fn(t, time, _params) {
1305
1336
  const phi = time * 0.38;
1306
1337
  return {
1307
1338
  x: Math.sin(4 * t + phi),
1308
- y: Math.sin(3 * t),
1339
+ y: Math.sin(3 * t)
1309
1340
  };
1310
1341
  }
1311
1342
  var lissajous43 = {
@@ -1313,18 +1344,17 @@ var lissajous43 = {
1313
1344
  fn: lissajous43Fn,
1314
1345
  period: TWO_PI8,
1315
1346
  speed: 1.8,
1316
- skeleton: "live",
1347
+ skeleton: "live"
1317
1348
  };
1318
1349
 
1319
1350
  // src/curves/lame.ts
1320
1351
  var TWO_PI9 = Math.PI * 2;
1321
1352
  function lameFn(t, time, _params) {
1322
1353
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1323
- const c = Math.cos(t),
1324
- s = Math.sin(t);
1354
+ const c = Math.cos(t), s = Math.sin(t);
1325
1355
  return {
1326
1356
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1327
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
1357
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
1328
1358
  };
1329
1359
  }
1330
1360
  var lame = {
@@ -1332,7 +1362,7 @@ var lame = {
1332
1362
  fn: lameFn,
1333
1363
  period: TWO_PI9,
1334
1364
  speed: 1,
1335
- skeleton: "live",
1365
+ skeleton: "live"
1336
1366
  };
1337
1367
 
1338
1368
  // src/curves/rose3.ts
@@ -1341,14 +1371,14 @@ function rose3Fn(t, _time, _params) {
1341
1371
  const r = Math.cos(3 * t);
1342
1372
  return {
1343
1373
  x: r * Math.cos(t),
1344
- y: r * Math.sin(t),
1374
+ y: r * Math.sin(t)
1345
1375
  };
1346
1376
  }
1347
1377
  var rose3 = {
1348
1378
  name: "Rose (n=3)",
1349
1379
  fn: rose3Fn,
1350
1380
  period: TWO_PI10,
1351
- speed: 1.15,
1381
+ speed: 1.15
1352
1382
  };
1353
1383
 
1354
1384
  // src/curves/rose5.ts
@@ -1357,87 +1387,78 @@ function rose5Fn(t, _time, _params) {
1357
1387
  const r = Math.cos(5 * t);
1358
1388
  return {
1359
1389
  x: r * Math.cos(t),
1360
- y: r * Math.sin(t),
1390
+ y: r * Math.sin(t)
1361
1391
  };
1362
1392
  }
1363
1393
  var rose5 = {
1364
1394
  name: "Rose (n=5)",
1365
1395
  fn: rose5Fn,
1366
1396
  period: TWO_PI11,
1367
- speed: 1,
1397
+ speed: 1
1368
1398
  };
1369
1399
 
1370
1400
  // src/curves/rose52.ts
1371
1401
  var FOUR_PI = Math.PI * 4;
1372
1402
  function rose52Fn(t, _time, _params) {
1373
- const r = Math.cos((5 / 2) * t);
1403
+ const r = Math.cos(5 / 2 * t);
1374
1404
  return {
1375
1405
  x: r * Math.cos(t),
1376
- y: r * Math.sin(t),
1406
+ y: r * Math.sin(t)
1377
1407
  };
1378
1408
  }
1379
1409
  var rose52 = {
1380
1410
  name: "Rose (n=5/2)",
1381
1411
  fn: rose52Fn,
1382
1412
  period: FOUR_PI,
1383
- speed: 0.8,
1413
+ speed: 0.8
1384
1414
  };
1385
1415
 
1386
1416
  // src/curves/star.ts
1387
1417
  var TWO_PI12 = Math.PI * 2;
1388
1418
  function starFn(t, _time, _params) {
1389
- const r =
1390
- Math.abs(Math.cos((5 / 2) * t)) +
1391
- 0.35 * Math.abs(Math.cos((15 / 2) * t)) +
1392
- 0.15 * Math.abs(Math.cos((25 / 2) * t));
1419
+ const r = Math.abs(Math.cos(5 / 2 * t)) + 0.35 * Math.abs(Math.cos(15 / 2 * t)) + 0.15 * Math.abs(Math.cos(25 / 2 * t));
1393
1420
  return {
1394
1421
  x: r * Math.cos(t),
1395
- y: r * Math.sin(t),
1422
+ y: r * Math.sin(t)
1396
1423
  };
1397
1424
  }
1398
1425
  var star = {
1399
1426
  name: "Star",
1400
1427
  fn: starFn,
1401
1428
  period: TWO_PI12,
1402
- speed: 1,
1429
+ speed: 1
1403
1430
  };
1404
1431
 
1405
1432
  // src/curves/star4.ts
1406
1433
  var TWO_PI13 = Math.PI * 2;
1407
1434
  function star4Fn(t, _time, _params) {
1408
- const r =
1409
- Math.abs(Math.cos(2 * t)) +
1410
- 0.35 * Math.abs(Math.cos(6 * t)) +
1411
- 0.15 * Math.abs(Math.cos(10 * t));
1435
+ const r = Math.abs(Math.cos(2 * t)) + 0.35 * Math.abs(Math.cos(6 * t)) + 0.15 * Math.abs(Math.cos(10 * t));
1412
1436
  return {
1413
1437
  x: r * Math.cos(t),
1414
- y: r * Math.sin(t),
1438
+ y: r * Math.sin(t)
1415
1439
  };
1416
1440
  }
1417
1441
  var star4 = {
1418
1442
  name: "Star (4-arm)",
1419
1443
  fn: star4Fn,
1420
1444
  period: TWO_PI13,
1421
- speed: 1,
1445
+ speed: 1
1422
1446
  };
1423
1447
 
1424
1448
  // src/curves/star7.ts
1425
1449
  var TWO_PI14 = Math.PI * 2;
1426
1450
  function star7Fn(t, _time, _params) {
1427
- const r =
1428
- Math.abs(Math.cos((7 / 2) * t)) +
1429
- 0.35 * Math.abs(Math.cos((21 / 2) * t)) +
1430
- 0.15 * Math.abs(Math.cos((35 / 2) * t));
1451
+ const r = Math.abs(Math.cos(7 / 2 * t)) + 0.35 * Math.abs(Math.cos(21 / 2 * t)) + 0.15 * Math.abs(Math.cos(35 / 2 * t));
1431
1452
  return {
1432
1453
  x: r * Math.cos(t),
1433
- y: r * Math.sin(t),
1454
+ y: r * Math.sin(t)
1434
1455
  };
1435
1456
  }
1436
1457
  var star7 = {
1437
1458
  name: "Star (7-arm)",
1438
1459
  fn: star7Fn,
1439
1460
  period: TWO_PI14,
1440
- speed: 1,
1461
+ speed: 1
1441
1462
  };
1442
1463
 
1443
1464
  // src/curves/index.ts
@@ -1455,7 +1476,7 @@ var curves = {
1455
1476
  lissajous32,
1456
1477
  lissajous43,
1457
1478
  epicycloid3,
1458
- lame,
1479
+ lame
1459
1480
  };
1460
1481
 
1461
1482
  // src/index.ts
@@ -1472,21 +1493,22 @@ function parseTrailColor(value) {
1472
1493
  if (Array.isArray(parsed)) {
1473
1494
  return parsed;
1474
1495
  }
1475
- } catch {}
1496
+ } catch {
1497
+ }
1476
1498
  return value;
1477
1499
  }
1478
1500
  function buildOptions(el2) {
1479
1501
  return {
1480
- ...(el2.dataset.trailColor && {
1481
- trailColor: parseTrailColor(el2.dataset.trailColor),
1482
- }),
1483
- ...(el2.dataset.skeletonColor && { skeletonColor: el2.dataset.skeletonColor }),
1484
- ...(el2.dataset.headColor && { headColor: el2.dataset.headColor }),
1485
- ...(el2.dataset.headRadius && { headRadius: parseFloat(el2.dataset.headRadius) }),
1486
- ...(el2.dataset.trailLength && { trailLength: parseInt(el2.dataset.trailLength, 10) }),
1487
- ...(el2.dataset.trailStyle && {
1488
- trailStyle: el2.dataset.trailStyle,
1489
- }),
1502
+ ...el2.dataset.trailColor && {
1503
+ trailColor: parseTrailColor(el2.dataset.trailColor)
1504
+ },
1505
+ ...el2.dataset.skeletonColor && { skeletonColor: el2.dataset.skeletonColor },
1506
+ ...el2.dataset.headColor && { headColor: el2.dataset.headColor },
1507
+ ...el2.dataset.headRadius && { headRadius: parseFloat(el2.dataset.headRadius) },
1508
+ ...el2.dataset.trailLength && { trailLength: parseInt(el2.dataset.trailLength, 10) },
1509
+ ...el2.dataset.trailStyle && {
1510
+ trailStyle: el2.dataset.trailStyle
1511
+ }
1490
1512
  };
1491
1513
  }
1492
1514
  function init() {
@@ -1501,10 +1523,7 @@ function init() {
1501
1523
  return console.error(`[sarmal] "${curveName}" is not a valid curve name`);
1502
1524
  }
1503
1525
  const options = buildOptions(el2);
1504
- const instance =
1505
- el2 instanceof HTMLCanvasElement
1506
- ? createSarmal(el2, curveDef, options)
1507
- : createSarmalSVG(el2, curveDef, options);
1526
+ const instance = el2 instanceof HTMLCanvasElement ? createSarmal(el2, curveDef, options) : createSarmalSVG(el2, curveDef, options);
1508
1527
  if (el2.dataset.speed) {
1509
1528
  instance.setSpeed(parseFloat(el2.dataset.speed));
1510
1529
  }
@@ -1520,4 +1539,4 @@ if (document.readyState === "loading") {
1520
1539
 
1521
1540
  export { init };
1522
1541
  //# sourceMappingURL=auto-init.js.map
1523
- //# sourceMappingURL=auto-init.js.map
1542
+ //# sourceMappingURL=auto-init.js.map