@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/index.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,7 +367,7 @@ 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
  var palettes = {
@@ -393,16 +376,16 @@ var palettes = {
393
376
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
394
377
  ice: ["#1e3a8a", "#67e8f9"],
395
378
  fire: ["#7f1d1d", "#fbbf24"],
396
- forest: ["#14532d", "#86efac"],
379
+ forest: ["#14532d", "#86efac"]
397
380
  };
398
381
  function hexToRgb(hex) {
399
382
  const n = parseInt(hex.slice(1), 16);
400
- return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
383
+ return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
401
384
  }
402
385
  var lerpRgb = (a, b, t) => ({
403
386
  r: Math.round(a.r + (b.r - a.r) * t),
404
387
  g: Math.round(a.g + (b.g - a.g) * t),
405
- b: Math.round(a.b + (b.b - a.b) * t),
388
+ b: Math.round(a.b + (b.b - a.b) * t)
406
389
  });
407
390
  function getPaletteColor(palette, position, timeOffset = 0) {
408
391
  if (palette.length === 0) {
@@ -411,7 +394,7 @@ function getPaletteColor(palette, position, timeOffset = 0) {
411
394
  if (palette.length === 1) {
412
395
  return hexToRgb(palette[0]);
413
396
  }
414
- const cyclePos = (((position + timeOffset) % 1) + 1) % 1;
397
+ const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
415
398
  const scaled = cyclePos * palette.length;
416
399
  const idx = Math.floor(scaled);
417
400
  const t = scaled - idx;
@@ -426,7 +409,7 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
426
409
  "headColor",
427
410
  "skeletonColor",
428
411
  "trailStyle",
429
- "headRadius",
412
+ "headRadius"
430
413
  ]);
431
414
  function validateRenderOptions(partial) {
432
415
  for (const key of Object.keys(partial)) {
@@ -454,7 +437,7 @@ function assertTrailColor(value) {
454
437
  if (typeof value === "string") {
455
438
  if (!HEX_COLOR_RE.test(value)) {
456
439
  throw new TypeError(
457
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`,
440
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
458
441
  );
459
442
  }
460
443
  return;
@@ -462,21 +445,21 @@ function assertTrailColor(value) {
462
445
  if (Array.isArray(value)) {
463
446
  if (value.length < 2) {
464
447
  throw new RangeError(
465
- `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`,
448
+ `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`
466
449
  );
467
450
  }
468
451
  for (let i = 0; i < value.length; i++) {
469
452
  const entry = value[i];
470
453
  if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
471
454
  throw new TypeError(
472
- `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`,
455
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
473
456
  );
474
457
  }
475
458
  }
476
459
  return;
477
460
  }
478
461
  throw new TypeError(
479
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`,
462
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
480
463
  );
481
464
  }
482
465
  function assertHeadColor(value) {
@@ -485,7 +468,7 @@ function assertHeadColor(value) {
485
468
  }
486
469
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
487
470
  throw new TypeError(
488
- `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`,
471
+ `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
489
472
  );
490
473
  }
491
474
  }
@@ -495,26 +478,26 @@ function assertSkeletonColor(value) {
495
478
  }
496
479
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
497
480
  throw new TypeError(
498
- `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`,
481
+ `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
499
482
  );
500
483
  }
501
484
  }
502
485
  function assertTrailStyle(value) {
503
486
  if (!TRAIL_STYLES.includes(value)) {
504
487
  throw new RangeError(
505
- `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`,
488
+ `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`
506
489
  );
507
490
  }
508
491
  }
509
492
  function assertHeadRadius(value) {
510
493
  if (typeof value !== "number") {
511
494
  throw new TypeError(
512
- `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`,
495
+ `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`
513
496
  );
514
497
  }
515
498
  if (!Number.isFinite(value) || value <= 0) {
516
499
  throw new TypeError(
517
- `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`,
500
+ `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`
518
501
  );
519
502
  }
520
503
  }
@@ -536,23 +519,23 @@ function resolveHeadColor(trailColor, trailStyle) {
536
519
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
537
520
  if (trailStyle === "default" && Array.isArray(trailColor)) {
538
521
  console.warn(
539
- '[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.',
522
+ '[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.'
540
523
  );
541
524
  return;
542
525
  }
543
526
  if (trailStyle !== "default" && typeof trailColor === "string") {
544
527
  console.warn(
545
- `[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.`,
528
+ `[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.`
546
529
  );
547
530
  }
548
531
  }
549
- var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
550
532
 
551
533
  // src/renderer.ts
534
+ var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160));
552
535
  var WHITE_HEX = "#ffffff";
553
536
  function hexToRgbComponents(hex) {
554
537
  const n = parseInt(hex.slice(1), 16);
555
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
538
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
556
539
  }
557
540
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
558
541
  target.style.width = `${logicalWidth}px`;
@@ -596,6 +579,7 @@ function createRenderer(options) {
596
579
  let offsetY = 0;
597
580
  let animationId = null;
598
581
  let lastTime = 0;
582
+ let pausedByVisibility = false;
599
583
  let morphResolve = null;
600
584
  let morphReject = null;
601
585
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -678,7 +662,7 @@ function createRenderer(options) {
678
662
  i,
679
663
  trailCount,
680
664
  toX,
681
- toY,
665
+ toY
682
666
  );
683
667
  if (trailStyle === "default") {
684
668
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
@@ -789,6 +773,7 @@ function createRenderer(options) {
789
773
  cancelAnimationFrame(animationId);
790
774
  animationId = null;
791
775
  }
776
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
792
777
  if (morphReject !== null) {
793
778
  morphReject(new Error("Instance destroyed during morph"));
794
779
  morphResolve = null;
@@ -842,16 +827,38 @@ function createRenderer(options) {
842
827
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
843
828
  warnIfTrailColorMismatch(trailColor, trailStyle);
844
829
  }
845
- },
830
+ }
846
831
  };
847
- if (shouldAutoStart) {
832
+ const pauseOnHidden = options.pauseOnHidden !== false;
833
+ function handleVisibilityChange() {
834
+ if (document.hidden) {
835
+ if (animationId !== null) {
836
+ instance.pause();
837
+ pausedByVisibility = true;
838
+ }
839
+ } else {
840
+ if (pausedByVisibility) {
841
+ pausedByVisibility = false;
842
+ instance.play();
843
+ }
844
+ }
845
+ }
846
+ if (pauseOnHidden) {
847
+ document.addEventListener("visibilitychange", handleVisibilityChange);
848
+ }
849
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHidden && document.hidden);
850
+ if (actuallyAutoStart) {
848
851
  instance.play();
852
+ } else if (shouldAutoStart) {
853
+ pausedByVisibility = true;
849
854
  }
850
855
  return instance;
851
856
  }
852
857
 
853
858
  // src/renderer-svg.ts
854
859
  var EMPTY_PARAMS2 = {};
860
+ var SVG_DEFAULT_HEAD_RADIUS = 0.5;
861
+ var SKELETON_STROKE_WIDTH_PX = 1.5;
855
862
  var HIGH_TRAIL_LENGTH_THRESHOLD = 5e3;
856
863
  function pointsToPathString(pts, scale, offsetX, offsetY) {
857
864
  if (pts.length < 2) {
@@ -870,7 +877,7 @@ function sampleCurveSkeleton(curveDef) {
870
877
  const samples = Math.ceil(period * 50);
871
878
  const pts = Array.from({ length: samples });
872
879
  for (let i = 0; i < samples; i++) {
873
- const t = (i / (samples - 1)) * period;
880
+ const t = i / (samples - 1) * period;
874
881
  pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
875
882
  }
876
883
  return pts;
@@ -883,7 +890,7 @@ function createSVGRenderer(options) {
883
890
  const poolSize = engine.trailLength;
884
891
  if (poolSize > HIGH_TRAIL_LENGTH_THRESHOLD) {
885
892
  console.warn(
886
- `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`,
893
+ `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`
887
894
  );
888
895
  }
889
896
  let trailStyle = options.trailStyle ?? "default";
@@ -891,15 +898,21 @@ function createSVGRenderer(options) {
891
898
  let skeletonColor = options.skeletonColor ?? "#ffffff";
892
899
  let userHeadColor = options.headColor ?? null;
893
900
  let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
894
- let headRadius = options.headRadius ?? 1.5;
901
+ let headRadius;
895
902
  let trailSolid = resolveTrailMainColor(trailColor);
896
903
  let trailPalette = resolveTrailPalette(trailColor);
897
904
  const ariaLabel = options.ariaLabel ?? "Loading";
898
905
  warnIfTrailColorMismatch(trailColor, trailStyle);
899
906
  const viewSize = 100;
900
- const svgTrailMinWidth = 0.25;
901
- const svgTrailMaxWidth = 1.25;
902
- const svgSkeletonStrokeWidth = "0.75";
907
+ function getContainerPixelSize() {
908
+ const rect = container.getBoundingClientRect();
909
+ return rect.width && rect.height ? Math.min(rect.width, rect.height) : 200;
910
+ }
911
+ const containerPx = getContainerPixelSize();
912
+ const svgTrailMinWidth = TRAIL_MIN_WIDTH * viewSize / containerPx;
913
+ const svgTrailMaxWidth = TRAIL_MAX_WIDTH * viewSize / containerPx;
914
+ const svgSkeletonStrokeWidth = String(SKELETON_STROKE_WIDTH_PX * viewSize / containerPx);
915
+ headRadius = options.headRadius ?? SVG_DEFAULT_HEAD_RADIUS;
903
916
  container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
904
917
  container.setAttribute("role", "img");
905
918
  container.setAttribute("aria-label", ariaLabel);
@@ -986,7 +999,7 @@ function createSVGRenderer(options) {
986
999
  px,
987
1000
  py,
988
1001
  svgTrailMinWidth,
989
- svgTrailMaxWidth,
1002
+ svgTrailMaxWidth
990
1003
  );
991
1004
  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`;
992
1005
  trailPaths[i].setAttribute("d", d);
@@ -1013,8 +1026,8 @@ function createSVGRenderer(options) {
1013
1026
  }
1014
1027
  let animationId = null;
1015
1028
  let lastTime = 0;
1016
- const prefersReducedMotion =
1017
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1029
+ let pausedByVisibility = false;
1030
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1018
1031
  let morphResolve = null;
1019
1032
  let morphReject = null;
1020
1033
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -1032,7 +1045,7 @@ function createSVGRenderer(options) {
1032
1045
  skeletonPathA.setAttribute("visibility", "visible");
1033
1046
  skeletonPathA.setAttribute(
1034
1047
  "stroke-opacity",
1035
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
1048
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
1036
1049
  );
1037
1050
  }
1038
1051
  if (morphPathBBuilt) {
@@ -1104,6 +1117,7 @@ function createSVGRenderer(options) {
1104
1117
  cancelAnimationFrame(animationId);
1105
1118
  animationId = null;
1106
1119
  }
1120
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
1107
1121
  if (morphReject !== null) {
1108
1122
  morphReject(new Error("Instance destroyed during morph"));
1109
1123
  morphResolve = null;
@@ -1185,10 +1199,30 @@ function createSVGRenderer(options) {
1185
1199
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1186
1200
  warnIfTrailColorMismatch(trailColor, trailStyle);
1187
1201
  }
1188
- },
1202
+ }
1189
1203
  };
1190
- if (shouldAutoStart) {
1204
+ const pauseOnHidden = options.pauseOnHidden !== false;
1205
+ function handleVisibilityChange() {
1206
+ if (document.hidden) {
1207
+ if (animationId !== null) {
1208
+ instance.pause();
1209
+ pausedByVisibility = true;
1210
+ }
1211
+ } else {
1212
+ if (pausedByVisibility) {
1213
+ pausedByVisibility = false;
1214
+ instance.play();
1215
+ }
1216
+ }
1217
+ }
1218
+ if (pauseOnHidden) {
1219
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1220
+ }
1221
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHidden && document.hidden);
1222
+ if (actuallyAutoStart) {
1191
1223
  instance.play();
1224
+ } else if (shouldAutoStart) {
1225
+ pausedByVisibility = true;
1192
1226
  }
1193
1227
  return instance;
1194
1228
  }
@@ -1201,22 +1235,19 @@ function createSarmalSVG(container, curveDef, options) {
1201
1235
  // src/curves/artemis2.ts
1202
1236
  var TWO_PI2 = Math.PI * 2;
1203
1237
  function artemis2Fn(t, _time, _params) {
1204
- const a = 0.35,
1205
- b = 0.15,
1206
- ox = 0.175;
1207
- const s = Math.sin(t),
1208
- c = Math.cos(t);
1238
+ const a = 0.35, b = 0.15, ox = 0.175;
1239
+ const s = Math.sin(t), c = Math.cos(t);
1209
1240
  const denom = 1 + s * s;
1210
1241
  return {
1211
- x: (c * (1 + a * c)) / denom - ox,
1212
- y: (s * c * (1 + b * c)) / denom,
1242
+ x: c * (1 + a * c) / denom - ox,
1243
+ y: s * c * (1 + b * c) / denom
1213
1244
  };
1214
1245
  }
1215
1246
  var artemis2 = {
1216
1247
  name: "Artemis II",
1217
1248
  fn: artemis2Fn,
1218
1249
  period: TWO_PI2,
1219
- speed: 0.7,
1250
+ speed: 0.7
1220
1251
  };
1221
1252
 
1222
1253
  // src/curves/astroid.ts
@@ -1226,14 +1257,14 @@ function astroidFn(t, _time, _params) {
1226
1257
  const s = Math.sin(t);
1227
1258
  return {
1228
1259
  x: c * c * c,
1229
- y: s * s * s,
1260
+ y: s * s * s
1230
1261
  };
1231
1262
  }
1232
1263
  var astroid = {
1233
1264
  name: "Astroid",
1234
1265
  fn: astroidFn,
1235
1266
  period: TWO_PI3,
1236
- speed: 1.1,
1267
+ speed: 1.1
1237
1268
  };
1238
1269
 
1239
1270
  // src/curves/deltoid.ts
@@ -1241,14 +1272,14 @@ var TWO_PI4 = Math.PI * 2;
1241
1272
  function deltoidFn(t, _time, _params) {
1242
1273
  return {
1243
1274
  x: 2 * Math.cos(t) + Math.cos(2 * t),
1244
- y: 2 * Math.sin(t) - Math.sin(2 * t),
1275
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
1245
1276
  };
1246
1277
  }
1247
1278
  var deltoid = {
1248
1279
  name: "Deltoid",
1249
1280
  fn: deltoidFn,
1250
1281
  period: TWO_PI4,
1251
- speed: 0.9,
1282
+ speed: 0.9
1252
1283
  };
1253
1284
 
1254
1285
  // src/curves/epicycloid3.ts
@@ -1256,14 +1287,14 @@ var TWO_PI5 = Math.PI * 2;
1256
1287
  function epicycloid3Fn(t, _time, _params) {
1257
1288
  return {
1258
1289
  x: 4 * Math.cos(t) - Math.cos(4 * t),
1259
- y: 4 * Math.sin(t) - Math.sin(4 * t),
1290
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
1260
1291
  };
1261
1292
  }
1262
1293
  var epicycloid3 = {
1263
1294
  name: "Epicycloid (n=3)",
1264
1295
  fn: epicycloid3Fn,
1265
1296
  period: TWO_PI5,
1266
- speed: 0.75,
1297
+ speed: 0.75
1267
1298
  };
1268
1299
 
1269
1300
  // src/curves/epitrochoid7.ts
@@ -1272,14 +1303,14 @@ function epitrochoid7Fn(t, _time, _params) {
1272
1303
  const d = 1 + 0.55 * Math.sin(t * 0.5);
1273
1304
  return {
1274
1305
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1275
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1306
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1276
1307
  };
1277
1308
  }
1278
1309
  function epitrochoid7SkeletonFn(t) {
1279
1310
  const d = 1.275;
1280
1311
  return {
1281
1312
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1282
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1313
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1283
1314
  };
1284
1315
  }
1285
1316
  var epitrochoid7 = {
@@ -1287,7 +1318,7 @@ var epitrochoid7 = {
1287
1318
  fn: epitrochoid7Fn,
1288
1319
  period: TWO_PI6,
1289
1320
  speed: 1.4,
1290
- skeletonFn: epitrochoid7SkeletonFn,
1321
+ skeletonFn: epitrochoid7SkeletonFn
1291
1322
  };
1292
1323
 
1293
1324
  // src/curves/lissajous32.ts
@@ -1296,7 +1327,7 @@ function lissajous32Fn(t, time, _params) {
1296
1327
  const phi = time * 0.45;
1297
1328
  return {
1298
1329
  x: Math.sin(3 * t + phi),
1299
- y: Math.sin(2 * t),
1330
+ y: Math.sin(2 * t)
1300
1331
  };
1301
1332
  }
1302
1333
  var lissajous32 = {
@@ -1304,7 +1335,7 @@ var lissajous32 = {
1304
1335
  fn: lissajous32Fn,
1305
1336
  period: TWO_PI7,
1306
1337
  speed: 2,
1307
- skeleton: "live",
1338
+ skeleton: "live"
1308
1339
  };
1309
1340
 
1310
1341
  // src/curves/lissajous43.ts
@@ -1313,7 +1344,7 @@ function lissajous43Fn(t, time, _params) {
1313
1344
  const phi = time * 0.38;
1314
1345
  return {
1315
1346
  x: Math.sin(4 * t + phi),
1316
- y: Math.sin(3 * t),
1347
+ y: Math.sin(3 * t)
1317
1348
  };
1318
1349
  }
1319
1350
  var lissajous43 = {
@@ -1321,18 +1352,17 @@ var lissajous43 = {
1321
1352
  fn: lissajous43Fn,
1322
1353
  period: TWO_PI8,
1323
1354
  speed: 1.8,
1324
- skeleton: "live",
1355
+ skeleton: "live"
1325
1356
  };
1326
1357
 
1327
1358
  // src/curves/lame.ts
1328
1359
  var TWO_PI9 = Math.PI * 2;
1329
1360
  function lameFn(t, time, _params) {
1330
1361
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1331
- const c = Math.cos(t),
1332
- s = Math.sin(t);
1362
+ const c = Math.cos(t), s = Math.sin(t);
1333
1363
  return {
1334
1364
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1335
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
1365
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
1336
1366
  };
1337
1367
  }
1338
1368
  var lame = {
@@ -1340,7 +1370,7 @@ var lame = {
1340
1370
  fn: lameFn,
1341
1371
  period: TWO_PI9,
1342
1372
  speed: 1,
1343
- skeleton: "live",
1373
+ skeleton: "live"
1344
1374
  };
1345
1375
 
1346
1376
  // src/curves/rose3.ts
@@ -1349,14 +1379,14 @@ function rose3Fn(t, _time, _params) {
1349
1379
  const r = Math.cos(3 * t);
1350
1380
  return {
1351
1381
  x: r * Math.cos(t),
1352
- y: r * Math.sin(t),
1382
+ y: r * Math.sin(t)
1353
1383
  };
1354
1384
  }
1355
1385
  var rose3 = {
1356
1386
  name: "Rose (n=3)",
1357
1387
  fn: rose3Fn,
1358
1388
  period: TWO_PI10,
1359
- speed: 1.15,
1389
+ speed: 1.15
1360
1390
  };
1361
1391
 
1362
1392
  // src/curves/rose5.ts
@@ -1365,87 +1395,78 @@ function rose5Fn(t, _time, _params) {
1365
1395
  const r = Math.cos(5 * t);
1366
1396
  return {
1367
1397
  x: r * Math.cos(t),
1368
- y: r * Math.sin(t),
1398
+ y: r * Math.sin(t)
1369
1399
  };
1370
1400
  }
1371
1401
  var rose5 = {
1372
1402
  name: "Rose (n=5)",
1373
1403
  fn: rose5Fn,
1374
1404
  period: TWO_PI11,
1375
- speed: 1,
1405
+ speed: 1
1376
1406
  };
1377
1407
 
1378
1408
  // src/curves/rose52.ts
1379
1409
  var FOUR_PI = Math.PI * 4;
1380
1410
  function rose52Fn(t, _time, _params) {
1381
- const r = Math.cos((5 / 2) * t);
1411
+ const r = Math.cos(5 / 2 * t);
1382
1412
  return {
1383
1413
  x: r * Math.cos(t),
1384
- y: r * Math.sin(t),
1414
+ y: r * Math.sin(t)
1385
1415
  };
1386
1416
  }
1387
1417
  var rose52 = {
1388
1418
  name: "Rose (n=5/2)",
1389
1419
  fn: rose52Fn,
1390
1420
  period: FOUR_PI,
1391
- speed: 0.8,
1421
+ speed: 0.8
1392
1422
  };
1393
1423
 
1394
1424
  // src/curves/star.ts
1395
1425
  var TWO_PI12 = Math.PI * 2;
1396
1426
  function starFn(t, _time, _params) {
1397
- const r =
1398
- Math.abs(Math.cos((5 / 2) * t)) +
1399
- 0.35 * Math.abs(Math.cos((15 / 2) * t)) +
1400
- 0.15 * Math.abs(Math.cos((25 / 2) * t));
1427
+ 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));
1401
1428
  return {
1402
1429
  x: r * Math.cos(t),
1403
- y: r * Math.sin(t),
1430
+ y: r * Math.sin(t)
1404
1431
  };
1405
1432
  }
1406
1433
  var star = {
1407
1434
  name: "Star",
1408
1435
  fn: starFn,
1409
1436
  period: TWO_PI12,
1410
- speed: 1,
1437
+ speed: 1
1411
1438
  };
1412
1439
 
1413
1440
  // src/curves/star4.ts
1414
1441
  var TWO_PI13 = Math.PI * 2;
1415
1442
  function star4Fn(t, _time, _params) {
1416
- const r =
1417
- Math.abs(Math.cos(2 * t)) +
1418
- 0.35 * Math.abs(Math.cos(6 * t)) +
1419
- 0.15 * Math.abs(Math.cos(10 * t));
1443
+ const r = Math.abs(Math.cos(2 * t)) + 0.35 * Math.abs(Math.cos(6 * t)) + 0.15 * Math.abs(Math.cos(10 * t));
1420
1444
  return {
1421
1445
  x: r * Math.cos(t),
1422
- y: r * Math.sin(t),
1446
+ y: r * Math.sin(t)
1423
1447
  };
1424
1448
  }
1425
1449
  var star4 = {
1426
1450
  name: "Star (4-arm)",
1427
1451
  fn: star4Fn,
1428
1452
  period: TWO_PI13,
1429
- speed: 1,
1453
+ speed: 1
1430
1454
  };
1431
1455
 
1432
1456
  // src/curves/star7.ts
1433
1457
  var TWO_PI14 = Math.PI * 2;
1434
1458
  function star7Fn(t, _time, _params) {
1435
- const r =
1436
- Math.abs(Math.cos((7 / 2) * t)) +
1437
- 0.35 * Math.abs(Math.cos((21 / 2) * t)) +
1438
- 0.15 * Math.abs(Math.cos((35 / 2) * t));
1459
+ 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));
1439
1460
  return {
1440
1461
  x: r * Math.cos(t),
1441
- y: r * Math.sin(t),
1462
+ y: r * Math.sin(t)
1442
1463
  };
1443
1464
  }
1444
1465
  var star7 = {
1445
1466
  name: "Star (7-arm)",
1446
1467
  fn: star7Fn,
1447
1468
  period: TWO_PI14,
1448
- speed: 1,
1469
+ speed: 1
1449
1470
  };
1450
1471
 
1451
1472
  // src/curves/index.ts
@@ -1463,7 +1484,7 @@ var curves = {
1463
1484
  lissajous32,
1464
1485
  lissajous43,
1465
1486
  epicycloid3,
1466
- lame,
1487
+ lame
1467
1488
  };
1468
1489
 
1469
1490
  // src/catmull-rom.ts
@@ -1471,13 +1492,7 @@ var PERIOD = 2 * Math.PI;
1471
1492
  function catmullRom1D(p0, p1, p2, p3, u) {
1472
1493
  const u2 = u * u;
1473
1494
  const u3 = u2 * u;
1474
- return (
1475
- 0.5 *
1476
- (2 * p1 +
1477
- (-p0 + p2) * u +
1478
- (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 +
1479
- (-p0 + 3 * p1 - 3 * p2 + p3) * u3)
1480
- );
1495
+ return 0.5 * (2 * p1 + (-p0 + p2) * u + (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 + (-p0 + 3 * p1 - 3 * p2 + p3) * u3);
1481
1496
  }
1482
1497
  function evaluateCatmullRom(points, t) {
1483
1498
  const N = points.length;
@@ -1487,7 +1502,7 @@ function evaluateCatmullRom(points, t) {
1487
1502
  if (N === 1) {
1488
1503
  return { x: points[0][0], y: points[0][1] };
1489
1504
  }
1490
- t = ((t % PERIOD) + PERIOD) % PERIOD;
1505
+ t = (t % PERIOD + PERIOD) % PERIOD;
1491
1506
  const segmentSize = PERIOD / N;
1492
1507
  let i = Math.floor(t / segmentSize);
1493
1508
  if (i >= N) {
@@ -1501,7 +1516,7 @@ function evaluateCatmullRom(points, t) {
1501
1516
  const p3 = points[(i + 2) % N];
1502
1517
  return {
1503
1518
  x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
1504
- y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u),
1519
+ y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u)
1505
1520
  };
1506
1521
  }
1507
1522
  function drawCurve(points) {
@@ -1511,7 +1526,7 @@ function drawCurve(points) {
1511
1526
  return {
1512
1527
  name: "custom",
1513
1528
  fn: (t) => evaluateCatmullRom(points, t),
1514
- period: PERIOD,
1529
+ period: PERIOD
1515
1530
  };
1516
1531
  }
1517
1532
 
@@ -1522,26 +1537,6 @@ function createSarmal(canvas, curveDef, options) {
1522
1537
  return createRenderer({ canvas, engine, ...rendererOpts });
1523
1538
  }
1524
1539
 
1525
- export {
1526
- artemis2,
1527
- astroid,
1528
- createEngine,
1529
- createRenderer,
1530
- createSVGRenderer,
1531
- createSarmal,
1532
- createSarmalSVG,
1533
- curves,
1534
- deltoid,
1535
- drawCurve,
1536
- epicycloid3,
1537
- epitrochoid7,
1538
- evaluateCatmullRom,
1539
- lame,
1540
- lissajous32,
1541
- lissajous43,
1542
- palettes,
1543
- rose3,
1544
- rose5,
1545
- };
1546
- //# sourceMappingURL=index.js.map
1540
+ export { artemis2, astroid, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves, deltoid, drawCurve, epicycloid3, epitrochoid7, evaluateCatmullRom, lame, lissajous32, lissajous43, palettes, rose3, rose5 };
1547
1541
  //# sourceMappingURL=index.js.map
1542
+ //# sourceMappingURL=index.js.map