@sarmal/core 0.24.0 → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/auto-init.cjs +245 -141
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.js +244 -140
  4. package/dist/auto-init.js.map +1 -1
  5. package/dist/curves/artemis2.cjs +87 -14
  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 +86 -13
  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 +154 -71
  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 +170 -71
  32. package/dist/curves/index.js.map +1 -1
  33. package/dist/curves/lame.cjs +6 -5
  34. package/dist/curves/lame.d.cts +1 -1
  35. package/dist/curves/lame.d.ts +1 -1
  36. package/dist/curves/lame.js +5 -4
  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 +8 -5
  58. package/dist/curves/star.d.cts +1 -1
  59. package/dist/curves/star.d.ts +1 -1
  60. package/dist/curves/star.js +7 -4
  61. package/dist/curves/star4.cjs +8 -5
  62. package/dist/curves/star4.d.cts +1 -1
  63. package/dist/curves/star4.d.ts +1 -1
  64. package/dist/curves/star4.js +7 -4
  65. package/dist/curves/star7.cjs +8 -5
  66. package/dist/curves/star7.d.cts +1 -1
  67. package/dist/curves/star7.d.ts +1 -1
  68. package/dist/curves/star7.js +7 -4
  69. package/dist/index.cjs +232 -172
  70. package/dist/index.cjs.map +1 -1
  71. package/dist/index.d.cts +106 -36
  72. package/dist/index.d.ts +106 -36
  73. package/dist/index.js +252 -172
  74. package/dist/index.js.map +1 -1
  75. package/dist/types-Z9i1_AQZ.d.cts +339 -0
  76. package/dist/types-Z9i1_AQZ.d.ts +339 -0
  77. package/package.json +1 -1
  78. package/dist/types-CknrlCAf.d.cts +0 -314
  79. package/dist/types-CknrlCAf.d.ts +0 -314
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,13 +170,16 @@ 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 = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
173
+ const tB =
174
+ frozenStrategy === "normalized"
175
+ ? (sampleT / frozenA.period) * frozenB.period
176
+ : sampleT;
174
177
  const b = frozenB.fn(tB, time, params);
175
178
  return {
176
179
  x: a.x + (b.x - a.x) * frozenAlpha,
177
- y: a.y + (b.y - a.y) * frozenAlpha
180
+ y: a.y + (b.y - a.y) * frozenAlpha,
178
181
  };
179
- }
182
+ },
180
183
  };
181
184
  }
182
185
  _morphStrategy = strategy;
@@ -189,7 +192,7 @@ function createEngine(curveDef, trailLength = 120) {
189
192
  completeMorph() {
190
193
  if (morphCurveB !== null) {
191
194
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
192
- t = t / curve.period * morphCurveB.period;
195
+ t = (t / curve.period) * morphCurveB.period;
193
196
  }
194
197
  curve = morphCurveB;
195
198
  }
@@ -198,25 +201,28 @@ function createEngine(curveDef, trailLength = 120) {
198
201
  },
199
202
  getSarmalSkeleton() {
200
203
  const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
201
- const points = new Array(steps);
204
+ const points2 = new Array(steps);
202
205
  if (morphCurveB !== null && _morphAlpha !== null) {
203
206
  for (let i = 0; i < steps; i++) {
204
- const sampleT = i / (steps - 1) * curve.period;
207
+ const sampleT = (i / (steps - 1)) * curve.period;
205
208
  const a = sampleSkeleton(curve, sampleT);
206
- const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
209
+ const tB =
210
+ _morphStrategy === "normalized"
211
+ ? (sampleT / curve.period) * morphCurveB.period
212
+ : sampleT;
207
213
  const b = sampleSkeleton(morphCurveB, tB);
208
- points[i] = {
214
+ points2[i] = {
209
215
  x: a.x + (b.x - a.x) * _morphAlpha,
210
- y: a.y + (b.y - a.y) * _morphAlpha
216
+ y: a.y + (b.y - a.y) * _morphAlpha,
211
217
  };
212
218
  }
213
- return points;
219
+ return points2;
214
220
  }
215
221
  for (let i = 0; i < steps; i++) {
216
- const sampleT = i / (steps - 1) * curve.period;
217
- points[i] = sampleSkeleton(curve, sampleT);
222
+ const sampleT = (i / (steps - 1)) * curve.period;
223
+ points2[i] = sampleSkeleton(curve, sampleT);
218
224
  }
219
- return points;
225
+ return points2;
220
226
  },
221
227
  setSpeed(speed) {
222
228
  if (!Number.isFinite(speed)) {
@@ -255,7 +261,7 @@ function createEngine(curveDef, trailLength = 120) {
255
261
  _speedTransition.reject(new Error("Speed transition cancelled"));
256
262
  _speedTransition = null;
257
263
  }
258
- }
264
+ },
259
265
  };
260
266
  }
261
267
 
@@ -294,7 +300,15 @@ function computeNormal(trail, i) {
294
300
  const tangent = computeTangent(trail, i);
295
301
  return { x: -tangent.y, y: tangent.x };
296
302
  }
297
- function computeTrailQuad(trail, i, trailCount, toX, toY, minWidth = TRAIL_MIN_WIDTH, maxWidth = TRAIL_MAX_WIDTH) {
303
+ function computeTrailQuad(
304
+ trail,
305
+ i,
306
+ trailCount,
307
+ toX,
308
+ toY,
309
+ minWidth = TRAIL_MIN_WIDTH,
310
+ maxWidth = TRAIL_MAX_WIDTH,
311
+ ) {
298
312
  const progress = i / (trailCount - 1);
299
313
  const nextProgress = (i + 1) / (trailCount - 1);
300
314
  const opacity = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
@@ -318,13 +332,16 @@ function computeTrailQuad(trail, i, trailCount, toX, toY, minWidth = TRAIL_MIN_W
318
332
  r1x: nx - n1.x * w1,
319
333
  r1y: ny - n1.y * w1,
320
334
  opacity,
321
- progress
335
+ progress,
322
336
  };
323
337
  }
324
- function computeBoundaries(pts, logicalWidth, logicalHeight) {
338
+ function computeBoundaries(pts, logicalWidth, logicalHeight, minPaddingPx = FIT_PADDING_MIN) {
325
339
  if (pts.length === 0) return null;
326
340
  const first = pts[0];
327
- let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
341
+ let minX = first.x,
342
+ maxX = first.x,
343
+ minY = first.y,
344
+ maxY = first.y;
328
345
  for (const p of pts) {
329
346
  if (p.x < minX) {
330
347
  minX = p.x;
@@ -343,23 +360,23 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
343
360
  const h = maxY - minY;
344
361
  if (w === 0 && h === 0) {
345
362
  throw new Error(
346
- "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
363
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
347
364
  );
348
365
  }
349
366
  const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
350
367
  const scaleYProportional = logicalHeight / (h * (1 + FIT_PADDING * 2));
351
- const scaleXMinPadding = (logicalWidth - FIT_PADDING_MIN * 2) / w;
352
- const scaleYMinPadding = (logicalHeight - FIT_PADDING_MIN * 2) / h;
368
+ const scaleXMinPadding = (logicalWidth - minPaddingPx * 2) / w;
369
+ const scaleYMinPadding = (logicalHeight - minPaddingPx * 2) / h;
353
370
  const scale = Math.min(
354
371
  scaleXProportional,
355
372
  scaleYProportional,
356
373
  scaleXMinPadding,
357
- scaleYMinPadding
374
+ scaleYMinPadding,
358
375
  );
359
376
  return {
360
377
  scale,
361
378
  offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
362
- offsetY: (logicalHeight - h * scale) / 2 - minY * scale
379
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
363
380
  };
364
381
  }
365
382
  function enginePassthroughs(engine) {
@@ -369,7 +386,8 @@ function enginePassthroughs(engine) {
369
386
  setSpeed: engine.setSpeed,
370
387
  getSpeed: engine.getSpeed,
371
388
  resetSpeed: engine.resetSpeed,
372
- setSpeedOver: engine.setSpeedOver
389
+ setSpeedOver: engine.setSpeedOver,
390
+ getSarmalSkeleton: engine.getSarmalSkeleton,
373
391
  };
374
392
  }
375
393
  var palettes = {
@@ -378,16 +396,16 @@ var palettes = {
378
396
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
379
397
  ice: ["#1e3a8a", "#67e8f9"],
380
398
  fire: ["#7f1d1d", "#fbbf24"],
381
- forest: ["#14532d", "#86efac"]
399
+ forest: ["#14532d", "#86efac"],
382
400
  };
383
401
  function hexToRgb(hex) {
384
402
  const n = parseInt(hex.slice(1), 16);
385
- return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
403
+ return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
386
404
  }
387
405
  var lerpRgb = (a, b, t) => ({
388
406
  r: Math.round(a.r + (b.r - a.r) * t),
389
407
  g: Math.round(a.g + (b.g - a.g) * t),
390
- b: Math.round(a.b + (b.b - a.b) * t)
408
+ b: Math.round(a.b + (b.b - a.b) * t),
391
409
  });
392
410
  function getPaletteColor(palette, position, timeOffset = 0) {
393
411
  if (palette.length === 0) {
@@ -396,7 +414,7 @@ function getPaletteColor(palette, position, timeOffset = 0) {
396
414
  if (palette.length === 1) {
397
415
  return hexToRgb(palette[0]);
398
416
  }
399
- const cyclePos = ((position + timeOffset) % 1 + 1) % 1;
417
+ const cyclePos = (((position + timeOffset) % 1) + 1) % 1;
400
418
  const scaled = cyclePos * palette.length;
401
419
  const idx = Math.floor(scaled);
402
420
  const t = scaled - idx;
@@ -411,7 +429,7 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
411
429
  "headColor",
412
430
  "skeletonColor",
413
431
  "trailStyle",
414
- "headRadius"
432
+ "headRadius",
415
433
  ]);
416
434
  function validateRenderOptions(partial) {
417
435
  for (const key of Object.keys(partial)) {
@@ -439,7 +457,7 @@ function assertTrailColor(value) {
439
457
  if (typeof value === "string") {
440
458
  if (!HEX_COLOR_RE.test(value)) {
441
459
  throw new TypeError(
442
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`
460
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`,
443
461
  );
444
462
  }
445
463
  return;
@@ -447,21 +465,21 @@ function assertTrailColor(value) {
447
465
  if (Array.isArray(value)) {
448
466
  if (value.length < 2) {
449
467
  throw new RangeError(
450
- `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`
468
+ `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`,
451
469
  );
452
470
  }
453
471
  for (let i = 0; i < value.length; i++) {
454
472
  const entry = value[i];
455
473
  if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
456
474
  throw new TypeError(
457
- `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`
475
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`,
458
476
  );
459
477
  }
460
478
  }
461
479
  return;
462
480
  }
463
481
  throw new TypeError(
464
- `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`
482
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`,
465
483
  );
466
484
  }
467
485
  function assertHeadColor(value) {
@@ -470,7 +488,7 @@ function assertHeadColor(value) {
470
488
  }
471
489
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
472
490
  throw new TypeError(
473
- `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`
491
+ `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`,
474
492
  );
475
493
  }
476
494
  }
@@ -480,26 +498,26 @@ function assertSkeletonColor(value) {
480
498
  }
481
499
  if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
482
500
  throw new TypeError(
483
- `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`
501
+ `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`,
484
502
  );
485
503
  }
486
504
  }
487
505
  function assertTrailStyle(value) {
488
506
  if (!TRAIL_STYLES.includes(value)) {
489
507
  throw new RangeError(
490
- `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`
508
+ `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`,
491
509
  );
492
510
  }
493
511
  }
494
512
  function assertHeadRadius(value) {
495
513
  if (typeof value !== "number") {
496
514
  throw new TypeError(
497
- `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`
515
+ `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`,
498
516
  );
499
517
  }
500
518
  if (!Number.isFinite(value) || value <= 0) {
501
519
  throw new TypeError(
502
- `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`
520
+ `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`,
503
521
  );
504
522
  }
505
523
  }
@@ -521,13 +539,13 @@ function resolveHeadColor(trailColor, trailStyle) {
521
539
  function warnIfTrailColorMismatch(trailColor, trailStyle) {
522
540
  if (trailStyle === "default" && Array.isArray(trailColor)) {
523
541
  console.warn(
524
- '[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
+ '[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
543
  );
526
544
  return;
527
545
  }
528
546
  if (trailStyle !== "default" && typeof trailColor === "string") {
529
547
  console.warn(
530
- `[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
+ `[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
549
  );
532
550
  }
533
551
  }
@@ -537,7 +555,7 @@ var getHeadDotRadius = (w, h) => Math.max(1, 3 * Math.sqrt(Math.min(w, h) / 160)
537
555
  var WHITE_HEX = "#ffffff";
538
556
  function hexToRgbComponents(hex) {
539
557
  const n = parseInt(hex.slice(1), 16);
540
- return `${n >> 16},${n >> 8 & 255},${n & 255}`;
558
+ return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
541
559
  }
542
560
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
543
561
  target.style.width = `${logicalWidth}px`;
@@ -664,7 +682,7 @@ function createRenderer(options) {
664
682
  i,
665
683
  trailCount,
666
684
  toX,
667
- toY
685
+ toY,
668
686
  );
669
687
  if (trailStyle === "default") {
670
688
  ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
@@ -829,7 +847,7 @@ function createRenderer(options) {
829
847
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
830
848
  warnIfTrailColorMismatch(trailColor, trailStyle);
831
849
  }
832
- }
850
+ },
833
851
  };
834
852
  const pauseOnHidden = options.pauseOnHidden !== false;
835
853
  function handleVisibilityChange() {
@@ -879,7 +897,7 @@ function sampleCurveSkeleton(curveDef) {
879
897
  const samples = Math.ceil(period * 50);
880
898
  const pts = Array.from({ length: samples });
881
899
  for (let i = 0; i < samples; i++) {
882
- const t = i / (samples - 1) * period;
900
+ const t = (i / (samples - 1)) * period;
883
901
  pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
884
902
  }
885
903
  return pts;
@@ -892,7 +910,7 @@ function createSVGRenderer(options) {
892
910
  const poolSize = engine.trailLength;
893
911
  if (poolSize > HIGH_TRAIL_LENGTH_THRESHOLD) {
894
912
  console.warn(
895
- `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`
913
+ `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`,
896
914
  );
897
915
  }
898
916
  let trailStyle = options.trailStyle ?? "default";
@@ -911,9 +929,9 @@ function createSVGRenderer(options) {
911
929
  return rect.width && rect.height ? Math.min(rect.width, rect.height) : 200;
912
930
  }
913
931
  const containerPx = getContainerPixelSize();
914
- const svgTrailMinWidth = TRAIL_MIN_WIDTH * viewSize / containerPx;
915
- const svgTrailMaxWidth = TRAIL_MAX_WIDTH * viewSize / containerPx;
916
- const svgSkeletonStrokeWidth = String(SKELETON_STROKE_WIDTH_PX * viewSize / containerPx);
932
+ const svgTrailMinWidth = (TRAIL_MIN_WIDTH * viewSize) / containerPx;
933
+ const svgTrailMaxWidth = (TRAIL_MAX_WIDTH * viewSize) / containerPx;
934
+ const svgSkeletonStrokeWidth = String((SKELETON_STROKE_WIDTH_PX * viewSize) / containerPx);
917
935
  headRadius = options.headRadius ?? SVG_DEFAULT_HEAD_RADIUS;
918
936
  container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
919
937
  container.setAttribute("role", "img");
@@ -1001,7 +1019,7 @@ function createSVGRenderer(options) {
1001
1019
  px,
1002
1020
  py,
1003
1021
  svgTrailMinWidth,
1004
- svgTrailMaxWidth
1022
+ svgTrailMaxWidth,
1005
1023
  );
1006
1024
  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`;
1007
1025
  trailPaths[i].setAttribute("d", d);
@@ -1029,7 +1047,8 @@ function createSVGRenderer(options) {
1029
1047
  let animationId = null;
1030
1048
  let lastTime = 0;
1031
1049
  let pausedByVisibility = false;
1032
- const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1050
+ const prefersReducedMotion =
1051
+ typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1033
1052
  let morphResolve = null;
1034
1053
  let morphReject = null;
1035
1054
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
@@ -1047,7 +1066,7 @@ function createSVGRenderer(options) {
1047
1066
  skeletonPathA.setAttribute("visibility", "visible");
1048
1067
  skeletonPathA.setAttribute(
1049
1068
  "stroke-opacity",
1050
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
1069
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
1051
1070
  );
1052
1071
  }
1053
1072
  if (morphPathBBuilt) {
@@ -1201,7 +1220,7 @@ function createSVGRenderer(options) {
1201
1220
  if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1202
1221
  warnIfTrailColorMismatch(trailColor, trailStyle);
1203
1222
  }
1204
- }
1223
+ },
1205
1224
  };
1206
1225
  const pauseOnHidden = options.pauseOnHidden !== false;
1207
1226
  function handleVisibilityChange() {
@@ -1234,241 +1253,324 @@ function createSarmalSVG(container, curveDef, options) {
1234
1253
  return createSVGRenderer({ container, engine, ...rendererOpts });
1235
1254
  }
1236
1255
 
1237
- // src/curves/artemis2.ts
1238
- var TWO_PI2 = Math.PI * 2;
1239
- function artemis2Fn(t, _time, _params) {
1240
- const a = 0.35, b = 0.15, ox = 0.175;
1241
- const s = Math.sin(t), c = Math.cos(t);
1242
- const denom = 1 + s * s;
1256
+ // src/catmull-rom.ts
1257
+ var PERIOD = 2 * Math.PI;
1258
+ function catmullRom1D(p0, p1, p2, p3, u) {
1259
+ const u2 = u * u;
1260
+ const u3 = u2 * u;
1261
+ return (
1262
+ 0.5 *
1263
+ (2 * p1 +
1264
+ (-p0 + p2) * u +
1265
+ (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 +
1266
+ (-p0 + 3 * p1 - 3 * p2 + p3) * u3)
1267
+ );
1268
+ }
1269
+ function evaluateCatmullRom(points2, t) {
1270
+ const N = points2.length;
1271
+ if (N === 0) {
1272
+ return { x: 0, y: 0 };
1273
+ }
1274
+ if (N === 1) {
1275
+ return { x: points2[0][0], y: points2[0][1] };
1276
+ }
1277
+ t = ((t % PERIOD) + PERIOD) % PERIOD;
1278
+ const segmentSize = PERIOD / N;
1279
+ let i = Math.floor(t / segmentSize);
1280
+ if (i >= N) {
1281
+ i = N - 1;
1282
+ }
1283
+ let u = (t - i * segmentSize) / segmentSize;
1284
+ u = Math.max(0, Math.min(1, u));
1285
+ const p0 = points2[(i - 1 + N) % N];
1286
+ const p1 = points2[i];
1287
+ const p2 = points2[(i + 1) % N];
1288
+ const p3 = points2[(i + 2) % N];
1289
+ return {
1290
+ x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
1291
+ y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u),
1292
+ };
1293
+ }
1294
+ function drawCurve(points2, opts) {
1295
+ if (points2.length < 3) {
1296
+ throw new Error(`drawCurve requires at least 3 points, received ${points2.length}.`);
1297
+ }
1298
+ const first = points2[0];
1299
+ if (points2.every((p) => p[0] === first[0] && p[1] === first[1])) {
1300
+ console.warn(
1301
+ "[sarmal].drawCurve: all control points are identical. The curve will be a single point.",
1302
+ );
1303
+ }
1304
+ const maxAbs = points2.reduce((m, p) => Math.max(m, Math.abs(p[0]), Math.abs(p[1])), 0);
1305
+ if (maxAbs > 2) {
1306
+ console.warn(
1307
+ `[sarmal].drawCurve: control points extend to \xB1${maxAbs.toFixed(1)}, which may render off-screen. Coordinates should be in [-1, 1].`,
1308
+ );
1309
+ }
1310
+ const pts = points2.map(([x, y]) => [x, y]);
1243
1311
  return {
1244
- x: c * (1 + a * c) / denom - ox,
1245
- y: s * c * (1 + b * c) / denom
1312
+ name: opts?.name ?? "drawn",
1313
+ fn: (t) => evaluateCatmullRom(pts, t),
1314
+ period: PERIOD,
1246
1315
  };
1247
1316
  }
1317
+
1318
+ // src/curves/artemis2.ts
1319
+ var points = [
1320
+ [-0.44, -0.45],
1321
+ [-0.53, -0.77],
1322
+ [-0.82, -0.66],
1323
+ [-0.82, -0.18],
1324
+ [-0.25, -0.04],
1325
+ [0.16, -0.49],
1326
+ [-0.03, -0.87],
1327
+ [-0.68, -0.94],
1328
+ [-0.95, -0.61],
1329
+ [-0.87, -0],
1330
+ [-0.34, 0.21],
1331
+ [0.27, -0.04],
1332
+ [0.87, 0.06],
1333
+ [0.87, 0.57],
1334
+ [0.32, 0.66],
1335
+ [-0.21, -0.43],
1336
+ [-0.43, -0.81],
1337
+ [-0.69, -0.84],
1338
+ [-0.87, -0.66],
1339
+ [-0.9, -0.47],
1340
+ [-0.76, -0.35],
1341
+ ];
1248
1342
  var artemis2 = {
1249
- name: "Artemis II",
1250
- fn: artemis2Fn,
1251
- period: TWO_PI2,
1252
- speed: 0.7
1343
+ ...drawCurve(points, { name: "Artemis II" }),
1344
+ speed: 0.7,
1253
1345
  };
1254
1346
 
1255
1347
  // src/curves/astroid.ts
1256
- var TWO_PI3 = Math.PI * 2;
1348
+ var TWO_PI2 = Math.PI * 2;
1257
1349
  function astroidFn(t, _time, _params) {
1258
1350
  const c = Math.cos(t);
1259
1351
  const s = Math.sin(t);
1260
1352
  return {
1261
1353
  x: c * c * c,
1262
- y: s * s * s
1354
+ y: s * s * s,
1263
1355
  };
1264
1356
  }
1265
1357
  var astroid = {
1266
1358
  name: "Astroid",
1267
1359
  fn: astroidFn,
1268
- period: TWO_PI3,
1269
- speed: 1.1
1360
+ period: TWO_PI2,
1361
+ speed: 1.1,
1270
1362
  };
1271
1363
 
1272
1364
  // src/curves/deltoid.ts
1273
- var TWO_PI4 = Math.PI * 2;
1365
+ var TWO_PI3 = Math.PI * 2;
1274
1366
  function deltoidFn(t, _time, _params) {
1275
1367
  return {
1276
1368
  x: 2 * Math.cos(t) + Math.cos(2 * t),
1277
- y: 2 * Math.sin(t) - Math.sin(2 * t)
1369
+ y: 2 * Math.sin(t) - Math.sin(2 * t),
1278
1370
  };
1279
1371
  }
1280
1372
  var deltoid = {
1281
1373
  name: "Deltoid",
1282
1374
  fn: deltoidFn,
1283
- period: TWO_PI4,
1284
- speed: 0.9
1375
+ period: TWO_PI3,
1376
+ speed: 0.9,
1285
1377
  };
1286
1378
 
1287
1379
  // src/curves/epicycloid3.ts
1288
- var TWO_PI5 = Math.PI * 2;
1380
+ var TWO_PI4 = Math.PI * 2;
1289
1381
  function epicycloid3Fn(t, _time, _params) {
1290
1382
  return {
1291
1383
  x: 4 * Math.cos(t) - Math.cos(4 * t),
1292
- y: 4 * Math.sin(t) - Math.sin(4 * t)
1384
+ y: 4 * Math.sin(t) - Math.sin(4 * t),
1293
1385
  };
1294
1386
  }
1295
1387
  var epicycloid3 = {
1296
1388
  name: "Epicycloid (n=3)",
1297
1389
  fn: epicycloid3Fn,
1298
- period: TWO_PI5,
1299
- speed: 0.75
1390
+ period: TWO_PI4,
1391
+ speed: 0.75,
1300
1392
  };
1301
1393
 
1302
1394
  // src/curves/epitrochoid7.ts
1303
- var TWO_PI6 = Math.PI * 2;
1395
+ var TWO_PI5 = Math.PI * 2;
1304
1396
  function epitrochoid7Fn(t, _time, _params) {
1305
1397
  const d = 1 + 0.55 * Math.sin(t * 0.5);
1306
1398
  return {
1307
1399
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1308
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1400
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1309
1401
  };
1310
1402
  }
1311
1403
  function epitrochoid7SkeletonFn(t) {
1312
1404
  const d = 1.275;
1313
1405
  return {
1314
1406
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1315
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1407
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1316
1408
  };
1317
1409
  }
1318
1410
  var epitrochoid7 = {
1319
1411
  name: "Epitrochoid",
1320
1412
  fn: epitrochoid7Fn,
1321
- period: TWO_PI6,
1413
+ period: TWO_PI5,
1322
1414
  speed: 1.4,
1323
- skeletonFn: epitrochoid7SkeletonFn
1415
+ skeletonFn: epitrochoid7SkeletonFn,
1324
1416
  };
1325
1417
 
1326
1418
  // src/curves/lissajous32.ts
1327
- var TWO_PI7 = Math.PI * 2;
1419
+ var TWO_PI6 = Math.PI * 2;
1328
1420
  function lissajous32Fn(t, time, _params) {
1329
1421
  const phi = time * 0.45;
1330
1422
  return {
1331
1423
  x: Math.sin(3 * t + phi),
1332
- y: Math.sin(2 * t)
1424
+ y: Math.sin(2 * t),
1333
1425
  };
1334
1426
  }
1335
1427
  var lissajous32 = {
1336
1428
  name: "Lissajous 3:2",
1337
1429
  fn: lissajous32Fn,
1338
- period: TWO_PI7,
1430
+ period: TWO_PI6,
1339
1431
  speed: 2,
1340
- skeleton: "live"
1432
+ skeleton: "live",
1341
1433
  };
1342
1434
 
1343
1435
  // src/curves/lissajous43.ts
1344
- var TWO_PI8 = Math.PI * 2;
1436
+ var TWO_PI7 = Math.PI * 2;
1345
1437
  function lissajous43Fn(t, time, _params) {
1346
1438
  const phi = time * 0.38;
1347
1439
  return {
1348
1440
  x: Math.sin(4 * t + phi),
1349
- y: Math.sin(3 * t)
1441
+ y: Math.sin(3 * t),
1350
1442
  };
1351
1443
  }
1352
1444
  var lissajous43 = {
1353
1445
  name: "Lissajous 4:3",
1354
1446
  fn: lissajous43Fn,
1355
- period: TWO_PI8,
1447
+ period: TWO_PI7,
1356
1448
  speed: 1.8,
1357
- skeleton: "live"
1449
+ skeleton: "live",
1358
1450
  };
1359
1451
 
1360
1452
  // src/curves/lame.ts
1361
- var TWO_PI9 = Math.PI * 2;
1453
+ var TWO_PI8 = Math.PI * 2;
1362
1454
  function lameFn(t, time, _params) {
1363
1455
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1364
- const c = Math.cos(t), s = Math.sin(t);
1456
+ const c = Math.cos(t),
1457
+ s = Math.sin(t);
1365
1458
  return {
1366
1459
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1367
- y: Math.sign(s) * Math.pow(Math.abs(s), p)
1460
+ y: Math.sign(s) * Math.pow(Math.abs(s), p),
1368
1461
  };
1369
1462
  }
1370
1463
  var lame = {
1371
1464
  name: "Lam\xE9 Curve",
1372
1465
  fn: lameFn,
1373
- period: TWO_PI9,
1466
+ period: TWO_PI8,
1374
1467
  speed: 1,
1375
- skeleton: "live"
1468
+ skeleton: "live",
1376
1469
  };
1377
1470
 
1378
1471
  // src/curves/rose3.ts
1379
- var TWO_PI10 = Math.PI * 2;
1472
+ var TWO_PI9 = Math.PI * 2;
1380
1473
  function rose3Fn(t, _time, _params) {
1381
1474
  const r = Math.cos(3 * t);
1382
1475
  return {
1383
1476
  x: r * Math.cos(t),
1384
- y: r * Math.sin(t)
1477
+ y: r * Math.sin(t),
1385
1478
  };
1386
1479
  }
1387
1480
  var rose3 = {
1388
1481
  name: "Rose (n=3)",
1389
1482
  fn: rose3Fn,
1390
- period: TWO_PI10,
1391
- speed: 1.15
1483
+ period: TWO_PI9,
1484
+ speed: 1.15,
1392
1485
  };
1393
1486
 
1394
1487
  // src/curves/rose5.ts
1395
- var TWO_PI11 = Math.PI * 2;
1488
+ var TWO_PI10 = Math.PI * 2;
1396
1489
  function rose5Fn(t, _time, _params) {
1397
1490
  const r = Math.cos(5 * t);
1398
1491
  return {
1399
1492
  x: r * Math.cos(t),
1400
- y: r * Math.sin(t)
1493
+ y: r * Math.sin(t),
1401
1494
  };
1402
1495
  }
1403
1496
  var rose5 = {
1404
1497
  name: "Rose (n=5)",
1405
1498
  fn: rose5Fn,
1406
- period: TWO_PI11,
1407
- speed: 1
1499
+ period: TWO_PI10,
1500
+ speed: 1,
1408
1501
  };
1409
1502
 
1410
1503
  // src/curves/rose52.ts
1411
1504
  var FOUR_PI = Math.PI * 4;
1412
1505
  function rose52Fn(t, _time, _params) {
1413
- const r = Math.cos(5 / 2 * t);
1506
+ const r = Math.cos((5 / 2) * t);
1414
1507
  return {
1415
1508
  x: r * Math.cos(t),
1416
- y: r * Math.sin(t)
1509
+ y: r * Math.sin(t),
1417
1510
  };
1418
1511
  }
1419
1512
  var rose52 = {
1420
1513
  name: "Rose (n=5/2)",
1421
1514
  fn: rose52Fn,
1422
1515
  period: FOUR_PI,
1423
- speed: 0.8
1516
+ speed: 0.8,
1424
1517
  };
1425
1518
 
1426
1519
  // src/curves/star.ts
1427
- var TWO_PI12 = Math.PI * 2;
1520
+ var TWO_PI11 = Math.PI * 2;
1428
1521
  function starFn(t, _time, _params) {
1429
- 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));
1522
+ const r =
1523
+ Math.abs(Math.cos((5 / 2) * t)) +
1524
+ 0.35 * Math.abs(Math.cos((15 / 2) * t)) +
1525
+ 0.15 * Math.abs(Math.cos((25 / 2) * t));
1430
1526
  return {
1431
1527
  x: r * Math.cos(t),
1432
- y: r * Math.sin(t)
1528
+ y: r * Math.sin(t),
1433
1529
  };
1434
1530
  }
1435
1531
  var star = {
1436
1532
  name: "Star",
1437
1533
  fn: starFn,
1438
- period: TWO_PI12,
1439
- speed: 1
1534
+ period: TWO_PI11,
1535
+ speed: 1,
1440
1536
  };
1441
1537
 
1442
1538
  // src/curves/star4.ts
1443
- var TWO_PI13 = Math.PI * 2;
1539
+ var TWO_PI12 = Math.PI * 2;
1444
1540
  function star4Fn(t, _time, _params) {
1445
- const r = Math.abs(Math.cos(2 * t)) + 0.35 * Math.abs(Math.cos(6 * t)) + 0.15 * Math.abs(Math.cos(10 * t));
1541
+ const r =
1542
+ Math.abs(Math.cos(2 * t)) +
1543
+ 0.35 * Math.abs(Math.cos(6 * t)) +
1544
+ 0.15 * Math.abs(Math.cos(10 * t));
1446
1545
  return {
1447
1546
  x: r * Math.cos(t),
1448
- y: r * Math.sin(t)
1547
+ y: r * Math.sin(t),
1449
1548
  };
1450
1549
  }
1451
1550
  var star4 = {
1452
1551
  name: "Star (4-arm)",
1453
1552
  fn: star4Fn,
1454
- period: TWO_PI13,
1455
- speed: 1
1553
+ period: TWO_PI12,
1554
+ speed: 1,
1456
1555
  };
1457
1556
 
1458
1557
  // src/curves/star7.ts
1459
- var TWO_PI14 = Math.PI * 2;
1558
+ var TWO_PI13 = Math.PI * 2;
1460
1559
  function star7Fn(t, _time, _params) {
1461
- 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));
1560
+ const r =
1561
+ Math.abs(Math.cos((7 / 2) * t)) +
1562
+ 0.35 * Math.abs(Math.cos((21 / 2) * t)) +
1563
+ 0.15 * Math.abs(Math.cos((35 / 2) * t));
1462
1564
  return {
1463
1565
  x: r * Math.cos(t),
1464
- y: r * Math.sin(t)
1566
+ y: r * Math.sin(t),
1465
1567
  };
1466
1568
  }
1467
1569
  var star7 = {
1468
1570
  name: "Star (7-arm)",
1469
1571
  fn: star7Fn,
1470
- period: TWO_PI14,
1471
- speed: 1
1572
+ period: TWO_PI13,
1573
+ speed: 1,
1472
1574
  };
1473
1575
 
1474
1576
  // src/curves/index.ts
@@ -1486,52 +1588,9 @@ var curves = {
1486
1588
  lissajous32,
1487
1589
  lissajous43,
1488
1590
  epicycloid3,
1489
- lame
1591
+ lame,
1490
1592
  };
1491
1593
 
1492
- // src/catmull-rom.ts
1493
- var PERIOD = 2 * Math.PI;
1494
- function catmullRom1D(p0, p1, p2, p3, u) {
1495
- const u2 = u * u;
1496
- const u3 = u2 * u;
1497
- return 0.5 * (2 * p1 + (-p0 + p2) * u + (2 * p0 - 5 * p1 + 4 * p2 - p3) * u2 + (-p0 + 3 * p1 - 3 * p2 + p3) * u3);
1498
- }
1499
- function evaluateCatmullRom(points, t) {
1500
- const N = points.length;
1501
- if (N === 0) {
1502
- return { x: 0, y: 0 };
1503
- }
1504
- if (N === 1) {
1505
- return { x: points[0][0], y: points[0][1] };
1506
- }
1507
- t = (t % PERIOD + PERIOD) % PERIOD;
1508
- const segmentSize = PERIOD / N;
1509
- let i = Math.floor(t / segmentSize);
1510
- if (i >= N) {
1511
- i = N - 1;
1512
- }
1513
- let u = (t - i * segmentSize) / segmentSize;
1514
- u = Math.max(0, Math.min(1, u));
1515
- const p0 = points[(i - 1 + N) % N];
1516
- const p1 = points[i];
1517
- const p2 = points[(i + 1) % N];
1518
- const p3 = points[(i + 2) % N];
1519
- return {
1520
- x: catmullRom1D(p0[0], p1[0], p2[0], p3[0], u),
1521
- y: catmullRom1D(p0[1], p1[1], p2[1], p3[1], u)
1522
- };
1523
- }
1524
- function drawCurve(points) {
1525
- if (points.length < 3) {
1526
- throw new Error(`drawCurve requires at least 3 points, received ${points.length}.`);
1527
- }
1528
- return {
1529
- name: "custom",
1530
- fn: (t) => evaluateCatmullRom(points, t),
1531
- period: PERIOD
1532
- };
1533
- }
1534
-
1535
1594
  // src/index.ts
1536
1595
  function createSarmal(canvas, curveDef, options) {
1537
1596
  const { trailLength, ...rendererOpts } = options ?? {};
@@ -1541,6 +1600,7 @@ function createSarmal(canvas, curveDef, options) {
1541
1600
 
1542
1601
  exports.artemis2 = artemis2;
1543
1602
  exports.astroid = astroid;
1603
+ exports.computeBoundaries = computeBoundaries;
1544
1604
  exports.createEngine = createEngine;
1545
1605
  exports.createRenderer = createRenderer;
1546
1606
  exports.createSVGRenderer = createSVGRenderer;
@@ -1559,4 +1619,4 @@ exports.palettes = palettes;
1559
1619
  exports.rose3 = rose3;
1560
1620
  exports.rose5 = rose5;
1561
1621
  //# sourceMappingURL=index.cjs.map
1562
- //# sourceMappingURL=index.cjs.map
1622
+ //# sourceMappingURL=index.cjs.map