@sarmal/core 0.23.0 → 0.25.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 (79) hide show
  1. package/dist/auto-init.cjs +255 -175
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.js +254 -174
  4. package/dist/auto-init.js.map +1 -1
  5. package/dist/curves/artemis2.cjs +82 -17
  6. package/dist/curves/artemis2.cjs.map +1 -1
  7. package/dist/curves/artemis2.d.cts +1 -5
  8. package/dist/curves/artemis2.d.ts +1 -5
  9. package/dist/curves/artemis2.js +81 -16
  10. package/dist/curves/artemis2.js.map +1 -1
  11. package/dist/curves/astroid.cjs +4 -4
  12. package/dist/curves/astroid.d.cts +1 -1
  13. package/dist/curves/astroid.d.ts +1 -1
  14. package/dist/curves/astroid.js +3 -3
  15. package/dist/curves/deltoid.cjs +4 -4
  16. package/dist/curves/deltoid.d.cts +1 -1
  17. package/dist/curves/deltoid.d.ts +1 -1
  18. package/dist/curves/deltoid.js +3 -3
  19. package/dist/curves/epicycloid3.cjs +4 -4
  20. package/dist/curves/epicycloid3.d.cts +1 -1
  21. package/dist/curves/epicycloid3.d.ts +1 -1
  22. package/dist/curves/epicycloid3.js +3 -3
  23. package/dist/curves/epitrochoid7.cjs +5 -5
  24. package/dist/curves/epitrochoid7.d.cts +1 -1
  25. package/dist/curves/epitrochoid7.d.ts +1 -1
  26. package/dist/curves/epitrochoid7.js +4 -4
  27. package/dist/curves/index.cjs +139 -84
  28. package/dist/curves/index.cjs.map +1 -1
  29. package/dist/curves/index.d.cts +29 -29
  30. package/dist/curves/index.d.ts +29 -29
  31. package/dist/curves/index.js +139 -100
  32. package/dist/curves/index.js.map +1 -1
  33. package/dist/curves/lame.cjs +5 -6
  34. package/dist/curves/lame.d.cts +1 -1
  35. package/dist/curves/lame.d.ts +1 -1
  36. package/dist/curves/lame.js +4 -5
  37. package/dist/curves/lissajous32.cjs +4 -4
  38. package/dist/curves/lissajous32.d.cts +1 -1
  39. package/dist/curves/lissajous32.d.ts +1 -1
  40. package/dist/curves/lissajous32.js +3 -3
  41. package/dist/curves/lissajous43.cjs +4 -4
  42. package/dist/curves/lissajous43.d.cts +1 -1
  43. package/dist/curves/lissajous43.d.ts +1 -1
  44. package/dist/curves/lissajous43.js +3 -3
  45. package/dist/curves/rose3.cjs +4 -4
  46. package/dist/curves/rose3.d.cts +1 -1
  47. package/dist/curves/rose3.d.ts +1 -1
  48. package/dist/curves/rose3.js +3 -3
  49. package/dist/curves/rose5.cjs +4 -4
  50. package/dist/curves/rose5.d.cts +1 -1
  51. package/dist/curves/rose5.d.ts +1 -1
  52. package/dist/curves/rose5.js +3 -3
  53. package/dist/curves/rose52.cjs +5 -5
  54. package/dist/curves/rose52.d.cts +1 -1
  55. package/dist/curves/rose52.d.ts +1 -1
  56. package/dist/curves/rose52.js +4 -4
  57. package/dist/curves/star.cjs +5 -8
  58. package/dist/curves/star.d.cts +1 -1
  59. package/dist/curves/star.d.ts +1 -1
  60. package/dist/curves/star.js +4 -7
  61. package/dist/curves/star4.cjs +5 -8
  62. package/dist/curves/star4.d.cts +1 -1
  63. package/dist/curves/star4.d.ts +1 -1
  64. package/dist/curves/star4.js +4 -7
  65. package/dist/curves/star7.cjs +5 -8
  66. package/dist/curves/star7.d.cts +1 -1
  67. package/dist/curves/star7.d.ts +1 -1
  68. package/dist/curves/star7.js +4 -7
  69. package/dist/index.cjs +244 -210
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +55 -78
  72. package/dist/index.d.ts +55 -78
  73. package/dist/index.js +243 -230
  74. package/dist/index.js.map +1 -1
  75. package/dist/types-BZpzgNau.d.cts +332 -0
  76. package/dist/types-BZpzgNau.d.ts +332 -0
  77. package/package.json +1 -1
  78. package/dist/types-C0b4MPtI.d.cts +0 -321
  79. package/dist/types-C0b4MPtI.d.ts +0 -321
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
3
  // src/engine.ts
4
4
  var TWO_PI = Math.PI * 2;
@@ -63,13 +63,13 @@ function resolveCurve(curveDef) {
63
63
  period,
64
64
  speed,
65
65
  skeleton: curveDef.skeleton,
66
- skeletonFn: curveDef.skeletonFn,
66
+ skeletonFn: curveDef.skeletonFn
67
67
  };
68
68
  }
69
69
  function createEngine(curveDef, trailLength = 120) {
70
70
  if (!Number.isFinite(trailLength) || trailLength <= 0) {
71
71
  throw new RangeError(
72
- `[sarmal] trailLength must be a positive finite number, got ${trailLength}`,
72
+ `[sarmal] trailLength must be a positive finite number, got ${trailLength}`
73
73
  );
74
74
  }
75
75
  let curve = resolveCurve(curveDef);
@@ -110,7 +110,7 @@ function createEngine(curveDef, trailLength = 120) {
110
110
  actualTime += deltaTime;
111
111
  if (morphCurveB !== null && _morphAlpha !== null) {
112
112
  const a = curve.fn(t, actualTime, EMPTY_PARAMS);
113
- const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
113
+ const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
114
114
  const b = morphCurveB.fn(tB, actualTime, EMPTY_PARAMS);
115
115
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
116
116
  } else {
@@ -137,14 +137,14 @@ function createEngine(curveDef, trailLength = 120) {
137
137
  trail.clear();
138
138
  },
139
139
  jump(newT, { clearTrail = false } = {}) {
140
- t = ((newT % curve.period) + curve.period) % curve.period;
140
+ t = (newT % curve.period + curve.period) % curve.period;
141
141
  if (clearTrail) {
142
142
  trail.clear();
143
143
  }
144
144
  },
145
145
  seek(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
146
146
  const advance = curve.speed * step;
147
- const target = ((targetT % curve.period) + curve.period) % curve.period;
147
+ const target = (targetT % curve.period + curve.period) % curve.period;
148
148
  const targetTime = target / curve.speed;
149
149
  t = target;
150
150
  actualTime = targetTime;
@@ -153,7 +153,7 @@ function createEngine(curveDef, trailLength = 120) {
153
153
  const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
154
154
  for (let i = count - 1; i >= 0; i--) {
155
155
  const sampleT = target - i * advance;
156
- const wrappedT = ((sampleT % curve.period) + curve.period) % curve.period;
156
+ const wrappedT = (sampleT % curve.period + curve.period) % curve.period;
157
157
  const time = targetTime - i * step;
158
158
  const point = curve.fn(wrappedT, time, EMPTY_PARAMS);
159
159
  trail.push(point.x, point.y);
@@ -170,16 +170,13 @@ function createEngine(curveDef, trailLength = 120) {
170
170
  ...frozenB,
171
171
  fn: (sampleT, time, params) => {
172
172
  const a = frozenA.fn(sampleT, time, params);
173
- const tB =
174
- frozenStrategy === "normalized"
175
- ? (sampleT / frozenA.period) * frozenB.period
176
- : sampleT;
173
+ const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
177
174
  const b = frozenB.fn(tB, time, params);
178
175
  return {
179
176
  x: a.x + (b.x - a.x) * frozenAlpha,
180
- y: a.y + (b.y - a.y) * frozenAlpha,
177
+ y: a.y + (b.y - a.y) * frozenAlpha
181
178
  };
182
- },
179
+ }
183
180
  };
184
181
  }
185
182
  _morphStrategy = strategy;
@@ -192,7 +189,7 @@ function createEngine(curveDef, trailLength = 120) {
192
189
  completeMorph() {
193
190
  if (morphCurveB !== null) {
194
191
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
195
- t = (t / curve.period) * morphCurveB.period;
192
+ t = t / curve.period * morphCurveB.period;
196
193
  }
197
194
  curve = morphCurveB;
198
195
  }
@@ -201,28 +198,25 @@ function createEngine(curveDef, trailLength = 120) {
201
198
  },
202
199
  getSarmalSkeleton() {
203
200
  const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
204
- const points = new Array(steps);
201
+ const points2 = new Array(steps);
205
202
  if (morphCurveB !== null && _morphAlpha !== null) {
206
203
  for (let i = 0; i < steps; i++) {
207
- const sampleT = (i / (steps - 1)) * curve.period;
204
+ const sampleT = i / (steps - 1) * curve.period;
208
205
  const a = sampleSkeleton(curve, sampleT);
209
- const tB =
210
- _morphStrategy === "normalized"
211
- ? (sampleT / curve.period) * morphCurveB.period
212
- : sampleT;
206
+ const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
213
207
  const b = sampleSkeleton(morphCurveB, tB);
214
- points[i] = {
208
+ points2[i] = {
215
209
  x: a.x + (b.x - a.x) * _morphAlpha,
216
- y: a.y + (b.y - a.y) * _morphAlpha,
210
+ y: a.y + (b.y - a.y) * _morphAlpha
217
211
  };
218
212
  }
219
- return points;
213
+ return points2;
220
214
  }
221
215
  for (let i = 0; i < steps; i++) {
222
- const sampleT = (i / (steps - 1)) * curve.period;
223
- points[i] = sampleSkeleton(curve, sampleT);
216
+ const sampleT = i / (steps - 1) * curve.period;
217
+ points2[i] = sampleSkeleton(curve, sampleT);
224
218
  }
225
- return points;
219
+ return points2;
226
220
  },
227
221
  setSpeed(speed) {
228
222
  if (!Number.isFinite(speed)) {
@@ -261,7 +255,7 @@ function createEngine(curveDef, trailLength = 120) {
261
255
  _speedTransition.reject(new Error("Speed transition cancelled"));
262
256
  _speedTransition = null;
263
257
  }
264
- },
258
+ }
265
259
  };
266
260
  }
267
261
 
@@ -300,15 +294,7 @@ function computeNormal(trail, i) {
300
294
  const tangent = computeTangent(trail, i);
301
295
  return { x: -tangent.y, y: tangent.x };
302
296
  }
303
- function computeTrailQuad(
304
- trail,
305
- i,
306
- trailCount,
307
- toX,
308
- toY,
309
- minWidth = TRAIL_MIN_WIDTH,
310
- maxWidth = TRAIL_MAX_WIDTH,
311
- ) {
297
+ function computeTrailQuad(trail, i, trailCount, toX, toY, minWidth = TRAIL_MIN_WIDTH, maxWidth = TRAIL_MAX_WIDTH) {
312
298
  const progress = i / (trailCount - 1);
313
299
  const nextProgress = (i + 1) / (trailCount - 1);
314
300
  const opacity = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
@@ -332,16 +318,13 @@ function computeTrailQuad(
332
318
  r1x: nx - n1.x * w1,
333
319
  r1y: ny - n1.y * w1,
334
320
  opacity,
335
- progress,
321
+ progress
336
322
  };
337
323
  }
338
- function computeBoundaries(pts, logicalWidth, logicalHeight) {
324
+ function computeBoundaries(pts, logicalWidth, logicalHeight, minPaddingPx = FIT_PADDING_MIN) {
339
325
  if (pts.length === 0) return null;
340
326
  const first = pts[0];
341
- let minX = first.x,
342
- maxX = first.x,
343
- minY = first.y,
344
- maxY = first.y;
327
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
345
328
  for (const p of pts) {
346
329
  if (p.x < minX) {
347
330
  minX = p.x;
@@ -360,23 +343,23 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
360
343
  const h = maxY - minY;
361
344
  if (w === 0 && h === 0) {
362
345
  throw new Error(
363
- "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
346
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
364
347
  );
365
348
  }
366
349
  const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
367
350
  const scaleYProportional = logicalHeight / (h * (1 + FIT_PADDING * 2));
368
- const scaleXMinPadding = (logicalWidth - FIT_PADDING_MIN * 2) / w;
369
- const scaleYMinPadding = (logicalHeight - FIT_PADDING_MIN * 2) / h;
351
+ const scaleXMinPadding = (logicalWidth - minPaddingPx * 2) / w;
352
+ const scaleYMinPadding = (logicalHeight - minPaddingPx * 2) / h;
370
353
  const scale = Math.min(
371
354
  scaleXProportional,
372
355
  scaleYProportional,
373
356
  scaleXMinPadding,
374
- scaleYMinPadding,
357
+ scaleYMinPadding
375
358
  );
376
359
  return {
377
360
  scale,
378
361
  offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
379
- offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
362
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale
380
363
  };
381
364
  }
382
365
  function enginePassthroughs(engine) {
@@ -387,6 +370,7 @@ function enginePassthroughs(engine) {
387
370
  getSpeed: engine.getSpeed,
388
371
  resetSpeed: engine.resetSpeed,
389
372
  setSpeedOver: engine.setSpeedOver,
373
+ getSarmalSkeleton: engine.getSarmalSkeleton
390
374
  };
391
375
  }
392
376
  var palettes = {
@@ -395,16 +379,16 @@ var palettes = {
395
379
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
396
380
  ice: ["#1e3a8a", "#67e8f9"],
397
381
  fire: ["#7f1d1d", "#fbbf24"],
398
- forest: ["#14532d", "#86efac"],
382
+ forest: ["#14532d", "#86efac"]
399
383
  };
400
384
  function hexToRgb(hex) {
401
385
  const n = parseInt(hex.slice(1), 16);
402
- return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
386
+ return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
403
387
  }
404
388
  var lerpRgb = (a, b, t) => ({
405
389
  r: Math.round(a.r + (b.r - a.r) * t),
406
390
  g: Math.round(a.g + (b.g - a.g) * t),
407
- b: Math.round(a.b + (b.b - a.b) * t),
391
+ b: Math.round(a.b + (b.b - a.b) * t)
408
392
  });
409
393
  function getPaletteColor(palette, position, timeOffset = 0) {
410
394
  if (palette.length === 0) {
@@ -413,7 +397,7 @@ function getPaletteColor(palette, position, timeOffset = 0) {
413
397
  if (palette.length === 1) {
414
398
  return hexToRgb(palette[0]);
415
399
  }
416
- const cyclePos = (((position + timeOffset) % 1) + 1) % 1;
400
+ const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
417
401
  const scaled = cyclePos * palette.length;
418
402
  const idx = Math.floor(scaled);
419
403
  const t = scaled - idx;
@@ -428,7 +412,7 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
428
412
  "headColor",
429
413
  "skeletonColor",
430
414
  "trailStyle",
431
- "headRadius",
415
+ "headRadius"
432
416
  ]);
433
417
  function validateRenderOptions(partial) {
434
418
  for (const key of Object.keys(partial)) {
@@ -456,7 +440,7 @@ function assertTrailColor(value) {
456
440
  if (typeof value === "string") {
457
441
  if (!HEX_COLOR_RE.test(value)) {
458
442
  throw new TypeError(
459
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`,
443
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
460
444
  );
461
445
  }
462
446
  return;
@@ -464,21 +448,21 @@ function assertTrailColor(value) {
464
448
  if (Array.isArray(value)) {
465
449
  if (value.length < 2) {
466
450
  throw new RangeError(
467
- `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`,
451
+ `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`
468
452
  );
469
453
  }
470
454
  for (let i = 0; i < value.length; i++) {
471
455
  const entry = value[i];
472
456
  if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
473
457
  throw new TypeError(
474
- `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`,
458
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
475
459
  );
476
460
  }
477
461
  }
478
462
  return;
479
463
  }
480
464
  throw new TypeError(
481
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`,
465
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
482
466
  );
483
467
  }
484
468
  function assertHeadColor(value) {
@@ -487,7 +471,7 @@ function assertHeadColor(value) {
487
471
  }
488
472
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
489
473
  throw new TypeError(
490
- `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`,
474
+ `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
491
475
  );
492
476
  }
493
477
  }
@@ -497,26 +481,26 @@ function assertSkeletonColor(value) {
497
481
  }
498
482
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
499
483
  throw new TypeError(
500
- `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`,
484
+ `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
501
485
  );
502
486
  }
503
487
  }
504
488
  function assertTrailStyle(value) {
505
489
  if (!TRAIL_STYLES.includes(value)) {
506
490
  throw new RangeError(
507
- `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`,
491
+ `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`
508
492
  );
509
493
  }
510
494
  }
511
495
  function assertHeadRadius(value) {
512
496
  if (typeof value !== "number") {
513
497
  throw new TypeError(
514
- `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`,
498
+ `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`
515
499
  );
516
500
  }
517
501
  if (!Number.isFinite(value) || value <= 0) {
518
502
  throw new TypeError(
519
- `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`,
503
+ `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`
520
504
  );
521
505
  }
522
506
  }
@@ -538,13 +522,13 @@ function resolveHeadColor(trailColor, trailStyle) {
538
522
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
539
523
  if (trailStyle === "default" && Array.isArray(trailColor)) {
540
524
  console.warn(
541
- '[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.',
525
+ '[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.'
542
526
  );
543
527
  return;
544
528
  }
545
529
  if (trailStyle !== "default" && typeof trailColor === "string") {
546
530
  console.warn(
547
- `[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.`,
531
+ `[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.`
548
532
  );
549
533
  }
550
534
  }
@@ -554,7 +538,7 @@ var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160)
554
538
  var WHITE_HEX = "#ffffff";
555
539
  function hexToRgbComponents(hex) {
556
540
  const n = parseInt(hex.slice(1), 16);
557
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
541
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
558
542
  }
559
543
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
560
544
  target.style.width = `${logicalWidth}px`;
@@ -598,6 +582,7 @@ function createRenderer(options) {
598
582
  let offsetY = 0;
599
583
  let animationId = null;
600
584
  let lastTime = 0;
585
+ let pausedByVisibility = false;
601
586
  let morphResolve = null;
602
587
  let morphReject = null;
603
588
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -680,7 +665,7 @@ function createRenderer(options) {
680
665
  i,
681
666
  trailCount,
682
667
  toX,
683
- toY,
668
+ toY
684
669
  );
685
670
  if (trailStyle === "default") {
686
671
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
@@ -791,6 +776,7 @@ function createRenderer(options) {
791
776
  cancelAnimationFrame(animationId);
792
777
  animationId = null;
793
778
  }
779
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
794
780
  if (morphReject !== null) {
795
781
  morphReject(new Error("Instance destroyed during morph"));
796
782
  morphResolve = null;
@@ -844,10 +830,30 @@ function createRenderer(options) {
844
830
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
845
831
  warnIfTrailColorMismatch(trailColor, trailStyle);
846
832
  }
847
- },
833
+ }
848
834
  };
849
- if (shouldAutoStart) {
835
+ const pauseOnHidden = options.pauseOnHidden !== false;
836
+ function handleVisibilityChange() {
837
+ if (document.hidden) {
838
+ if (animationId !== null) {
839
+ instance.pause();
840
+ pausedByVisibility = true;
841
+ }
842
+ } else {
843
+ if (pausedByVisibility) {
844
+ pausedByVisibility = false;
845
+ instance.play();
846
+ }
847
+ }
848
+ }
849
+ if (pauseOnHidden) {
850
+ document.addEventListener("visibilitychange", handleVisibilityChange);
851
+ }
852
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHidden && document.hidden);
853
+ if (actuallyAutoStart) {
850
854
  instance.play();
855
+ } else if (shouldAutoStart) {
856
+ pausedByVisibility = true;
851
857
  }
852
858
  return instance;
853
859
  }
@@ -874,7 +880,7 @@ function sampleCurveSkeleton(curveDef) {
874
880
  const samples = Math.ceil(period * 50);
875
881
  const pts = Array.from({ length: samples });
876
882
  for (let i = 0; i < samples; i++) {
877
- const t = (i / (samples - 1)) * period;
883
+ const t = i / (samples - 1) * period;
878
884
  pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
879
885
  }
880
886
  return pts;
@@ -887,7 +893,7 @@ function createSVGRenderer(options) {
887
893
  const poolSize = engine.trailLength;
888
894
  if (poolSize > HIGH_TRAIL_LENGTH_THRESHOLD) {
889
895
  console.warn(
890
- `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`,
896
+ `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`
891
897
  );
892
898
  }
893
899
  let trailStyle = options.trailStyle ?? "default";
@@ -906,9 +912,9 @@ function createSVGRenderer(options) {
906
912
  return rect.width && rect.height ? Math.min(rect.width, rect.height) : 200;
907
913
  }
908
914
  const containerPx = getContainerPixelSize();
909
- const svgTrailMinWidth = (TRAIL_MIN_WIDTH * viewSize) / containerPx;
910
- const svgTrailMaxWidth = (TRAIL_MAX_WIDTH * viewSize) / containerPx;
911
- const svgSkeletonStrokeWidth = String((SKELETON_STROKE_WIDTH_PX * viewSize) / containerPx);
915
+ const svgTrailMinWidth = TRAIL_MIN_WIDTH * viewSize / containerPx;
916
+ const svgTrailMaxWidth = TRAIL_MAX_WIDTH * viewSize / containerPx;
917
+ const svgSkeletonStrokeWidth = String(SKELETON_STROKE_WIDTH_PX * viewSize / containerPx);
912
918
  headRadius = options.headRadius ?? SVG_DEFAULT_HEAD_RADIUS;
913
919
  container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
914
920
  container.setAttribute("role", "img");
@@ -996,7 +1002,7 @@ function createSVGRenderer(options) {
996
1002
  px,
997
1003
  py,
998
1004
  svgTrailMinWidth,
999
- svgTrailMaxWidth,
1005
+ svgTrailMaxWidth
1000
1006
  );
1001
1007
  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`;
1002
1008
  trailPaths[i].setAttribute("d", d);
@@ -1023,8 +1029,8 @@ function createSVGRenderer(options) {
1023
1029
  }
1024
1030
  let animationId = null;
1025
1031
  let lastTime = 0;
1026
- const prefersReducedMotion =
1027
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1032
+ let pausedByVisibility = false;
1033
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1028
1034
  let morphResolve = null;
1029
1035
  let morphReject = null;
1030
1036
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -1042,7 +1048,7 @@ function createSVGRenderer(options) {
1042
1048
  skeletonPathA.setAttribute("visibility", "visible");
1043
1049
  skeletonPathA.setAttribute(
1044
1050
  "stroke-opacity",
1045
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
1051
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
1046
1052
  );
1047
1053
  }
1048
1054
  if (morphPathBBuilt) {
@@ -1114,6 +1120,7 @@ function createSVGRenderer(options) {
1114
1120
  cancelAnimationFrame(animationId);
1115
1121
  animationId = null;
1116
1122
  }
1123
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
1117
1124
  if (morphReject !== null) {
1118
1125
  morphReject(new Error("Instance destroyed during morph"));
1119
1126
  morphResolve = null;
@@ -1195,10 +1202,30 @@ function createSVGRenderer(options) {
1195
1202
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1196
1203
  warnIfTrailColorMismatch(trailColor, trailStyle);
1197
1204
  }
1198
- },
1205
+ }
1199
1206
  };
1200
- if (shouldAutoStart) {
1207
+ const pauseOnHidden = options.pauseOnHidden !== false;
1208
+ function handleVisibilityChange() {
1209
+ if (document.hidden) {
1210
+ if (animationId !== null) {
1211
+ instance.pause();
1212
+ pausedByVisibility = true;
1213
+ }
1214
+ } else {
1215
+ if (pausedByVisibility) {
1216
+ pausedByVisibility = false;
1217
+ instance.play();
1218
+ }
1219
+ }
1220
+ }
1221
+ if (pauseOnHidden) {
1222
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1223
+ }
1224
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHidden && document.hidden);
1225
+ if (actuallyAutoStart) {
1201
1226
  instance.play();
1227
+ } else if (shouldAutoStart) {
1228
+ pausedByVisibility = true;
1202
1229
  }
1203
1230
  return instance;
1204
1231
  }
@@ -1208,254 +1235,309 @@ function createSarmalSVG(container, curveDef, options) {
1208
1235
  return createSVGRenderer({ container, engine, ...rendererOpts });
1209
1236
  }
1210
1237
 
1211
- // src/curves/artemis2.ts
1212
- var TWO_PI2 = Math.PI * 2;
1213
- function artemis2Fn(t, _time, _params) {
1214
- const a = 0.35,
1215
- b = 0.15,
1216
- ox = 0.175;
1217
- const s = Math.sin(t),
1218
- c = Math.cos(t);
1219
- const denom = 1 + s * s;
1238
+ // src/catmull-rom.ts
1239
+ var PERIOD = 2 * Math.PI;
1240
+ function catmullRom1D(p0, p1, p2, p3, u) {
1241
+ const u2 = u * u;
1242
+ const u3 = u2 * u;
1243
+ return 0.5 * (2 * p1 + (-p0 + p2) * u + (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 + (-p0 + 3 * p1 - 3 * p2 + p3) * u3);
1244
+ }
1245
+ function evaluateCatmullRom(points2, t) {
1246
+ const N = points2.length;
1247
+ if (N === 0) {
1248
+ return { x: 0, y: 0 };
1249
+ }
1250
+ if (N === 1) {
1251
+ return { x: points2[0][0], y: points2[0][1] };
1252
+ }
1253
+ t = (t % PERIOD + PERIOD) % PERIOD;
1254
+ const segmentSize = PERIOD / N;
1255
+ let i = Math.floor(t / segmentSize);
1256
+ if (i >= N) {
1257
+ i = N - 1;
1258
+ }
1259
+ let u = (t - i * segmentSize) / segmentSize;
1260
+ u = Math.max(0, Math.min(1, u));
1261
+ const p0 = points2[(i - 1 + N) % N];
1262
+ const p1 = points2[i];
1263
+ const p2 = points2[(i + 1) % N];
1264
+ const p3 = points2[(i + 2) % N];
1220
1265
  return {
1221
- x: (c * (1 + a * c)) / denom - ox,
1222
- y: (s * c * (1 + b * c)) / denom,
1266
+ x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
1267
+ y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u)
1268
+ };
1269
+ }
1270
+ function drawCurve(points2, opts) {
1271
+ if (points2.length < 3) {
1272
+ throw new Error(`drawCurve requires at least 3 points, received ${points2.length}.`);
1273
+ }
1274
+ const first = points2[0];
1275
+ if (points2.every((p) => p[0] === first[0] && p[1] === first[1])) {
1276
+ console.warn(
1277
+ "[sarmal].drawCurve: all control points are identical. The curve will be a single point."
1278
+ );
1279
+ }
1280
+ const maxAbs = points2.reduce((m, p) => Math.max(m, Math.abs(p[0]), Math.abs(p[1])), 0);
1281
+ if (maxAbs > 2) {
1282
+ console.warn(
1283
+ `[sarmal].drawCurve: control points extend to \xB1${maxAbs.toFixed(1)}, which may render off-screen. Coordinates should be in [-1, 1].`
1284
+ );
1285
+ }
1286
+ const pts = points2.map(([x, y]) => [x, y]);
1287
+ return {
1288
+ name: opts?.name ?? "drawn",
1289
+ fn: (t) => evaluateCatmullRom(pts, t),
1290
+ period: PERIOD,
1291
+ kind: "drawn"
1223
1292
  };
1224
1293
  }
1294
+
1295
+ // src/curves/artemis2.ts
1296
+ var points = [
1297
+ [-0.44, -0.45],
1298
+ [-0.53, -0.77],
1299
+ [-0.82, -0.66],
1300
+ [-0.82, -0.18],
1301
+ [-0.25, -0.04],
1302
+ [0.16, -0.49],
1303
+ [-0.03, -0.87],
1304
+ [-0.68, -0.94],
1305
+ [-0.95, -0.61],
1306
+ [-0.87, -0],
1307
+ [-0.34, 0.21],
1308
+ [0.27, -0.04],
1309
+ [0.87, 0.06],
1310
+ [0.87, 0.57],
1311
+ [0.32, 0.66],
1312
+ [-0.21, -0.43],
1313
+ [-0.43, -0.81],
1314
+ [-0.69, -0.84],
1315
+ [-0.87, -0.66],
1316
+ [-0.9, -0.47],
1317
+ [-0.76, -0.35]
1318
+ ];
1225
1319
  var artemis2 = {
1226
- name: "Artemis II",
1227
- fn: artemis2Fn,
1228
- period: TWO_PI2,
1229
- speed: 0.7,
1320
+ ...drawCurve(points, { name: "Artemis II" }),
1321
+ speed: 0.7
1230
1322
  };
1231
1323
 
1232
1324
  // src/curves/astroid.ts
1233
- var TWO_PI3 = Math.PI * 2;
1325
+ var TWO_PI2 = Math.PI * 2;
1234
1326
  function astroidFn(t, _time, _params) {
1235
1327
  const c = Math.cos(t);
1236
1328
  const s = Math.sin(t);
1237
1329
  return {
1238
1330
  x: c * c * c,
1239
- y: s * s * s,
1331
+ y: s * s * s
1240
1332
  };
1241
1333
  }
1242
1334
  var astroid = {
1243
1335
  name: "Astroid",
1244
1336
  fn: astroidFn,
1245
- period: TWO_PI3,
1246
- speed: 1.1,
1337
+ period: TWO_PI2,
1338
+ speed: 1.1
1247
1339
  };
1248
1340
 
1249
1341
  // src/curves/deltoid.ts
1250
- var TWO_PI4 = Math.PI * 2;
1342
+ var TWO_PI3 = Math.PI * 2;
1251
1343
  function deltoidFn(t, _time, _params) {
1252
1344
  return {
1253
1345
  x: 2 * Math.cos(t) + Math.cos(2 * t),
1254
- y: 2 * Math.sin(t) - Math.sin(2 * t),
1346
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
1255
1347
  };
1256
1348
  }
1257
1349
  var deltoid = {
1258
1350
  name: "Deltoid",
1259
1351
  fn: deltoidFn,
1260
- period: TWO_PI4,
1261
- speed: 0.9,
1352
+ period: TWO_PI3,
1353
+ speed: 0.9
1262
1354
  };
1263
1355
 
1264
1356
  // src/curves/epicycloid3.ts
1265
- var TWO_PI5 = Math.PI * 2;
1357
+ var TWO_PI4 = Math.PI * 2;
1266
1358
  function epicycloid3Fn(t, _time, _params) {
1267
1359
  return {
1268
1360
  x: 4 * Math.cos(t) - Math.cos(4 * t),
1269
- y: 4 * Math.sin(t) - Math.sin(4 * t),
1361
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
1270
1362
  };
1271
1363
  }
1272
1364
  var epicycloid3 = {
1273
1365
  name: "Epicycloid (n=3)",
1274
1366
  fn: epicycloid3Fn,
1275
- period: TWO_PI5,
1276
- speed: 0.75,
1367
+ period: TWO_PI4,
1368
+ speed: 0.75
1277
1369
  };
1278
1370
 
1279
1371
  // src/curves/epitrochoid7.ts
1280
- var TWO_PI6 = Math.PI * 2;
1372
+ var TWO_PI5 = Math.PI * 2;
1281
1373
  function epitrochoid7Fn(t, _time, _params) {
1282
1374
  const d = 1 + 0.55 * Math.sin(t * 0.5);
1283
1375
  return {
1284
1376
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1285
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1377
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1286
1378
  };
1287
1379
  }
1288
1380
  function epitrochoid7SkeletonFn(t) {
1289
1381
  const d = 1.275;
1290
1382
  return {
1291
1383
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1292
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1384
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1293
1385
  };
1294
1386
  }
1295
1387
  var epitrochoid7 = {
1296
1388
  name: "Epitrochoid",
1297
1389
  fn: epitrochoid7Fn,
1298
- period: TWO_PI6,
1390
+ period: TWO_PI5,
1299
1391
  speed: 1.4,
1300
- skeletonFn: epitrochoid7SkeletonFn,
1392
+ skeletonFn: epitrochoid7SkeletonFn
1301
1393
  };
1302
1394
 
1303
1395
  // src/curves/lissajous32.ts
1304
- var TWO_PI7 = Math.PI * 2;
1396
+ var TWO_PI6 = Math.PI * 2;
1305
1397
  function lissajous32Fn(t, time, _params) {
1306
1398
  const phi = time * 0.45;
1307
1399
  return {
1308
1400
  x: Math.sin(3 * t + phi),
1309
- y: Math.sin(2 * t),
1401
+ y: Math.sin(2 * t)
1310
1402
  };
1311
1403
  }
1312
1404
  var lissajous32 = {
1313
1405
  name: "Lissajous 3:2",
1314
1406
  fn: lissajous32Fn,
1315
- period: TWO_PI7,
1407
+ period: TWO_PI6,
1316
1408
  speed: 2,
1317
- skeleton: "live",
1409
+ skeleton: "live"
1318
1410
  };
1319
1411
 
1320
1412
  // src/curves/lissajous43.ts
1321
- var TWO_PI8 = Math.PI * 2;
1413
+ var TWO_PI7 = Math.PI * 2;
1322
1414
  function lissajous43Fn(t, time, _params) {
1323
1415
  const phi = time * 0.38;
1324
1416
  return {
1325
1417
  x: Math.sin(4 * t + phi),
1326
- y: Math.sin(3 * t),
1418
+ y: Math.sin(3 * t)
1327
1419
  };
1328
1420
  }
1329
1421
  var lissajous43 = {
1330
1422
  name: "Lissajous 4:3",
1331
1423
  fn: lissajous43Fn,
1332
- period: TWO_PI8,
1424
+ period: TWO_PI7,
1333
1425
  speed: 1.8,
1334
- skeleton: "live",
1426
+ skeleton: "live"
1335
1427
  };
1336
1428
 
1337
1429
  // src/curves/lame.ts
1338
- var TWO_PI9 = Math.PI * 2;
1430
+ var TWO_PI8 = Math.PI * 2;
1339
1431
  function lameFn(t, time, _params) {
1340
1432
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1341
- const c = Math.cos(t),
1342
- s = Math.sin(t);
1433
+ const c = Math.cos(t), s = Math.sin(t);
1343
1434
  return {
1344
1435
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1345
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
1436
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
1346
1437
  };
1347
1438
  }
1348
1439
  var lame = {
1349
1440
  name: "Lam\xE9 Curve",
1350
1441
  fn: lameFn,
1351
- period: TWO_PI9,
1442
+ period: TWO_PI8,
1352
1443
  speed: 1,
1353
- skeleton: "live",
1444
+ skeleton: "live"
1354
1445
  };
1355
1446
 
1356
1447
  // src/curves/rose3.ts
1357
- var TWO_PI10 = Math.PI * 2;
1448
+ var TWO_PI9 = Math.PI * 2;
1358
1449
  function rose3Fn(t, _time, _params) {
1359
1450
  const r = Math.cos(3 * t);
1360
1451
  return {
1361
1452
  x: r * Math.cos(t),
1362
- y: r * Math.sin(t),
1453
+ y: r * Math.sin(t)
1363
1454
  };
1364
1455
  }
1365
1456
  var rose3 = {
1366
1457
  name: "Rose (n=3)",
1367
1458
  fn: rose3Fn,
1368
- period: TWO_PI10,
1369
- speed: 1.15,
1459
+ period: TWO_PI9,
1460
+ speed: 1.15
1370
1461
  };
1371
1462
 
1372
1463
  // src/curves/rose5.ts
1373
- var TWO_PI11 = Math.PI * 2;
1464
+ var TWO_PI10 = Math.PI * 2;
1374
1465
  function rose5Fn(t, _time, _params) {
1375
1466
  const r = Math.cos(5 * t);
1376
1467
  return {
1377
1468
  x: r * Math.cos(t),
1378
- y: r * Math.sin(t),
1469
+ y: r * Math.sin(t)
1379
1470
  };
1380
1471
  }
1381
1472
  var rose5 = {
1382
1473
  name: "Rose (n=5)",
1383
1474
  fn: rose5Fn,
1384
- period: TWO_PI11,
1385
- speed: 1,
1475
+ period: TWO_PI10,
1476
+ speed: 1
1386
1477
  };
1387
1478
 
1388
1479
  // src/curves/rose52.ts
1389
1480
  var FOUR_PI = Math.PI * 4;
1390
1481
  function rose52Fn(t, _time, _params) {
1391
- const r = Math.cos((5 / 2) * t);
1482
+ const r = Math.cos(5 / 2 * t);
1392
1483
  return {
1393
1484
  x: r * Math.cos(t),
1394
- y: r * Math.sin(t),
1485
+ y: r * Math.sin(t)
1395
1486
  };
1396
1487
  }
1397
1488
  var rose52 = {
1398
1489
  name: "Rose (n=5/2)",
1399
1490
  fn: rose52Fn,
1400
1491
  period: FOUR_PI,
1401
- speed: 0.8,
1492
+ speed: 0.8
1402
1493
  };
1403
1494
 
1404
1495
  // src/curves/star.ts
1405
- var TWO_PI12 = Math.PI * 2;
1496
+ var TWO_PI11 = Math.PI * 2;
1406
1497
  function starFn(t, _time, _params) {
1407
- const r =
1408
- Math.abs(Math.cos((5 / 2) * t)) +
1409
- 0.35 * Math.abs(Math.cos((15 / 2) * t)) +
1410
- 0.15 * Math.abs(Math.cos((25 / 2) * t));
1498
+ 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));
1411
1499
  return {
1412
1500
  x: r * Math.cos(t),
1413
- y: r * Math.sin(t),
1501
+ y: r * Math.sin(t)
1414
1502
  };
1415
1503
  }
1416
1504
  var star = {
1417
1505
  name: "Star",
1418
1506
  fn: starFn,
1419
- period: TWO_PI12,
1420
- speed: 1,
1507
+ period: TWO_PI11,
1508
+ speed: 1
1421
1509
  };
1422
1510
 
1423
1511
  // src/curves/star4.ts
1424
- var TWO_PI13 = Math.PI * 2;
1512
+ var TWO_PI12 = Math.PI * 2;
1425
1513
  function star4Fn(t, _time, _params) {
1426
- const r =
1427
- Math.abs(Math.cos(2 * t)) +
1428
- 0.35 * Math.abs(Math.cos(6 * t)) +
1429
- 0.15 * Math.abs(Math.cos(10 * t));
1514
+ const r = Math.abs(Math.cos(2 * t)) + 0.35 * Math.abs(Math.cos(6 * t)) + 0.15 * Math.abs(Math.cos(10 * t));
1430
1515
  return {
1431
1516
  x: r * Math.cos(t),
1432
- y: r * Math.sin(t),
1517
+ y: r * Math.sin(t)
1433
1518
  };
1434
1519
  }
1435
1520
  var star4 = {
1436
1521
  name: "Star (4-arm)",
1437
1522
  fn: star4Fn,
1438
- period: TWO_PI13,
1439
- speed: 1,
1523
+ period: TWO_PI12,
1524
+ speed: 1
1440
1525
  };
1441
1526
 
1442
1527
  // src/curves/star7.ts
1443
- var TWO_PI14 = Math.PI * 2;
1528
+ var TWO_PI13 = Math.PI * 2;
1444
1529
  function star7Fn(t, _time, _params) {
1445
- const r =
1446
- Math.abs(Math.cos((7 / 2) * t)) +
1447
- 0.35 * Math.abs(Math.cos((21 / 2) * t)) +
1448
- 0.15 * Math.abs(Math.cos((35 / 2) * t));
1530
+ 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));
1449
1531
  return {
1450
1532
  x: r * Math.cos(t),
1451
- y: r * Math.sin(t),
1533
+ y: r * Math.sin(t)
1452
1534
  };
1453
1535
  }
1454
1536
  var star7 = {
1455
1537
  name: "Star (7-arm)",
1456
1538
  fn: star7Fn,
1457
- period: TWO_PI14,
1458
- speed: 1,
1539
+ period: TWO_PI13,
1540
+ speed: 1
1459
1541
  };
1460
1542
 
1461
1543
  // src/curves/index.ts
@@ -1473,58 +1555,9 @@ var curves = {
1473
1555
  lissajous32,
1474
1556
  lissajous43,
1475
1557
  epicycloid3,
1476
- lame,
1558
+ lame
1477
1559
  };
1478
1560
 
1479
- // src/catmull-rom.ts
1480
- var PERIOD = 2 * Math.PI;
1481
- function catmullRom1D(p0, p1, p2, p3, u) {
1482
- const u2 = u * u;
1483
- const u3 = u2 * u;
1484
- return (
1485
- 0.5 *
1486
- (2 * p1 +
1487
- (-p0 + p2) * u +
1488
- (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 +
1489
- (-p0 + 3 * p1 - 3 * p2 + p3) * u3)
1490
- );
1491
- }
1492
- function evaluateCatmullRom(points, t) {
1493
- const N = points.length;
1494
- if (N === 0) {
1495
- return { x: 0, y: 0 };
1496
- }
1497
- if (N === 1) {
1498
- return { x: points[0][0], y: points[0][1] };
1499
- }
1500
- t = ((t % PERIOD) + PERIOD) % PERIOD;
1501
- const segmentSize = PERIOD / N;
1502
- let i = Math.floor(t / segmentSize);
1503
- if (i >= N) {
1504
- i = N - 1;
1505
- }
1506
- let u = (t - i * segmentSize) / segmentSize;
1507
- u = Math.max(0, Math.min(1, u));
1508
- const p0 = points[(i - 1 + N) % N];
1509
- const p1 = points[i];
1510
- const p2 = points[(i + 1) % N];
1511
- const p3 = points[(i + 2) % N];
1512
- return {
1513
- x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
1514
- y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u),
1515
- };
1516
- }
1517
- function drawCurve(points) {
1518
- if (points.length < 3) {
1519
- throw new Error(`drawCurve requires at least 3 points, received ${points.length}.`);
1520
- }
1521
- return {
1522
- name: "custom",
1523
- fn: (t) => evaluateCatmullRom(points, t),
1524
- period: PERIOD,
1525
- };
1526
- }
1527
-
1528
1561
  // src/index.ts
1529
1562
  function createSarmal(canvas, curveDef, options) {
1530
1563
  const { trailLength, ...rendererOpts } = options ?? {};
@@ -1534,6 +1567,7 @@ function createSarmal(canvas, curveDef, options) {
1534
1567
 
1535
1568
  exports.artemis2 = artemis2;
1536
1569
  exports.astroid = astroid;
1570
+ exports.computeBoundaries = computeBoundaries;
1537
1571
  exports.createEngine = createEngine;
1538
1572
  exports.createRenderer = createRenderer;
1539
1573
  exports.createSVGRenderer = createSVGRenderer;
@@ -1552,4 +1586,4 @@ exports.palettes = palettes;
1552
1586
  exports.rose3 = rose3;
1553
1587
  exports.rose5 = rose5;
1554
1588
  //# sourceMappingURL=index.cjs.map
1555
- //# sourceMappingURL=index.cjs.map
1589
+ //# sourceMappingURL=index.cjs.map