@sarmal/core 0.7.1 → 0.8.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.
package/dist/index.js CHANGED
@@ -49,7 +49,7 @@ function resolveCurve(curveDef) {
49
49
  period: curveDef.period ?? TWO_PI,
50
50
  speed: curveDef.speed ?? 1,
51
51
  skeleton: curveDef.skeleton,
52
- skeletonFn: curveDef.skeletonFn
52
+ skeletonFn: curveDef.skeletonFn,
53
53
  };
54
54
  }
55
55
  function createEngine(curveDef, trailLength = 120) {
@@ -75,7 +75,7 @@ function createEngine(curveDef, trailLength = 120) {
75
75
  actualTime += deltaTime;
76
76
  if (morphCurveB !== null && _morphAlpha !== null) {
77
77
  const a = curve.fn(t, actualTime, {});
78
- const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
78
+ const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
79
79
  const b = morphCurveB.fn(tB, actualTime, {});
80
80
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
81
81
  } else {
@@ -99,14 +99,14 @@ function createEngine(curveDef, trailLength = 120) {
99
99
  trail.clear();
100
100
  },
101
101
  seek(newT, { clearTrail = false } = {}) {
102
- t = (newT % curve.period + curve.period) % curve.period;
102
+ t = ((newT % curve.period) + curve.period) % curve.period;
103
103
  if (clearTrail) {
104
104
  trail.clear();
105
105
  }
106
106
  },
107
107
  seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
108
108
  const advance = curve.speed * step;
109
- const target = (targetT % curve.period + curve.period) % curve.period;
109
+ const target = ((targetT % curve.period) + curve.period) % curve.period;
110
110
  const targetTime = target / curve.speed;
111
111
  t = target;
112
112
  actualTime = targetTime;
@@ -132,13 +132,16 @@ function createEngine(curveDef, trailLength = 120) {
132
132
  ...frozenB,
133
133
  fn: (sampleT, time, params) => {
134
134
  const a = frozenA.fn(sampleT, time, params);
135
- const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
135
+ const tB =
136
+ frozenStrategy === "normalized"
137
+ ? (sampleT / frozenA.period) * frozenB.period
138
+ : sampleT;
136
139
  const b = frozenB.fn(tB, time, params);
137
140
  return {
138
141
  x: a.x + (b.x - a.x) * frozenAlpha,
139
- y: a.y + (b.y - a.y) * frozenAlpha
142
+ y: a.y + (b.y - a.y) * frozenAlpha,
140
143
  };
141
- }
144
+ },
142
145
  };
143
146
  }
144
147
  _morphStrategy = strategy;
@@ -151,7 +154,7 @@ function createEngine(curveDef, trailLength = 120) {
151
154
  completeMorph() {
152
155
  if (morphCurveB !== null) {
153
156
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
154
- t = t / curve.period * morphCurveB.period;
157
+ t = (t / curve.period) * morphCurveB.period;
155
158
  }
156
159
  curve = morphCurveB;
157
160
  }
@@ -163,43 +166,74 @@ function createEngine(curveDef, trailLength = 120) {
163
166
  const points = new Array(steps);
164
167
  if (morphCurveB !== null && _morphAlpha !== null) {
165
168
  for (let i = 0; i < steps; i++) {
166
- const sampleT = i / (steps - 1) * curve.period;
169
+ const sampleT = (i / (steps - 1)) * curve.period;
167
170
  const a = sampleSkeleton(curve, sampleT);
168
- const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
171
+ const tB =
172
+ _morphStrategy === "normalized"
173
+ ? (sampleT / curve.period) * morphCurveB.period
174
+ : sampleT;
169
175
  const b = sampleSkeleton(morphCurveB, tB);
170
176
  points[i] = {
171
177
  x: a.x + (b.x - a.x) * _morphAlpha,
172
- y: a.y + (b.y - a.y) * _morphAlpha
178
+ y: a.y + (b.y - a.y) * _morphAlpha,
173
179
  };
174
180
  }
175
181
  return points;
176
182
  }
177
183
  for (let i = 0; i < steps; i++) {
178
- const sampleT = i / (steps - 1) * curve.period;
184
+ const sampleT = (i / (steps - 1)) * curve.period;
179
185
  points[i] = sampleSkeleton(curve, sampleT);
180
186
  }
181
187
  return points;
182
- }
188
+ },
183
189
  };
184
190
  }
185
191
 
186
192
  // src/renderer.ts
187
193
  var DEFAULT_MORPH_DURATION_MS = 300;
188
194
  var DEFAULT_HEAD_RADIUS = 4;
189
- var DEFAULT_GLOW_SIZE = 20;
190
195
  var DEFAULT_SKELETON_COLOR = "#ffffff";
191
196
  var DEFAULT_SKELETON_OPACITY = 0.15;
192
197
  var FIT_PADDING = 0.1;
193
- var TRAIL_BATCH_SIZE = 20;
194
198
  var TRAIL_FADE_CURVE = 1.5;
195
199
  var TRAIL_MAX_OPACITY = 0.88;
196
200
  var TRAIL_MIN_WIDTH = 0.5;
197
201
  var TRAIL_MAX_WIDTH = 2.5;
198
- var GLOW_INNER_EDGE = 0.4;
199
- var GLOW_FALLOFF_OPACITY = 0.53;
200
202
  function hexToRgbComponents(hex) {
201
203
  const n = parseInt(hex.slice(1), 16);
202
- return `${n >> 16},${n >> 8 & 255},${n & 255}`;
204
+ return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
205
+ }
206
+ function computeTangent(trail, i) {
207
+ const count = trail.length;
208
+ if (count < 2) {
209
+ return { x: 1, y: 0 };
210
+ }
211
+ if (i === 0) {
212
+ const dx2 = trail[1].x - trail[0].x;
213
+ const dy2 = trail[1].y - trail[0].y;
214
+ const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
215
+ return { x: dx2 / len2, y: dy2 / len2 };
216
+ }
217
+ if (i === count - 1) {
218
+ const dx2 = trail[count - 1].x - trail[count - 2].x;
219
+ const dy2 = trail[count - 1].y - trail[count - 2].y;
220
+ const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
221
+ return { x: dx2 / len2, y: dy2 / len2 };
222
+ }
223
+ const dx = trail[i + 1].x - trail[i - 1].x;
224
+ const dy = trail[i + 1].y - trail[i - 1].y;
225
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
226
+ return { x: dx / len, y: dy / len };
227
+ }
228
+ function computeNormal(trail, i) {
229
+ const tangent = computeTangent(trail, i);
230
+ return { x: -tangent.y, y: tangent.x };
231
+ }
232
+ function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
233
+ target.style.width = `${logicalWidth}px`;
234
+ target.style.height = `${logicalHeight}px`;
235
+ target.width = logicalWidth * dpr;
236
+ target.height = logicalHeight * dpr;
203
237
  }
204
238
  function createRenderer(options) {
205
239
  const canvas = options.canvas;
@@ -213,10 +247,19 @@ function createRenderer(options) {
213
247
  trailColor: options.trailColor ?? "#ffffff",
214
248
  headColor: options.headColor ?? "#ffffff",
215
249
  headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
216
- glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
217
250
  };
218
251
  const trailRgb = hexToRgbComponents(opts.trailColor);
219
- const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;
252
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
253
+ function setupCanvas() {
254
+ const rect = canvas.getBoundingClientRect();
255
+ const lw = rect.width || 200;
256
+ const lh = rect.height || 200;
257
+ applyDprSizing(canvas, lw, lh, dpr);
258
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
259
+ }
260
+ setupCanvas();
261
+ let logicalWidth = canvas.width / dpr;
262
+ let logicalHeight = canvas.height / dpr;
220
263
  let skeleton = [];
221
264
  let skeletonCanvas = null;
222
265
  let trail = [];
@@ -233,7 +276,10 @@ function createRenderer(options) {
233
276
  function computeBoundaries(pts) {
234
277
  if (pts.length === 0) return null;
235
278
  const first = pts[0];
236
- let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
279
+ let minX = first.x,
280
+ maxX = first.x,
281
+ minY = first.y,
282
+ maxY = first.y;
237
283
  for (const p of pts) {
238
284
  if (p.x < minX) minX = p.x;
239
285
  if (p.x > maxX) maxX = p.x;
@@ -242,17 +288,15 @@ function createRenderer(options) {
242
288
  }
243
289
  const width = maxX - minX;
244
290
  const height = maxY - minY;
245
- const canvasWidth = canvas.width;
246
- const canvasHeight = canvas.height;
247
- const scaleX = canvasWidth / (width * (1 + FIT_PADDING * 2));
248
- const scaleY = canvasHeight / (height * (1 + FIT_PADDING * 2));
291
+ const scaleX = logicalWidth / (width * (1 + FIT_PADDING * 2));
292
+ const scaleY = logicalHeight / (height * (1 + FIT_PADDING * 2));
249
293
  const s = Math.min(scaleX, scaleY);
250
294
  const boundsWidth = width * s;
251
295
  const boundsHeight = height * s;
252
296
  return {
253
297
  scale: s,
254
- offsetX: (canvasWidth - boundsWidth) / 2 - minX * s,
255
- offsetY: (canvasHeight - boundsHeight) / 2 - minY * s
298
+ offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
299
+ offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
256
300
  };
257
301
  }
258
302
  function calculateBoundaries() {
@@ -267,6 +311,7 @@ function createRenderer(options) {
267
311
  if (skeleton.length < 2) return;
268
312
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
269
313
  const skeletonCtx = skeletonCanvas.getContext("2d");
314
+ skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
270
315
  skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
271
316
  skeletonCtx.lineWidth = 1.5;
272
317
  skeletonCtx.beginPath();
@@ -312,32 +357,41 @@ function createRenderer(options) {
312
357
  }
313
358
  ctx.stroke();
314
359
  } else if (skeletonCanvas) {
315
- ctx.drawImage(skeletonCanvas, 0, 0);
360
+ ctx.drawImage(skeletonCanvas, 0, 0, logicalWidth, logicalHeight);
316
361
  }
317
362
  }
318
363
  function drawTrail() {
319
364
  if (trailCount < 2) {
320
365
  return;
321
366
  }
322
- ctx.lineJoin = "round";
323
- ctx.lineCap = "round";
324
- for (let batchIndex = 0; batchIndex < trailCount - 1; batchIndex += TRAIL_BATCH_SIZE) {
325
- const bEnd = Math.min(batchIndex + TRAIL_BATCH_SIZE, trailCount - 1);
326
- const progress = (batchIndex + bEnd) / 2 / (trailCount - 1);
367
+ for (let i = 0; i < trailCount - 1; i++) {
368
+ const progress = i / (trailCount - 1);
369
+ const nextProgress = (i + 1) / (trailCount - 1);
327
370
  const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
328
- const lineWidth = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
371
+ const width = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
372
+ const nextWidth = TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
373
+ const curr = trail[i];
374
+ const next = trail[i + 1];
375
+ const n0 = computeNormal(trail, i);
376
+ const n1 = computeNormal(trail, i + 1);
377
+ const halfW0 = width / 2;
378
+ const halfW1 = nextWidth / 2;
379
+ const l0x = curr.x * scale + offsetX + n0.x * halfW0;
380
+ const l0y = curr.y * scale + offsetY + n0.y * halfW0;
381
+ const r0x = curr.x * scale + offsetX - n0.x * halfW0;
382
+ const r0y = curr.y * scale + offsetY - n0.y * halfW0;
383
+ const l1x = next.x * scale + offsetX + n1.x * halfW1;
384
+ const l1y = next.y * scale + offsetY + n1.y * halfW1;
385
+ const r1x = next.x * scale + offsetX - n1.x * halfW1;
386
+ const r1y = next.y * scale + offsetY - n1.y * halfW1;
387
+ ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
329
388
  ctx.beginPath();
330
- for (let i = batchIndex; i <= bEnd; i++) {
331
- const point = trail[i];
332
- if (i === batchIndex) {
333
- ctx.moveTo(point.x * scale + offsetX, point.y * scale + offsetY);
334
- } else {
335
- ctx.lineTo(point.x * scale + offsetX, point.y * scale + offsetY);
336
- }
337
- }
338
- ctx.strokeStyle = `rgba(${trailRgb},${alpha})`;
339
- ctx.lineWidth = lineWidth;
340
- ctx.stroke();
389
+ ctx.moveTo(l0x, l0y);
390
+ ctx.lineTo(l1x, l1y);
391
+ ctx.lineTo(r1x, r1y);
392
+ ctx.lineTo(r0x, r0y);
393
+ ctx.closePath();
394
+ ctx.fill();
341
395
  }
342
396
  }
343
397
  function drawHead() {
@@ -346,14 +400,6 @@ function createRenderer(options) {
346
400
  }
347
401
  const x = head.x * scale + offsetX;
348
402
  const y = head.y * scale + offsetY;
349
- const gradient = ctx.createRadialGradient(x, y, 0, x, y, opts.glowSize);
350
- gradient.addColorStop(0, opts.headColor);
351
- gradient.addColorStop(GLOW_INNER_EDGE, headRgbFalloff);
352
- gradient.addColorStop(1, "transparent");
353
- ctx.fillStyle = gradient;
354
- ctx.beginPath();
355
- ctx.arc(x, y, opts.glowSize, 0, Math.PI * 2);
356
- ctx.fill();
357
403
  ctx.fillStyle = opts.headColor;
358
404
  ctx.beginPath();
359
405
  ctx.arc(x, y, opts.headRadius, 0, Math.PI * 2);
@@ -387,7 +433,7 @@ function createRenderer(options) {
387
433
  trail = engine.tick(deltaTime);
388
434
  trailCount = engine.trailCount;
389
435
  head = trailCount > 0 ? trail[trailCount - 1] : null;
390
- ctx.clearRect(0, 0, canvas.width, canvas.height);
436
+ ctx.clearRect(0, 0, logicalWidth, logicalHeight);
391
437
  if (engine.isLiveSkeleton && engine.morphAlpha === null) {
392
438
  skeleton = engine.getSarmalSkeleton();
393
439
  calculateBoundaries();
@@ -447,22 +493,19 @@ function createRenderer(options) {
447
493
  return new Promise((resolve) => {
448
494
  morphResolve = resolve;
449
495
  });
450
- }
496
+ },
451
497
  };
452
498
  }
453
499
 
454
500
  // src/renderer-svg.ts
455
501
  var DEFAULT_MORPH_DURATION_MS2 = 300;
456
- var TRAIL_BATCH_COUNT = 12;
502
+ var MAX_TRAIL_SEGMENTS = 200;
457
503
  var TRAIL_FADE_CURVE2 = 1.5;
458
504
  var TRAIL_MAX_OPACITY2 = 0.88;
459
505
  var TRAIL_MIN_WIDTH2 = 0.5;
460
506
  var TRAIL_MAX_WIDTH2 = 2.5;
461
507
  var DEFAULT_SKELETON_OPACITY2 = 0.15;
462
- var DEFAULT_GLOW_INNER_STOP = 0.4;
463
- var DEFAULT_GLOW_FALLOFF_OPACITY = 0.53;
464
508
  var FIT_PADDING2 = 0.1;
465
- var instanceCount = 0;
466
509
  function el(tag) {
467
510
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
468
511
  }
@@ -473,11 +516,8 @@ function createSVGRenderer(options) {
473
516
  trailColor: options.trailColor ?? "#ffffff",
474
517
  headColor: options.headColor ?? "#ffffff",
475
518
  headRadius: options.headRadius ?? 4,
476
- glowSize: options.glowSize ?? 20,
477
- ariaLabel: options.ariaLabel ?? "Loading"
519
+ ariaLabel: options.ariaLabel ?? "Loading",
478
520
  };
479
- const uid = ++instanceCount;
480
- const gradientId = `sarmal-glow-${uid}`;
481
521
  const rect = container.getBoundingClientRect();
482
522
  const width = rect.width || 200;
483
523
  const height = rect.height || 200;
@@ -490,27 +530,6 @@ function createSVGRenderer(options) {
490
530
  const titleEl = el("title");
491
531
  titleEl.textContent = opts.ariaLabel;
492
532
  svg.appendChild(titleEl);
493
- const defs = el("defs");
494
- const gradient = el("radialGradient");
495
- gradient.id = gradientId;
496
- gradient.setAttribute("cx", "50%");
497
- gradient.setAttribute("cy", "50%");
498
- gradient.setAttribute("r", "50%");
499
- const stop0 = el("stop");
500
- stop0.setAttribute("offset", "0%");
501
- stop0.setAttribute("stop-color", opts.headColor);
502
- stop0.setAttribute("stop-opacity", "1");
503
- const stopMid = el("stop");
504
- stopMid.setAttribute("offset", `${DEFAULT_GLOW_INNER_STOP * 100}%`);
505
- stopMid.setAttribute("stop-color", opts.headColor);
506
- stopMid.setAttribute("stop-opacity", String(DEFAULT_GLOW_FALLOFF_OPACITY));
507
- const stop1 = el("stop");
508
- stop1.setAttribute("offset", "100%");
509
- stop1.setAttribute("stop-color", opts.headColor);
510
- stop1.setAttribute("stop-opacity", "0");
511
- gradient.append(stop0, stopMid, stop1);
512
- defs.appendChild(gradient);
513
- svg.appendChild(defs);
514
533
  const skeletonPath = el("path");
515
534
  skeletonPath.setAttribute("fill", "none");
516
535
  skeletonPath.setAttribute("stroke", opts.skeletonColor);
@@ -532,19 +551,12 @@ function createSVGRenderer(options) {
532
551
  let morphPathABuilt = "";
533
552
  let morphPathBBuilt = "";
534
553
  const trailPaths = [];
535
- for (let i = 0; i < TRAIL_BATCH_COUNT; i++) {
554
+ for (let i = 0; i < MAX_TRAIL_SEGMENTS; i++) {
536
555
  const path = el("path");
537
- path.setAttribute("fill", "none");
538
- path.setAttribute("stroke", opts.trailColor);
539
- path.setAttribute("stroke-linecap", "round");
540
- path.setAttribute("stroke-linejoin", "round");
556
+ path.setAttribute("fill", opts.trailColor);
541
557
  svg.appendChild(path);
542
558
  trailPaths.push(path);
543
559
  }
544
- const glowCircle = el("circle");
545
- glowCircle.setAttribute("fill", `url(#${gradientId})`);
546
- glowCircle.setAttribute("r", String(opts.glowSize));
547
- svg.appendChild(glowCircle);
548
560
  const headCircle = el("circle");
549
561
  headCircle.setAttribute("fill", opts.headColor);
550
562
  headCircle.setAttribute("r", String(opts.headRadius));
@@ -558,7 +570,10 @@ function createSVGRenderer(options) {
558
570
  return;
559
571
  }
560
572
  const first = skeleton2[0];
561
- let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
573
+ let minX = first.x,
574
+ maxX = first.x,
575
+ minY = first.y,
576
+ maxY = first.y;
562
577
  for (const p of skeleton2) {
563
578
  if (p.x < minX) {
564
579
  minX = p.x;
@@ -611,24 +626,32 @@ function createSVGRenderer(options) {
611
626
  }
612
627
  return;
613
628
  }
614
- const batchSize = Math.ceil(trailCount / TRAIL_BATCH_COUNT);
615
- for (let b = 0; b < TRAIL_BATCH_COUNT; b++) {
616
- const start = b * batchSize;
617
- const end = Math.min(start + batchSize, trailCount - 1);
618
- if (start >= trailCount - 1) {
619
- trailPaths[b].setAttribute("d", "");
620
- continue;
621
- }
622
- const progress = (start + end) / 2 / (trailCount - 1);
629
+ for (let i = 0; i < trailCount - 1; i++) {
630
+ const progress = i / (trailCount - 1);
631
+ const nextProgress = (i + 1) / (trailCount - 1);
623
632
  const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
624
- const strokeWidth = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
625
- let d = `M${px(trail[start])} ${py(trail[start])}`;
626
- for (let i = start + 1; i <= end; i++) {
627
- d += ` L${px(trail[i])} ${py(trail[i])}`;
628
- }
629
- trailPaths[b].setAttribute("d", d);
630
- trailPaths[b].setAttribute("stroke-opacity", opacity.toFixed(3));
631
- trailPaths[b].setAttribute("stroke-width", strokeWidth.toFixed(2));
633
+ const width2 = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
634
+ const nextWidth = TRAIL_MIN_WIDTH2 + nextProgress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
635
+ const curr = trail[i];
636
+ const next = trail[i + 1];
637
+ const n0 = computeNormal(trail, i);
638
+ const n1 = computeNormal(trail, i + 1);
639
+ const halfW0 = width2 / 2;
640
+ const halfW1 = nextWidth / 2;
641
+ const l0x = px(curr) + n0.x * halfW0;
642
+ const l0y = py(curr) + n0.y * halfW0;
643
+ const r0x = px(curr) - n0.x * halfW0;
644
+ const r0y = py(curr) - n0.y * halfW0;
645
+ const l1x = px(next) + n1.x * halfW1;
646
+ const l1y = py(next) + n1.y * halfW1;
647
+ const r1x = px(next) - n1.x * halfW1;
648
+ const r1y = py(next) - n1.y * halfW1;
649
+ 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`;
650
+ trailPaths[i].setAttribute("d", d);
651
+ trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
652
+ }
653
+ for (let i = trailCount - 1; i < trailPaths.length; i++) {
654
+ trailPaths[i].setAttribute("d", "");
632
655
  }
633
656
  }
634
657
  function updateHead(trail, trailCount) {
@@ -638,14 +661,13 @@ function createSVGRenderer(options) {
638
661
  const head = trail[trailCount - 1];
639
662
  const x = px(head);
640
663
  const y = py(head);
641
- glowCircle.setAttribute("cx", x);
642
- glowCircle.setAttribute("cy", y);
643
664
  headCircle.setAttribute("cx", x);
644
665
  headCircle.setAttribute("cy", y);
645
666
  }
646
667
  let animationId = null;
647
668
  let lastTime = 0;
648
- const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
669
+ const prefersReducedMotion =
670
+ typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
649
671
  let morphResolve = null;
650
672
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
651
673
  let morphTarget = null;
@@ -655,7 +677,7 @@ function createSVGRenderer(options) {
655
677
  const samples = Math.max(50, Math.round(period * 20));
656
678
  const points = [];
657
679
  for (let i = 0; i <= samples; i++) {
658
- const t = i / samples * period;
680
+ const t = (i / samples) * period;
659
681
  const p = target.fn(t, 0, {});
660
682
  points.push(p);
661
683
  }
@@ -683,13 +705,16 @@ function createSVGRenderer(options) {
683
705
  skeletonPathA.setAttribute("visibility", "visible");
684
706
  skeletonPathA.setAttribute(
685
707
  "stroke-opacity",
686
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2)
708
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
687
709
  );
688
710
  }
689
711
  if (morphPathBBuilt) {
690
712
  skeletonPathB.setAttribute("d", morphPathBBuilt);
691
713
  skeletonPathB.setAttribute("visibility", "visible");
692
- skeletonPathB.setAttribute("stroke-opacity", String(morphAlpha * DEFAULT_SKELETON_OPACITY2));
714
+ skeletonPathB.setAttribute(
715
+ "stroke-opacity",
716
+ String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
717
+ );
693
718
  }
694
719
  if (morphAlpha >= 1) {
695
720
  engine.completeMorph();
@@ -781,7 +806,7 @@ function createSVGRenderer(options) {
781
806
  return new Promise((resolve) => {
782
807
  morphResolve = resolve;
783
808
  });
784
- }
809
+ },
785
810
  };
786
811
  }
787
812
  function createSarmalSVG(container, curveDef, options) {
@@ -793,26 +818,29 @@ function createSarmalSVG(container, curveDef, options) {
793
818
  // src/curves.ts
794
819
  var TWO_PI2 = Math.PI * 2;
795
820
  function artemis2(t, _time, _params) {
796
- const a = 0.35, b = 0.15, ox = 0.175;
797
- const s = Math.sin(t), c = Math.cos(t);
821
+ const a = 0.35,
822
+ b = 0.15,
823
+ ox = 0.175;
824
+ const s = Math.sin(t),
825
+ c = Math.cos(t);
798
826
  const denom = 1 + s * s;
799
827
  return {
800
- x: c * (1 + a * c) / denom - ox,
801
- y: s * c * (1 + b * c) / denom
828
+ x: (c * (1 + a * c)) / denom - ox,
829
+ y: (s * c * (1 + b * c)) / denom,
802
830
  };
803
831
  }
804
832
  function epitrochoid7(t, _time, _params) {
805
833
  const d = 1 + 0.55 * Math.sin(t * 0.5);
806
834
  return {
807
835
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
808
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
836
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
809
837
  };
810
838
  }
811
839
  function epitrochoid7Skeleton(t) {
812
840
  const d = 1.275;
813
841
  return {
814
842
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
815
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
843
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
816
844
  };
817
845
  }
818
846
  function astroid(t, _time, _params) {
@@ -820,55 +848,56 @@ function astroid(t, _time, _params) {
820
848
  const s = Math.sin(t);
821
849
  return {
822
850
  x: c * c * c,
823
- y: s * s * s
851
+ y: s * s * s,
824
852
  };
825
853
  }
826
854
  function deltoid(t, _time, _params) {
827
855
  return {
828
856
  x: 2 * Math.cos(t) + Math.cos(2 * t),
829
- y: 2 * Math.sin(t) - Math.sin(2 * t)
857
+ y: 2 * Math.sin(t) - Math.sin(2 * t),
830
858
  };
831
859
  }
832
860
  function rose5(t, _time, _params) {
833
861
  const r = Math.cos(5 * t);
834
862
  return {
835
863
  x: r * Math.cos(t),
836
- y: r * Math.sin(t)
864
+ y: r * Math.sin(t),
837
865
  };
838
866
  }
839
867
  function rose3(t, _time, _params) {
840
868
  const r = Math.cos(3 * t);
841
869
  return {
842
870
  x: r * Math.cos(t),
843
- y: r * Math.sin(t)
871
+ y: r * Math.sin(t),
844
872
  };
845
873
  }
846
874
  function lissajous32(t, time, _params) {
847
875
  const phi = time * 0.45;
848
876
  return {
849
877
  x: Math.sin(3 * t + phi),
850
- y: Math.sin(2 * t)
878
+ y: Math.sin(2 * t),
851
879
  };
852
880
  }
853
881
  function lissajous43(t, time, _params) {
854
882
  const phi = time * 0.38;
855
883
  return {
856
884
  x: Math.sin(4 * t + phi),
857
- y: Math.sin(3 * t)
885
+ y: Math.sin(3 * t),
858
886
  };
859
887
  }
860
888
  function epicycloid3(t, _time, _params) {
861
889
  return {
862
890
  x: 4 * Math.cos(t) - Math.cos(4 * t),
863
- y: 4 * Math.sin(t) - Math.sin(4 * t)
891
+ y: 4 * Math.sin(t) - Math.sin(4 * t),
864
892
  };
865
893
  }
866
894
  function lame(t, time, _params) {
867
895
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
868
- const c = Math.cos(t), s = Math.sin(t);
896
+ const c = Math.cos(t),
897
+ s = Math.sin(t);
869
898
  return {
870
899
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
871
- y: Math.sign(s) * Math.pow(Math.abs(s), p)
900
+ y: Math.sign(s) * Math.pow(Math.abs(s), p),
872
901
  };
873
902
  }
874
903
  var curves = {
@@ -876,66 +905,66 @@ var curves = {
876
905
  name: "Artemis II",
877
906
  fn: artemis2,
878
907
  period: TWO_PI2,
879
- speed: 0.7
908
+ speed: 0.7,
880
909
  },
881
910
  epitrochoid7: {
882
911
  name: "Epitrochoid",
883
912
  fn: epitrochoid7,
884
913
  period: TWO_PI2,
885
914
  speed: 1.4,
886
- skeletonFn: epitrochoid7Skeleton
915
+ skeletonFn: epitrochoid7Skeleton,
887
916
  },
888
917
  astroid: {
889
918
  name: "Astroid",
890
919
  fn: astroid,
891
920
  period: TWO_PI2,
892
- speed: 1.1
921
+ speed: 1.1,
893
922
  },
894
923
  deltoid: {
895
924
  name: "Deltoid",
896
925
  fn: deltoid,
897
926
  period: TWO_PI2,
898
- speed: 0.9
927
+ speed: 0.9,
899
928
  },
900
929
  rose5: {
901
930
  name: "Rose (n=5)",
902
931
  fn: rose5,
903
932
  period: TWO_PI2,
904
- speed: 1
933
+ speed: 1,
905
934
  },
906
935
  rose3: {
907
936
  name: "Rose (n=3)",
908
937
  fn: rose3,
909
938
  period: TWO_PI2,
910
- speed: 1.15
939
+ speed: 1.15,
911
940
  },
912
941
  lissajous32: {
913
942
  name: "Lissajous 3:2",
914
943
  fn: lissajous32,
915
944
  period: TWO_PI2,
916
945
  speed: 2,
917
- skeleton: "live"
946
+ skeleton: "live",
918
947
  },
919
948
  lissajous43: {
920
949
  name: "Lissajous 4:3",
921
950
  fn: lissajous43,
922
951
  period: TWO_PI2,
923
952
  speed: 1.8,
924
- skeleton: "live"
953
+ skeleton: "live",
925
954
  },
926
955
  epicycloid3: {
927
956
  name: "Epicycloid (n=3)",
928
957
  fn: epicycloid3,
929
958
  period: TWO_PI2,
930
- speed: 0.75
959
+ speed: 0.75,
931
960
  },
932
961
  lame: {
933
962
  name: "Lam\xE9 Curve",
934
963
  fn: lame,
935
964
  period: TWO_PI2,
936
965
  speed: 1,
937
- skeleton: "live"
938
- }
966
+ skeleton: "live",
967
+ },
939
968
  };
940
969
 
941
970
  // src/index.ts
@@ -947,4 +976,4 @@ function createSarmal(canvas, curveDef, options) {
947
976
 
948
977
  export { createEngine, createRenderer, createSVGRenderer, createSarmal, createSarmalSVG, curves };
949
978
  //# sourceMappingURL=index.js.map
950
- //# sourceMappingURL=index.js.map
979
+ //# sourceMappingURL=index.js.map