@sarmal/core 0.10.0 → 0.13.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 (63) hide show
  1. package/dist/auto-init.cjs +189 -94
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.d.cts +1 -2
  4. package/dist/auto-init.d.ts +1 -2
  5. package/dist/auto-init.js +188 -93
  6. package/dist/auto-init.js.map +1 -1
  7. package/dist/curves/artemis2.cjs +10 -7
  8. package/dist/curves/artemis2.d.cts +1 -1
  9. package/dist/curves/artemis2.d.ts +1 -1
  10. package/dist/curves/artemis2.js +9 -6
  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 +32 -28
  28. package/dist/curves/index.cjs.map +1 -1
  29. package/dist/curves/index.d.cts +25 -13
  30. package/dist/curves/index.d.ts +25 -13
  31. package/dist/curves/index.js +44 -28
  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/index.cjs +216 -108
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.d.cts +69 -34
  56. package/dist/index.d.ts +69 -34
  57. package/dist/index.js +233 -108
  58. package/dist/index.js.map +1 -1
  59. package/dist/types-BW0bpL1Z.d.cts +290 -0
  60. package/dist/types-BW0bpL1Z.d.ts +290 -0
  61. package/package.json +1 -1
  62. package/dist/types-DcyISvnH.d.cts +0 -230
  63. package/dist/types-DcyISvnH.d.ts +0 -230
package/dist/index.js CHANGED
@@ -61,22 +61,24 @@ 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);
74
74
  const trail = new CircularBuffer(trailLength);
75
75
  let t = 0;
76
76
  let actualTime = 0;
77
+ let userSpeedOverride = null;
77
78
  let morphCurveB = null;
78
79
  let _morphAlpha = null;
79
80
  let _morphStrategy = "normalized";
81
+ let _speedTransition = null;
80
82
  function sampleSkeleton(c, sampleT) {
81
83
  if (c.skeletonFn) {
82
84
  return c.skeletonFn(sampleT);
@@ -88,15 +90,25 @@ function createEngine(curveDef, trailLength = 120) {
88
90
  }
89
91
  return {
90
92
  tick(deltaTime) {
91
- let effectiveSpeed = curve.speed;
93
+ if (_speedTransition !== null) {
94
+ _speedTransition.elapsed += deltaTime * 1e3;
95
+ const alpha = Math.min(_speedTransition.elapsed / _speedTransition.duration, 1);
96
+ userSpeedOverride = lerp(_speedTransition.from, _speedTransition.to, alpha);
97
+ if (alpha >= 1) {
98
+ userSpeedOverride = _speedTransition.to;
99
+ _speedTransition.resolve();
100
+ _speedTransition = null;
101
+ }
102
+ }
103
+ let effectiveSpeed = userSpeedOverride ?? curve.speed;
92
104
  if (morphCurveB !== null && _morphAlpha !== null) {
93
- effectiveSpeed = lerp(curve.speed, morphCurveB.speed, _morphAlpha);
105
+ effectiveSpeed = lerp(effectiveSpeed, morphCurveB.speed, _morphAlpha);
94
106
  }
95
107
  t = (t + effectiveSpeed * deltaTime) % curve.period;
96
108
  actualTime += deltaTime;
97
109
  if (morphCurveB !== null && _morphAlpha !== null) {
98
110
  const a = curve.fn(t, actualTime, EMPTY_PARAMS);
99
- const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
111
+ const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
100
112
  const b = morphCurveB.fn(tB, actualTime, EMPTY_PARAMS);
101
113
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
102
114
  } else {
@@ -120,14 +132,14 @@ function createEngine(curveDef, trailLength = 120) {
120
132
  trail.clear();
121
133
  },
122
134
  jump(newT, { clearTrail = false } = {}) {
123
- t = (newT % curve.period + curve.period) % curve.period;
135
+ t = ((newT % curve.period) + curve.period) % curve.period;
124
136
  if (clearTrail) {
125
137
  trail.clear();
126
138
  }
127
139
  },
128
140
  seek(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
129
141
  const advance = curve.speed * step;
130
- const target = (targetT % curve.period + curve.period) % curve.period;
142
+ const target = ((targetT % curve.period) + curve.period) % curve.period;
131
143
  const targetTime = target / curve.speed;
132
144
  t = target;
133
145
  actualTime = targetTime;
@@ -136,7 +148,7 @@ function createEngine(curveDef, trailLength = 120) {
136
148
  const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
137
149
  for (let i = count - 1; i >= 0; i--) {
138
150
  const sampleT = target - i * advance;
139
- const wrappedT = (sampleT % curve.period + curve.period) % curve.period;
151
+ const wrappedT = ((sampleT % curve.period) + curve.period) % curve.period;
140
152
  const time = targetTime - i * step;
141
153
  const point = curve.fn(wrappedT, time, EMPTY_PARAMS);
142
154
  trail.push(point.x, point.y);
@@ -153,13 +165,16 @@ function createEngine(curveDef, trailLength = 120) {
153
165
  ...frozenB,
154
166
  fn: (sampleT, time, params) => {
155
167
  const a = frozenA.fn(sampleT, time, params);
156
- const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
168
+ const tB =
169
+ frozenStrategy === "normalized"
170
+ ? (sampleT / frozenA.period) * frozenB.period
171
+ : sampleT;
157
172
  const b = frozenB.fn(tB, time, params);
158
173
  return {
159
174
  x: a.x + (b.x - a.x) * frozenAlpha,
160
- y: a.y + (b.y - a.y) * frozenAlpha
175
+ y: a.y + (b.y - a.y) * frozenAlpha,
161
176
  };
162
- }
177
+ },
163
178
  };
164
179
  }
165
180
  _morphStrategy = strategy;
@@ -172,7 +187,7 @@ function createEngine(curveDef, trailLength = 120) {
172
187
  completeMorph() {
173
188
  if (morphCurveB !== null) {
174
189
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
175
- t = t / curve.period * morphCurveB.period;
190
+ t = (t / curve.period) * morphCurveB.period;
176
191
  }
177
192
  curve = morphCurveB;
178
193
  }
@@ -184,23 +199,64 @@ function createEngine(curveDef, trailLength = 120) {
184
199
  const points = new Array(steps);
185
200
  if (morphCurveB !== null && _morphAlpha !== null) {
186
201
  for (let i = 0; i < steps; i++) {
187
- const sampleT = i / (steps - 1) * curve.period;
202
+ const sampleT = (i / (steps - 1)) * curve.period;
188
203
  const a = sampleSkeleton(curve, sampleT);
189
- const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
204
+ const tB =
205
+ _morphStrategy === "normalized"
206
+ ? (sampleT / curve.period) * morphCurveB.period
207
+ : sampleT;
190
208
  const b = sampleSkeleton(morphCurveB, tB);
191
209
  points[i] = {
192
210
  x: a.x + (b.x - a.x) * _morphAlpha,
193
- y: a.y + (b.y - a.y) * _morphAlpha
211
+ y: a.y + (b.y - a.y) * _morphAlpha,
194
212
  };
195
213
  }
196
214
  return points;
197
215
  }
198
216
  for (let i = 0; i < steps; i++) {
199
- const sampleT = i / (steps - 1) * curve.period;
217
+ const sampleT = (i / (steps - 1)) * curve.period;
200
218
  points[i] = sampleSkeleton(curve, sampleT);
201
219
  }
202
220
  return points;
203
- }
221
+ },
222
+ setSpeed(speed) {
223
+ if (!Number.isFinite(speed)) {
224
+ throw new Error("speed must be a finite number");
225
+ }
226
+ if (_speedTransition !== null) {
227
+ _speedTransition.reject(new Error("Speed transition cancelled"));
228
+ _speedTransition = null;
229
+ }
230
+ userSpeedOverride = speed;
231
+ },
232
+ getSpeed() {
233
+ return userSpeedOverride ?? curve.speed;
234
+ },
235
+ resetSpeed() {
236
+ userSpeedOverride = null;
237
+ },
238
+ setSpeedOver(speed, duration) {
239
+ if (!Number.isFinite(speed)) {
240
+ throw new Error("speed must be a finite number");
241
+ }
242
+ if (!Number.isFinite(duration) || duration <= 0) {
243
+ throw new Error("duration must be a finite number greater than 0");
244
+ }
245
+ if (_speedTransition !== null) {
246
+ _speedTransition.reject(new Error("Speed transition cancelled"));
247
+ _speedTransition = null;
248
+ }
249
+ const from = userSpeedOverride ?? curve.speed;
250
+ return new Promise((resolve, reject) => {
251
+ _speedTransition = { from, to: speed, elapsed: 0, duration, resolve, reject };
252
+ });
253
+ },
254
+ cancelSpeedTransition() {
255
+ if (_speedTransition !== null) {
256
+ _speedTransition.reject(new Error("Speed transition cancelled"));
257
+ _speedTransition = null;
258
+ }
259
+ },
204
260
  };
205
261
  }
206
262
 
@@ -208,6 +264,7 @@ function createEngine(curveDef, trailLength = 120) {
208
264
  var DEFAULT_MORPH_DURATION_MS = 300;
209
265
  var DEFAULT_SKELETON_OPACITY = 0.15;
210
266
  var FIT_PADDING = 0.1;
267
+ var FIT_PADDING_MIN = 4;
211
268
  var TRAIL_FADE_CURVE = 1.5;
212
269
  var TRAIL_MAX_OPACITY = 0.88;
213
270
  var TRAIL_MIN_WIDTH = 0.5;
@@ -262,13 +319,16 @@ function computeTrailQuad(trail, i, trailCount, toX, toY) {
262
319
  r1x: nx - n1.x * w1,
263
320
  r1y: ny - n1.y * w1,
264
321
  opacity,
265
- progress
322
+ progress,
266
323
  };
267
324
  }
268
325
  function computeBoundaries(pts, logicalWidth, logicalHeight) {
269
326
  if (pts.length === 0) return null;
270
327
  const first = pts[0];
271
- let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
328
+ let minX = first.x,
329
+ maxX = first.x,
330
+ minY = first.y,
331
+ maxY = first.y;
272
332
  for (const p of pts) {
273
333
  if (p.x < minX) {
274
334
  minX = p.x;
@@ -287,21 +347,37 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
287
347
  const h = maxY - minY;
288
348
  if (w === 0 && h === 0) {
289
349
  throw new Error(
290
- "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
350
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
291
351
  );
292
352
  }
293
- const scaleX = logicalWidth / (w * (1 + FIT_PADDING * 2));
294
- const scaleY = logicalHeight / (h * (1 + FIT_PADDING * 2));
295
- const scale = Math.min(scaleX, scaleY);
353
+ const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
354
+ const scaleYProportional = logicalHeight / (h * (1 + FIT_PADDING * 2));
355
+ const scaleXMinPadding = (logicalWidth - FIT_PADDING_MIN * 2) / w;
356
+ const scaleYMinPadding = (logicalHeight - FIT_PADDING_MIN * 2) / h;
357
+ const scale = Math.min(
358
+ scaleXProportional,
359
+ scaleYProportional,
360
+ scaleXMinPadding,
361
+ scaleYMinPadding,
362
+ );
296
363
  return {
297
364
  scale,
298
365
  offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
299
- offsetY: (logicalHeight - h * scale) / 2 - minY * scale
366
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
367
+ };
368
+ }
369
+ function enginePassthroughs(engine) {
370
+ return {
371
+ jump: engine.jump,
372
+ seek: engine.seek,
373
+ setSpeed: engine.setSpeed,
374
+ getSpeed: engine.getSpeed,
375
+ resetSpeed: engine.resetSpeed,
376
+ setSpeedOver: engine.setSpeedOver,
300
377
  };
301
378
  }
302
379
 
303
380
  // src/renderer.ts
304
- var DEFAULT_HEAD_RADIUS = 4;
305
381
  var DEFAULT_SKELETON_COLOR = "#ffffff";
306
382
  var GRADIENT = {
307
383
  bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
@@ -309,7 +385,7 @@ var GRADIENT = {
309
385
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
310
386
  ice: ["#1e3a8a", "#67e8f9"],
311
387
  fire: ["#7f1d1d", "#fbbf24"],
312
- forest: ["#14532d", "#86efac"]
388
+ forest: ["#14532d", "#86efac"],
313
389
  };
314
390
  var PRESETS = {
315
391
  bard: GRADIENT.bard,
@@ -317,16 +393,16 @@ var PRESETS = {
317
393
  ocean: GRADIENT.ocean,
318
394
  ice: GRADIENT.ice,
319
395
  fire: GRADIENT.fire,
320
- forest: GRADIENT.forest
396
+ forest: GRADIENT.forest,
321
397
  };
322
398
  function hexToRgb(hex) {
323
399
  const n = parseInt(hex.slice(1), 16);
324
- return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
400
+ return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
325
401
  }
326
402
  var lerpRgb = (a, b, t) => ({
327
403
  r: Math.round(a.r + (b.r - a.r) * t),
328
404
  g: Math.round(a.g + (b.g - a.g) * t),
329
- b: Math.round(a.b + (b.b - a.b) * t)
405
+ b: Math.round(a.b + (b.b - a.b) * t),
330
406
  });
331
407
  function getPaletteColor(palette, position, timeOffset = 0) {
332
408
  if (palette.length === 0) return { r: 255, g: 255, b: 255 };
@@ -346,7 +422,7 @@ function resolvePalette(palette, trailStyle) {
346
422
  }
347
423
  function hexToRgbComponents(hex) {
348
424
  const n = parseInt(hex.slice(1), 16);
349
- return `${n >> 16},${n >> 8 & 255},${n & 255}`;
425
+ return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
350
426
  }
351
427
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
352
428
  target.style.width = `${logicalWidth}px`;
@@ -361,14 +437,21 @@ function createRenderer(options) {
361
437
  }
362
438
  const ctx = canvas.getContext("2d");
363
439
  const engine = options.engine;
440
+ const trailStyle = options.trailStyle ?? "default";
441
+ const trailColor = options.trailColor ?? "#ffffff";
442
+ const palette = resolvePalette(options.palette, trailStyle);
443
+ function defaultHeadColor() {
444
+ if (trailStyle !== "default") {
445
+ const { r, g, b } = getPaletteColor(palette, 1);
446
+ return `rgb(${r},${g},${b})`;
447
+ }
448
+ return trailColor;
449
+ }
364
450
  const opts = {
365
451
  skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
366
- trailColor: options.trailColor ?? "#ffffff",
367
- headColor: options.headColor ?? "#ffffff",
368
- headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS
452
+ trailColor,
453
+ headColor: options.headColor ?? defaultHeadColor(),
369
454
  };
370
- const trailStyle = options.trailStyle ?? "default";
371
- const palette = resolvePalette(options.palette, trailStyle);
372
455
  const trailRgb = hexToRgbComponents(opts.trailColor);
373
456
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
374
457
  function setupCanvas() {
@@ -468,7 +551,7 @@ function createRenderer(options) {
468
551
  i,
469
552
  trailCount,
470
553
  toX,
471
- toY
554
+ toY,
472
555
  );
473
556
  if (trailStyle === "default") {
474
557
  ctx.fillStyle = `rgba(${trailRgb},${opacity})`;
@@ -492,15 +575,14 @@ function createRenderer(options) {
492
575
  }
493
576
  const x = head.x * scale + offsetX;
494
577
  const y = head.y * scale + offsetY;
578
+ const r =
579
+ options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(logicalWidth, logicalHeight) / 160));
495
580
  ctx.fillStyle = opts.headColor;
496
581
  ctx.beginPath();
497
- ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);
582
+ ctx.arc(x, y, r, 0, Math.PI * 2);
498
583
  ctx.fill();
499
584
  }
500
- function render() {
501
- const now = performance.now();
502
- const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
503
- lastTime = now;
585
+ function renderFrame(deltaTime) {
504
586
  if (trailStyle === "gradient-animated") {
505
587
  gradientAnimTime += deltaTime * 1e3;
506
588
  }
@@ -536,27 +618,39 @@ function createRenderer(options) {
536
618
  drawSkeleton();
537
619
  drawTrail();
538
620
  drawHead();
539
- animationId = requestAnimationFrame(render);
621
+ }
622
+ function loop() {
623
+ const now = performance.now();
624
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
625
+ lastTime = now;
626
+ renderFrame(deltaTime);
627
+ animationId = requestAnimationFrame(loop);
540
628
  }
541
629
  skeleton = engine.getSarmalSkeleton();
542
630
  calculateBoundaries();
543
631
  if (!engine.isLiveSkeleton) {
544
632
  buildSkeletonCanvas();
545
633
  }
546
- return {
547
- start() {
634
+ if (options.initialT !== void 0) {
635
+ engine.seek(options.initialT);
636
+ }
637
+ renderFrame(0);
638
+ const shouldAutoStart = options.autoStart !== false;
639
+ const instance = {
640
+ play() {
548
641
  if (animationId !== null) {
549
642
  return;
550
643
  }
551
644
  lastTime = performance.now();
552
- render();
645
+ loop();
553
646
  },
554
- stop() {
647
+ pause() {
555
648
  if (animationId === null) {
556
649
  return;
557
650
  }
558
651
  cancelAnimationFrame(animationId);
559
652
  animationId = null;
653
+ engine.cancelSpeedTransition();
560
654
  },
561
655
  reset() {
562
656
  engine.reset();
@@ -569,12 +663,7 @@ function createRenderer(options) {
569
663
  animationId = null;
570
664
  }
571
665
  },
572
- jump(t, options2) {
573
- engine.jump(t, options2);
574
- },
575
- seek(t, options2) {
576
- engine.seek(t, options2);
577
- },
666
+ ...enginePassthroughs(engine),
578
667
  morphTo(target, options2) {
579
668
  if (morphResolve !== null) {
580
669
  engine.completeMorph();
@@ -588,8 +677,12 @@ function createRenderer(options) {
588
677
  return new Promise((resolve) => {
589
678
  morphResolve = resolve;
590
679
  });
591
- }
680
+ },
592
681
  };
682
+ if (shouldAutoStart) {
683
+ instance.play();
684
+ }
685
+ return instance;
593
686
  }
594
687
 
595
688
  // src/renderer-svg.ts
@@ -610,7 +703,7 @@ function sampleCurveSkeleton(curveDef) {
610
703
  const samples = Math.ceil(period * 50);
611
704
  const pts = Array.from({ length: samples });
612
705
  for (let i = 0; i < samples; i++) {
613
- const t = i / (samples - 1) * period;
706
+ const t = (i / (samples - 1)) * period;
614
707
  pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
615
708
  }
616
709
  return pts;
@@ -620,16 +713,18 @@ function el(tag) {
620
713
  }
621
714
  function createSVGRenderer(options) {
622
715
  const { container, engine } = options;
716
+ const trailColor = options.trailColor ?? "#ffffff";
623
717
  const opts = {
624
718
  skeletonColor: options.skeletonColor ?? "#ffffff",
625
- trailColor: options.trailColor ?? "#ffffff",
626
- headColor: options.headColor ?? "#ffffff",
627
- headRadius: options.headRadius ?? 4,
628
- ariaLabel: options.ariaLabel ?? "Loading"
719
+ trailColor,
720
+ headColor: options.headColor ?? trailColor,
721
+ ariaLabel: options.ariaLabel ?? "Loading",
629
722
  };
630
723
  const rect = container.getBoundingClientRect();
631
724
  const width = rect.width || 200;
632
725
  const height = rect.height || 200;
726
+ const headRadius =
727
+ options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(width, height) / 160));
633
728
  const svg = el("svg");
634
729
  svg.setAttribute("width", String(width));
635
730
  svg.setAttribute("height", String(height));
@@ -668,7 +763,7 @@ function createSVGRenderer(options) {
668
763
  }
669
764
  const headCircle = el("circle");
670
765
  headCircle.setAttribute("fill", opts.headColor);
671
- headCircle.setAttribute("r", String(opts.headRadius));
766
+ headCircle.setAttribute("r", String(headRadius));
672
767
  svg.appendChild(headCircle);
673
768
  container.appendChild(svg);
674
769
  let scale = 1;
@@ -709,7 +804,7 @@ function createSVGRenderer(options) {
709
804
  i,
710
805
  trailCount,
711
806
  px,
712
- py
807
+ py,
713
808
  );
714
809
  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`;
715
810
  trailPaths[i].setAttribute("d", d);
@@ -731,24 +826,22 @@ function createSVGRenderer(options) {
731
826
  }
732
827
  let animationId = null;
733
828
  let lastTime = 0;
734
- const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
829
+ const prefersReducedMotion =
830
+ typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
735
831
  let morphResolve = null;
736
832
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
737
833
  let morphTarget = null;
738
834
  let morphAlpha = 0;
739
- function renderFrame() {
740
- const now = performance.now();
741
- const dt = Math.min((now - lastTime) / 1e3, 1 / 30);
742
- lastTime = now;
835
+ function renderFrame(deltaTime) {
743
836
  if (engine.morphAlpha !== null) {
744
- morphAlpha = Math.min(1, morphAlpha + dt / (morphDurationMs / 1e3));
837
+ morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
745
838
  engine.setMorphAlpha(morphAlpha);
746
839
  if (morphPathABuilt) {
747
840
  skeletonPathA.setAttribute("d", morphPathABuilt);
748
841
  skeletonPathA.setAttribute("visibility", "visible");
749
842
  skeletonPathA.setAttribute(
750
843
  "stroke-opacity",
751
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
844
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
752
845
  );
753
846
  }
754
847
  if (morphPathBBuilt) {
@@ -771,7 +864,7 @@ function createSVGRenderer(options) {
771
864
  updateSkeleton(newSkeleton);
772
865
  }
773
866
  }
774
- const trail = engine.tick(dt);
867
+ const trail = engine.tick(deltaTime);
775
868
  const trailCount = engine.trailCount;
776
869
  if (engine.isLiveSkeleton && engine.morphAlpha === null) {
777
870
  const liveSkeleton = engine.getSarmalSkeleton();
@@ -780,24 +873,36 @@ function createSVGRenderer(options) {
780
873
  }
781
874
  updateTrail(trail, trailCount);
782
875
  updateHead(trail, trailCount);
876
+ }
877
+ function loop() {
878
+ const now = performance.now();
879
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
880
+ lastTime = now;
881
+ renderFrame(deltaTime);
783
882
  if (!prefersReducedMotion) {
784
- animationId = requestAnimationFrame(renderFrame);
883
+ animationId = requestAnimationFrame(loop);
785
884
  }
786
885
  }
787
- return {
788
- start() {
886
+ if (options.initialT !== void 0) {
887
+ engine.seek(options.initialT);
888
+ }
889
+ renderFrame(0);
890
+ const shouldAutoStart = options.autoStart !== false;
891
+ const instance = {
892
+ play() {
789
893
  if (animationId !== null) {
790
894
  return;
791
895
  }
792
896
  lastTime = performance.now();
793
- renderFrame();
897
+ loop();
794
898
  },
795
- stop() {
899
+ pause() {
796
900
  if (animationId === null) {
797
901
  return;
798
902
  }
799
903
  cancelAnimationFrame(animationId);
800
904
  animationId = null;
905
+ engine.cancelSpeedTransition();
801
906
  },
802
907
  reset() {
803
908
  engine.reset();
@@ -809,12 +914,7 @@ function createSVGRenderer(options) {
809
914
  }
810
915
  svg.remove();
811
916
  },
812
- jump(t, options2) {
813
- engine.jump(t, options2);
814
- },
815
- seek(t, options2) {
816
- engine.seek(t, options2);
817
- },
917
+ ...enginePassthroughs(engine),
818
918
  morphTo(target, options2) {
819
919
  if (morphResolve !== null) {
820
920
  engine.completeMorph();
@@ -837,8 +937,12 @@ function createSVGRenderer(options) {
837
937
  return new Promise((resolve) => {
838
938
  morphResolve = resolve;
839
939
  });
840
- }
940
+ },
841
941
  };
942
+ if (shouldAutoStart) {
943
+ instance.play();
944
+ }
945
+ return instance;
842
946
  }
843
947
  function createSarmalSVG(container, curveDef, options) {
844
948
  const { trailLength, ...rendererOpts } = options ?? {};
@@ -849,19 +953,22 @@ function createSarmalSVG(container, curveDef, options) {
849
953
  // src/curves/artemis2.ts
850
954
  var TWO_PI2 = Math.PI * 2;
851
955
  function artemis2Fn(t, _time, _params) {
852
- const a = 0.35, b = 0.15, ox = 0.175;
853
- const s = Math.sin(t), c = Math.cos(t);
956
+ const a = 0.35,
957
+ b = 0.15,
958
+ ox = 0.175;
959
+ const s = Math.sin(t),
960
+ c = Math.cos(t);
854
961
  const denom = 1 + s * s;
855
962
  return {
856
- x: c * (1 + a * c) / denom - ox,
857
- y: s * c * (1 + b * c) / denom
963
+ x: (c * (1 + a * c)) / denom - ox,
964
+ y: (s * c * (1 + b * c)) / denom,
858
965
  };
859
966
  }
860
967
  var artemis2 = {
861
968
  name: "Artemis II",
862
969
  fn: artemis2Fn,
863
970
  period: TWO_PI2,
864
- speed: 0.7
971
+ speed: 0.7,
865
972
  };
866
973
 
867
974
  // src/curves/astroid.ts
@@ -871,14 +978,14 @@ function astroidFn(t, _time, _params) {
871
978
  const s = Math.sin(t);
872
979
  return {
873
980
  x: c * c * c,
874
- y: s * s * s
981
+ y: s * s * s,
875
982
  };
876
983
  }
877
984
  var astroid = {
878
985
  name: "Astroid",
879
986
  fn: astroidFn,
880
987
  period: TWO_PI3,
881
- speed: 1.1
988
+ speed: 1.1,
882
989
  };
883
990
 
884
991
  // src/curves/deltoid.ts
@@ -886,14 +993,14 @@ var TWO_PI4 = Math.PI * 2;
886
993
  function deltoidFn(t, _time, _params) {
887
994
  return {
888
995
  x: 2 * Math.cos(t) + Math.cos(2 * t),
889
- y: 2 * Math.sin(t) - Math.sin(2 * t)
996
+ y: 2 * Math.sin(t) - Math.sin(2 * t),
890
997
  };
891
998
  }
892
999
  var deltoid = {
893
1000
  name: "Deltoid",
894
1001
  fn: deltoidFn,
895
1002
  period: TWO_PI4,
896
- speed: 0.9
1003
+ speed: 0.9,
897
1004
  };
898
1005
 
899
1006
  // src/curves/epicycloid3.ts
@@ -901,14 +1008,14 @@ var TWO_PI5 = Math.PI * 2;
901
1008
  function epicycloid3Fn(t, _time, _params) {
902
1009
  return {
903
1010
  x: 4 * Math.cos(t) - Math.cos(4 * t),
904
- y: 4 * Math.sin(t) - Math.sin(4 * t)
1011
+ y: 4 * Math.sin(t) - Math.sin(4 * t),
905
1012
  };
906
1013
  }
907
1014
  var epicycloid3 = {
908
1015
  name: "Epicycloid (n=3)",
909
1016
  fn: epicycloid3Fn,
910
1017
  period: TWO_PI5,
911
- speed: 0.75
1018
+ speed: 0.75,
912
1019
  };
913
1020
 
914
1021
  // src/curves/epitrochoid7.ts
@@ -917,14 +1024,14 @@ function epitrochoid7Fn(t, _time, _params) {
917
1024
  const d = 1 + 0.55 * Math.sin(t * 0.5);
918
1025
  return {
919
1026
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
920
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1027
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
921
1028
  };
922
1029
  }
923
1030
  function epitrochoid7SkeletonFn(t) {
924
1031
  const d = 1.275;
925
1032
  return {
926
1033
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
927
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1034
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
928
1035
  };
929
1036
  }
930
1037
  var epitrochoid7 = {
@@ -932,7 +1039,7 @@ var epitrochoid7 = {
932
1039
  fn: epitrochoid7Fn,
933
1040
  period: TWO_PI6,
934
1041
  speed: 1.4,
935
- skeletonFn: epitrochoid7SkeletonFn
1042
+ skeletonFn: epitrochoid7SkeletonFn,
936
1043
  };
937
1044
 
938
1045
  // src/curves/lissajous32.ts
@@ -941,7 +1048,7 @@ function lissajous32Fn(t, time, _params) {
941
1048
  const phi = time * 0.45;
942
1049
  return {
943
1050
  x: Math.sin(3 * t + phi),
944
- y: Math.sin(2 * t)
1051
+ y: Math.sin(2 * t),
945
1052
  };
946
1053
  }
947
1054
  var lissajous32 = {
@@ -949,7 +1056,7 @@ var lissajous32 = {
949
1056
  fn: lissajous32Fn,
950
1057
  period: TWO_PI7,
951
1058
  speed: 2,
952
- skeleton: "live"
1059
+ skeleton: "live",
953
1060
  };
954
1061
 
955
1062
  // src/curves/lissajous43.ts
@@ -958,7 +1065,7 @@ function lissajous43Fn(t, time, _params) {
958
1065
  const phi = time * 0.38;
959
1066
  return {
960
1067
  x: Math.sin(4 * t + phi),
961
- y: Math.sin(3 * t)
1068
+ y: Math.sin(3 * t),
962
1069
  };
963
1070
  }
964
1071
  var lissajous43 = {
@@ -966,17 +1073,18 @@ var lissajous43 = {
966
1073
  fn: lissajous43Fn,
967
1074
  period: TWO_PI8,
968
1075
  speed: 1.8,
969
- skeleton: "live"
1076
+ skeleton: "live",
970
1077
  };
971
1078
 
972
1079
  // src/curves/lame.ts
973
1080
  var TWO_PI9 = Math.PI * 2;
974
1081
  function lameFn(t, time, _params) {
975
1082
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
976
- const c = Math.cos(t), s = Math.sin(t);
1083
+ const c = Math.cos(t),
1084
+ s = Math.sin(t);
977
1085
  return {
978
1086
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
979
- y: Math.sign(s) * Math.pow(Math.abs(s), p)
1087
+ y: Math.sign(s) * Math.pow(Math.abs(s), p),
980
1088
  };
981
1089
  }
982
1090
  var lame = {
@@ -984,7 +1092,7 @@ var lame = {
984
1092
  fn: lameFn,
985
1093
  period: TWO_PI9,
986
1094
  speed: 1,
987
- skeleton: "live"
1095
+ skeleton: "live",
988
1096
  };
989
1097
 
990
1098
  // src/curves/rose3.ts
@@ -993,14 +1101,14 @@ function rose3Fn(t, _time, _params) {
993
1101
  const r = Math.cos(3 * t);
994
1102
  return {
995
1103
  x: r * Math.cos(t),
996
- y: r * Math.sin(t)
1104
+ y: r * Math.sin(t),
997
1105
  };
998
1106
  }
999
1107
  var rose3 = {
1000
1108
  name: "Rose (n=3)",
1001
1109
  fn: rose3Fn,
1002
1110
  period: TWO_PI10,
1003
- speed: 1.15
1111
+ speed: 1.15,
1004
1112
  };
1005
1113
 
1006
1114
  // src/curves/rose5.ts
@@ -1009,14 +1117,14 @@ function rose5Fn(t, _time, _params) {
1009
1117
  const r = Math.cos(5 * t);
1010
1118
  return {
1011
1119
  x: r * Math.cos(t),
1012
- y: r * Math.sin(t)
1120
+ y: r * Math.sin(t),
1013
1121
  };
1014
1122
  }
1015
1123
  var rose5 = {
1016
1124
  name: "Rose (n=5)",
1017
1125
  fn: rose5Fn,
1018
1126
  period: TWO_PI11,
1019
- speed: 1
1127
+ speed: 1,
1020
1128
  };
1021
1129
 
1022
1130
  // src/curves/index.ts
@@ -1030,7 +1138,7 @@ var curves = {
1030
1138
  lissajous32,
1031
1139
  lissajous43,
1032
1140
  epicycloid3,
1033
- lame
1141
+ lame,
1034
1142
  };
1035
1143
 
1036
1144
  // src/index.ts
@@ -1040,6 +1148,23 @@ function createSarmal(canvas, curveDef, options) {
1040
1148
  return createRenderer({ canvas, engine, ...rendererOpts });
1041
1149
  }
1042
1150
 
1043
- export { artemis2, astroid, createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves, deltoid, epicycloid3, epitrochoid7, lame, lissajous32, lissajous43, rose3, rose5 };
1151
+ export {
1152
+ artemis2,
1153
+ astroid,
1154
+ createEngine,
1155
+ createRenderer,
1156
+ createSVGRenderer,
1157
+ createSarmal,
1158
+ createSarmalSVG,
1159
+ curves,
1160
+ deltoid,
1161
+ epicycloid3,
1162
+ epitrochoid7,
1163
+ lame,
1164
+ lissajous32,
1165
+ lissajous43,
1166
+ rose3,
1167
+ rose5,
1168
+ };
1169
+ //# sourceMappingURL=index.js.map
1044
1170
  //# sourceMappingURL=index.js.map
1045
- //# sourceMappingURL=index.js.map