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