@sarmal/core 0.9.7 → 0.10.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 (61) hide show
  1. package/dist/auto-init.cjs +171 -166
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.d.cts +2 -1
  4. package/dist/auto-init.d.ts +2 -1
  5. package/dist/auto-init.js +170 -165
  6. package/dist/auto-init.js.map +1 -1
  7. package/dist/curves/artemis2.cjs +7 -10
  8. package/dist/curves/artemis2.d.cts +1 -1
  9. package/dist/curves/artemis2.d.ts +1 -1
  10. package/dist/curves/artemis2.js +6 -9
  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 +28 -32
  28. package/dist/curves/index.d.cts +11 -11
  29. package/dist/curves/index.d.ts +11 -11
  30. package/dist/curves/index.js +28 -44
  31. package/dist/curves/lame.cjs +5 -6
  32. package/dist/curves/lame.d.cts +1 -1
  33. package/dist/curves/lame.d.ts +1 -1
  34. package/dist/curves/lame.js +4 -5
  35. package/dist/curves/lissajous32.cjs +4 -4
  36. package/dist/curves/lissajous32.d.cts +1 -1
  37. package/dist/curves/lissajous32.d.ts +1 -1
  38. package/dist/curves/lissajous32.js +3 -3
  39. package/dist/curves/lissajous43.cjs +4 -4
  40. package/dist/curves/lissajous43.d.cts +1 -1
  41. package/dist/curves/lissajous43.d.ts +1 -1
  42. package/dist/curves/lissajous43.js +3 -3
  43. package/dist/curves/rose3.cjs +4 -4
  44. package/dist/curves/rose3.d.cts +1 -1
  45. package/dist/curves/rose3.d.ts +1 -1
  46. package/dist/curves/rose3.js +3 -3
  47. package/dist/curves/rose5.cjs +4 -4
  48. package/dist/curves/rose5.d.cts +1 -1
  49. package/dist/curves/rose5.d.ts +1 -1
  50. package/dist/curves/rose5.js +3 -3
  51. package/dist/index.cjs +211 -283
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +31 -64
  54. package/dist/index.d.ts +31 -64
  55. package/dist/index.js +211 -300
  56. package/dist/index.js.map +1 -1
  57. package/dist/types-DcyISvnH.d.cts +230 -0
  58. package/dist/types-DcyISvnH.d.ts +230 -0
  59. package/package.json +2 -1
  60. package/dist/types-cR2xOewv.d.cts +0 -234
  61. package/dist/types-cR2xOewv.d.ts +0 -234
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);
@@ -98,7 +98,7 @@ function createEngine(curveDef, trailLength = 120) {
98
98
  actualTime += deltaTime;
99
99
  if (morphCurveB !== null && _morphAlpha !== null) {
100
100
  const a = curve.fn(t, actualTime, EMPTY_PARAMS);
101
- const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
101
+ const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
102
102
  const b = morphCurveB.fn(tB, actualTime, EMPTY_PARAMS);
103
103
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
104
104
  } else {
@@ -121,15 +121,15 @@ function createEngine(curveDef, trailLength = 120) {
121
121
  actualTime = 0;
122
122
  trail.clear();
123
123
  },
124
- seek(newT, { clearTrail = false } = {}) {
125
- t = ((newT % curve.period) + curve.period) % curve.period;
124
+ jump(newT, { clearTrail = false } = {}) {
125
+ t = (newT % curve.period + curve.period) % curve.period;
126
126
  if (clearTrail) {
127
127
  trail.clear();
128
128
  }
129
129
  },
130
- seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
130
+ seek(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
131
131
  const advance = curve.speed * step;
132
- const target = ((targetT % curve.period) + curve.period) % curve.period;
132
+ const target = (targetT % curve.period + curve.period) % curve.period;
133
133
  const targetTime = target / curve.speed;
134
134
  t = target;
135
135
  actualTime = targetTime;
@@ -138,7 +138,7 @@ function createEngine(curveDef, trailLength = 120) {
138
138
  const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
139
139
  for (let i = count - 1; i >= 0; i--) {
140
140
  const sampleT = target - i * advance;
141
- const wrappedT = ((sampleT % curve.period) + curve.period) % curve.period;
141
+ const wrappedT = (sampleT % curve.period + curve.period) % curve.period;
142
142
  const time = targetTime - i * step;
143
143
  const point = curve.fn(wrappedT, time, EMPTY_PARAMS);
144
144
  trail.push(point.x, point.y);
@@ -155,16 +155,13 @@ function createEngine(curveDef, trailLength = 120) {
155
155
  ...frozenB,
156
156
  fn: (sampleT, time, params) => {
157
157
  const a = frozenA.fn(sampleT, time, params);
158
- const tB =
159
- frozenStrategy === "normalized"
160
- ? (sampleT / frozenA.period) * frozenB.period
161
- : sampleT;
158
+ const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
162
159
  const b = frozenB.fn(tB, time, params);
163
160
  return {
164
161
  x: a.x + (b.x - a.x) * frozenAlpha,
165
- y: a.y + (b.y - a.y) * frozenAlpha,
162
+ y: a.y + (b.y - a.y) * frozenAlpha
166
163
  };
167
- },
164
+ }
168
165
  };
169
166
  }
170
167
  _morphStrategy = strategy;
@@ -177,7 +174,7 @@ function createEngine(curveDef, trailLength = 120) {
177
174
  completeMorph() {
178
175
  if (morphCurveB !== null) {
179
176
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
180
- t = (t / curve.period) * morphCurveB.period;
177
+ t = t / curve.period * morphCurveB.period;
181
178
  }
182
179
  curve = morphCurveB;
183
180
  }
@@ -189,46 +186,132 @@ function createEngine(curveDef, trailLength = 120) {
189
186
  const points = new Array(steps);
190
187
  if (morphCurveB !== null && _morphAlpha !== null) {
191
188
  for (let i = 0; i < steps; i++) {
192
- const sampleT = (i / (steps - 1)) * curve.period;
189
+ const sampleT = i / (steps - 1) * curve.period;
193
190
  const a = sampleSkeleton(curve, sampleT);
194
- const tB =
195
- _morphStrategy === "normalized"
196
- ? (sampleT / curve.period) * morphCurveB.period
197
- : sampleT;
191
+ const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
198
192
  const b = sampleSkeleton(morphCurveB, tB);
199
193
  points[i] = {
200
194
  x: a.x + (b.x - a.x) * _morphAlpha,
201
- y: a.y + (b.y - a.y) * _morphAlpha,
195
+ y: a.y + (b.y - a.y) * _morphAlpha
202
196
  };
203
197
  }
204
198
  return points;
205
199
  }
206
200
  for (let i = 0; i < steps; i++) {
207
- const sampleT = (i / (steps - 1)) * curve.period;
201
+ const sampleT = i / (steps - 1) * curve.period;
208
202
  points[i] = sampleSkeleton(curve, sampleT);
209
203
  }
210
204
  return points;
211
- },
205
+ }
212
206
  };
213
207
  }
214
208
 
215
- // src/renderer.ts
209
+ // src/renderer-shared.ts
216
210
  var DEFAULT_MORPH_DURATION_MS = 300;
217
- var DEFAULT_HEAD_RADIUS = 4;
218
- var DEFAULT_SKELETON_COLOR = "#ffffff";
219
211
  var DEFAULT_SKELETON_OPACITY = 0.15;
220
212
  var FIT_PADDING = 0.1;
221
213
  var TRAIL_FADE_CURVE = 1.5;
222
214
  var TRAIL_MAX_OPACITY = 0.88;
223
215
  var TRAIL_MIN_WIDTH = 0.5;
224
216
  var TRAIL_MAX_WIDTH = 2.5;
217
+ function computeTangent(trail, i) {
218
+ const count = trail.length;
219
+ if (count < 2) {
220
+ return { x: 1, y: 0 };
221
+ }
222
+ if (i === 0) {
223
+ const dx2 = trail[1].x - trail[0].x;
224
+ const dy2 = trail[1].y - trail[0].y;
225
+ const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
226
+ return { x: dx2 / len2, y: dy2 / len2 };
227
+ }
228
+ if (i === count - 1) {
229
+ const dx2 = trail[count - 1].x - trail[count - 2].x;
230
+ const dy2 = trail[count - 1].y - trail[count - 2].y;
231
+ const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
232
+ return { x: dx2 / len2, y: dy2 / len2 };
233
+ }
234
+ const dx = trail[i + 1].x - trail[i - 1].x;
235
+ const dy = trail[i + 1].y - trail[i - 1].y;
236
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
237
+ return { x: dx / len, y: dy / len };
238
+ }
239
+ function computeNormal(trail, i) {
240
+ const tangent = computeTangent(trail, i);
241
+ return { x: -tangent.y, y: tangent.x };
242
+ }
243
+ function computeTrailQuad(trail, i, trailCount, toX, toY) {
244
+ const progress = i / (trailCount - 1);
245
+ const nextProgress = (i + 1) / (trailCount - 1);
246
+ const opacity = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
247
+ const w0 = (TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH)) / 2;
248
+ const w1 = (TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH)) / 2;
249
+ const curr = trail[i];
250
+ const next = trail[i + 1];
251
+ const n0 = computeNormal(trail, i);
252
+ const n1 = computeNormal(trail, i + 1);
253
+ const cx = toX(curr);
254
+ const cy = toY(curr);
255
+ const nx = toX(next);
256
+ const ny = toY(next);
257
+ return {
258
+ l0x: cx + n0.x * w0,
259
+ l0y: cy + n0.y * w0,
260
+ r0x: cx - n0.x * w0,
261
+ r0y: cy - n0.y * w0,
262
+ l1x: nx + n1.x * w1,
263
+ l1y: ny + n1.y * w1,
264
+ r1x: nx - n1.x * w1,
265
+ r1y: ny - n1.y * w1,
266
+ opacity,
267
+ progress
268
+ };
269
+ }
270
+ function computeBoundaries(pts, logicalWidth, logicalHeight) {
271
+ if (pts.length === 0) return null;
272
+ const first = pts[0];
273
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
274
+ for (const p of pts) {
275
+ if (p.x < minX) {
276
+ minX = p.x;
277
+ }
278
+ if (p.x > maxX) {
279
+ maxX = p.x;
280
+ }
281
+ if (p.y < minY) {
282
+ minY = p.y;
283
+ }
284
+ if (p.y > maxY) {
285
+ maxY = p.y;
286
+ }
287
+ }
288
+ const w = maxX - minX;
289
+ const h = maxY - minY;
290
+ if (w === 0 && h === 0) {
291
+ throw new Error(
292
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
293
+ );
294
+ }
295
+ const scaleX = logicalWidth / (w * (1 + FIT_PADDING * 2));
296
+ const scaleY = logicalHeight / (h * (1 + FIT_PADDING * 2));
297
+ const scale = Math.min(scaleX, scaleY);
298
+ return {
299
+ scale,
300
+ offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
301
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale
302
+ };
303
+ }
304
+
305
+ // src/renderer.ts
306
+ var DEFAULT_HEAD_RADIUS = 4;
307
+ var DEFAULT_SKELETON_COLOR = "#ffffff";
225
308
  var GRADIENT = {
226
309
  bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
227
310
  sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
228
311
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
229
312
  ice: ["#1e3a8a", "#67e8f9"],
230
313
  fire: ["#7f1d1d", "#fbbf24"],
231
- forest: ["#14532d", "#86efac"],
314
+ forest: ["#14532d", "#86efac"]
232
315
  };
233
316
  var PRESETS = {
234
317
  bard: GRADIENT.bard,
@@ -236,16 +319,16 @@ var PRESETS = {
236
319
  ocean: GRADIENT.ocean,
237
320
  ice: GRADIENT.ice,
238
321
  fire: GRADIENT.fire,
239
- forest: GRADIENT.forest,
322
+ forest: GRADIENT.forest
240
323
  };
241
324
  function hexToRgb(hex) {
242
325
  const n = parseInt(hex.slice(1), 16);
243
- return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
326
+ return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
244
327
  }
245
328
  var lerpRgb = (a, b, t) => ({
246
329
  r: Math.round(a.r + (b.r - a.r) * t),
247
330
  g: Math.round(a.g + (b.g - a.g) * t),
248
- b: Math.round(a.b + (b.b - a.b) * t),
331
+ b: Math.round(a.b + (b.b - a.b) * t)
249
332
  });
250
333
  function getPaletteColor(palette, position, timeOffset = 0) {
251
334
  if (palette.length === 0) return { r: 255, g: 255, b: 255 };
@@ -265,33 +348,7 @@ function resolvePalette(palette, trailStyle) {
265
348
  }
266
349
  function hexToRgbComponents(hex) {
267
350
  const n = parseInt(hex.slice(1), 16);
268
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
269
- }
270
- function computeTangent(trail, i) {
271
- const count = trail.length;
272
- if (count < 2) {
273
- return { x: 1, y: 0 };
274
- }
275
- if (i === 0) {
276
- const dx2 = trail[1].x - trail[0].x;
277
- const dy2 = trail[1].y - trail[0].y;
278
- const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
279
- return { x: dx2 / len2, y: dy2 / len2 };
280
- }
281
- if (i === count - 1) {
282
- const dx2 = trail[count - 1].x - trail[count - 2].x;
283
- const dy2 = trail[count - 1].y - trail[count - 2].y;
284
- const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
285
- return { x: dx2 / len2, y: dy2 / len2 };
286
- }
287
- const dx = trail[i + 1].x - trail[i - 1].x;
288
- const dy = trail[i + 1].y - trail[i - 1].y;
289
- const len = Math.sqrt(dx * dx + dy * dy) || 1;
290
- return { x: dx / len, y: dy / len };
291
- }
292
- function computeNormal(trail, i) {
293
- const tangent = computeTangent(trail, i);
294
- return { x: -tangent.y, y: tangent.x };
351
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
295
352
  }
296
353
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
297
354
  target.style.width = `${logicalWidth}px`;
@@ -310,7 +367,7 @@ function createRenderer(options) {
310
367
  skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
311
368
  trailColor: options.trailColor ?? "#ffffff",
312
369
  headColor: options.headColor ?? "#ffffff",
313
- headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
370
+ headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS
314
371
  };
315
372
  const trailStyle = options.trailStyle ?? "default";
316
373
  const palette = resolvePalette(options.palette, trailStyle);
@@ -340,47 +397,8 @@ function createRenderer(options) {
340
397
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
341
398
  let morphAlpha = 0;
342
399
  let gradientAnimTime = 0;
343
- function computeBoundaries(pts) {
344
- if (pts.length === 0) return null;
345
- const first = pts[0];
346
- let minX = first.x,
347
- maxX = first.x,
348
- minY = first.y,
349
- maxY = first.y;
350
- for (const p of pts) {
351
- if (p.x < minX) {
352
- minX = p.x;
353
- }
354
- if (p.x > maxX) {
355
- maxX = p.x;
356
- }
357
- if (p.y < minY) {
358
- minY = p.y;
359
- }
360
- if (p.y > maxY) {
361
- maxY = p.y;
362
- }
363
- }
364
- const width = maxX - minX;
365
- const height = maxY - minY;
366
- if (width === 0 && height === 0) {
367
- throw new Error(
368
- "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
369
- );
370
- }
371
- const scaleX = logicalWidth / (width * (1 + FIT_PADDING * 2));
372
- const scaleY = logicalHeight / (height * (1 + FIT_PADDING * 2));
373
- const s = Math.min(scaleX, scaleY);
374
- const boundsWidth = width * s;
375
- const boundsHeight = height * s;
376
- return {
377
- scale: s,
378
- offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
379
- offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
380
- };
381
- }
382
400
  function calculateBoundaries() {
383
- const b = computeBoundaries(skeleton);
401
+ const b = computeBoundaries(skeleton, logicalWidth, logicalHeight);
384
402
  if (b) {
385
403
  scale = b.scale;
386
404
  offsetX = b.offsetX;
@@ -444,32 +462,22 @@ function createRenderer(options) {
444
462
  if (trailCount < 2) {
445
463
  return;
446
464
  }
465
+ const toX = (p) => p.x * scale + offsetX;
466
+ const toY = (p) => p.y * scale + offsetY;
447
467
  for (let i = 0; i < trailCount - 1; i++) {
448
- const progress = i / (trailCount - 1);
449
- const nextProgress = (i + 1) / (trailCount - 1);
450
- const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
451
- const width = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
452
- const nextWidth = TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
453
- const curr = trail[i];
454
- const next = trail[i + 1];
455
- const n0 = computeNormal(trail, i);
456
- const n1 = computeNormal(trail, i + 1);
457
- const halfW0 = width / 2;
458
- const halfW1 = nextWidth / 2;
459
- const l0x = curr.x * scale + offsetX + n0.x * halfW0;
460
- const l0y = curr.y * scale + offsetY + n0.y * halfW0;
461
- const r0x = curr.x * scale + offsetX - n0.x * halfW0;
462
- const r0y = curr.y * scale + offsetY - n0.y * halfW0;
463
- const l1x = next.x * scale + offsetX + n1.x * halfW1;
464
- const l1y = next.y * scale + offsetY + n1.y * halfW1;
465
- const r1x = next.x * scale + offsetX - n1.x * halfW1;
466
- const r1y = next.y * scale + offsetY - n1.y * halfW1;
468
+ const { l0x, l0y, r0x, r0y, l1x, l1y, r1x, r1y, opacity, progress } = computeTrailQuad(
469
+ trail,
470
+ i,
471
+ trailCount,
472
+ toX,
473
+ toY
474
+ );
467
475
  if (trailStyle === "default") {
468
- ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
476
+ ctx.fillStyle = `rgba(${trailRgb},${opacity})`;
469
477
  } else {
470
478
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
471
479
  const color = getPaletteColor(palette, progress, timeOffset);
472
- ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`;
480
+ ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
473
481
  }
474
482
  ctx.beginPath();
475
483
  ctx.moveTo(l0x, l0y);
@@ -502,7 +510,7 @@ function createRenderer(options) {
502
510
  morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
503
511
  engine.setMorphAlpha(morphAlpha);
504
512
  const interpolatedSkeleton = engine.getSarmalSkeleton();
505
- const bounds = computeBoundaries(interpolatedSkeleton);
513
+ const bounds = computeBoundaries(interpolatedSkeleton, logicalWidth, logicalHeight);
506
514
  if (bounds) {
507
515
  scale = bounds.scale;
508
516
  offsetX = bounds.offsetX;
@@ -563,12 +571,12 @@ function createRenderer(options) {
563
571
  animationId = null;
564
572
  }
565
573
  },
574
+ jump(t, options2) {
575
+ engine.jump(t, options2);
576
+ },
566
577
  seek(t, options2) {
567
578
  engine.seek(t, options2);
568
579
  },
569
- seekWithTrail(t) {
570
- engine.seekWithTrail(t);
571
- },
572
580
  morphTo(target, options2) {
573
581
  if (morphResolve !== null) {
574
582
  engine.completeMorph();
@@ -582,20 +590,33 @@ function createRenderer(options) {
582
590
  return new Promise((resolve) => {
583
591
  morphResolve = resolve;
584
592
  });
585
- },
593
+ }
586
594
  };
587
595
  }
588
596
 
589
597
  // src/renderer-svg.ts
590
- var DEFAULT_MORPH_DURATION_MS2 = 300;
591
598
  var MAX_TRAIL_SEGMENTS = 200;
592
- var TRAIL_FADE_CURVE2 = 1.5;
593
- var TRAIL_MAX_OPACITY2 = 0.88;
594
- var TRAIL_MIN_WIDTH2 = 0.5;
595
- var TRAIL_MAX_WIDTH2 = 2.5;
596
- var DEFAULT_SKELETON_OPACITY2 = 0.15;
597
- var FIT_PADDING2 = 0.1;
598
599
  var EMPTY_PARAMS2 = {};
600
+ function pointsToPathString(pts, scale, offsetX, offsetY) {
601
+ if (pts.length < 2) return "";
602
+ const px = (p) => (p.x * scale + offsetX).toFixed(2);
603
+ const py = (p) => (p.y * scale + offsetY).toFixed(2);
604
+ let d = `M${px(pts[0])} ${py(pts[0])}`;
605
+ for (let i = 1; i < pts.length; i++) {
606
+ d += ` L${px(pts[i])} ${py(pts[i])}`;
607
+ }
608
+ return d + " Z";
609
+ }
610
+ function sampleCurveSkeleton(curveDef) {
611
+ const period = curveDef.period ?? Math.PI * 2;
612
+ const samples = Math.ceil(period * 50);
613
+ const pts = Array.from({ length: samples });
614
+ for (let i = 0; i < samples; i++) {
615
+ const t = i / (samples - 1) * period;
616
+ pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
617
+ }
618
+ return pts;
619
+ }
599
620
  function el(tag) {
600
621
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
601
622
  }
@@ -606,7 +627,7 @@ function createSVGRenderer(options) {
606
627
  trailColor: options.trailColor ?? "#ffffff",
607
628
  headColor: options.headColor ?? "#ffffff",
608
629
  headRadius: options.headRadius ?? 4,
609
- ariaLabel: options.ariaLabel ?? "Loading",
630
+ ariaLabel: options.ariaLabel ?? "Loading"
610
631
  };
611
632
  const rect = container.getBoundingClientRect();
612
633
  const width = rect.width || 200;
@@ -623,7 +644,7 @@ function createSVGRenderer(options) {
623
644
  const skeletonPath = el("path");
624
645
  skeletonPath.setAttribute("fill", "none");
625
646
  skeletonPath.setAttribute("stroke", opts.skeletonColor);
626
- skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY2));
647
+ skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
627
648
  skeletonPath.setAttribute("stroke-width", "1.5");
628
649
  svg.appendChild(skeletonPath);
629
650
  const skeletonPathA = el("path");
@@ -655,41 +676,13 @@ function createSVGRenderer(options) {
655
676
  let scale = 1;
656
677
  let offsetX = 0;
657
678
  let offsetY = 0;
658
- function calculateBoundaries(skeleton2) {
659
- if (skeleton2.length === 0) {
660
- return;
661
- }
662
- const first = skeleton2[0];
663
- let minX = first.x,
664
- maxX = first.x,
665
- minY = first.y,
666
- maxY = first.y;
667
- for (const p of skeleton2) {
668
- if (p.x < minX) {
669
- minX = p.x;
670
- }
671
- if (p.x > maxX) {
672
- maxX = p.x;
673
- }
674
- if (p.y < minY) {
675
- minY = p.y;
676
- }
677
- if (p.y > maxY) {
678
- maxY = p.y;
679
- }
680
- }
681
- const w = maxX - minX;
682
- const h = maxY - minY;
683
- if (w === 0 && h === 0) {
684
- throw new Error(
685
- "[sarmal] All skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
686
- );
679
+ function applyBoundaries(skeleton2) {
680
+ const b = computeBoundaries(skeleton2, width, height);
681
+ if (b) {
682
+ scale = b.scale;
683
+ offsetX = b.offsetX;
684
+ offsetY = b.offsetY;
687
685
  }
688
- const scaleX = width / (w * (1 + FIT_PADDING2 * 2));
689
- const scaleY = height / (h * (1 + FIT_PADDING2 * 2));
690
- scale = Math.min(scaleX, scaleY);
691
- offsetX = (width - w * scale) / 2 - minX * scale;
692
- offsetY = (height - h * scale) / 2 - minY * scale;
693
686
  }
694
687
  function px(p) {
695
688
  return p.x * scale + offsetX;
@@ -697,26 +690,11 @@ function createSVGRenderer(options) {
697
690
  function py(p) {
698
691
  return p.y * scale + offsetY;
699
692
  }
700
- function pxStr(p) {
701
- return px(p).toFixed(2);
702
- }
703
- function pyStr(p) {
704
- return py(p).toFixed(2);
705
- }
706
693
  function updateSkeleton(skeleton2) {
707
- if (skeleton2.length < 2) {
708
- skeletonPath.setAttribute("d", "");
709
- return;
710
- }
711
- let d = `M${pxStr(skeleton2[0])} ${pyStr(skeleton2[0])}`;
712
- for (let i = 1; i < skeleton2.length; i++) {
713
- d += ` L${pxStr(skeleton2[i])} ${pyStr(skeleton2[i])}`;
714
- }
715
- d += " Z";
716
- skeletonPath.setAttribute("d", d);
694
+ skeletonPath.setAttribute("d", pointsToPathString(skeleton2, scale, offsetX, offsetY));
717
695
  }
718
696
  const skeleton = engine.getSarmalSkeleton();
719
- calculateBoundaries(skeleton);
697
+ applyBoundaries(skeleton);
720
698
  if (!engine.isLiveSkeleton) {
721
699
  updateSkeleton(skeleton);
722
700
  }
@@ -728,25 +706,13 @@ function createSVGRenderer(options) {
728
706
  return;
729
707
  }
730
708
  for (let i = 0; i < trailCount - 1; i++) {
731
- const progress = i / (trailCount - 1);
732
- const nextProgress = (i + 1) / (trailCount - 1);
733
- const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
734
- const width2 = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
735
- const nextWidth = TRAIL_MIN_WIDTH2 + nextProgress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
736
- const curr = trail[i];
737
- const next = trail[i + 1];
738
- const n0 = computeNormal(trail, i);
739
- const n1 = computeNormal(trail, i + 1);
740
- const halfW0 = width2 / 2;
741
- const halfW1 = nextWidth / 2;
742
- const l0x = px(curr) + n0.x * halfW0;
743
- const l0y = py(curr) + n0.y * halfW0;
744
- const r0x = px(curr) - n0.x * halfW0;
745
- const r0y = py(curr) - n0.y * halfW0;
746
- const l1x = px(next) + n1.x * halfW1;
747
- const l1y = py(next) + n1.y * halfW1;
748
- const r1x = px(next) - n1.x * halfW1;
749
- const r1y = py(next) - n1.y * halfW1;
709
+ const { l0x, l0y, r0x, r0y, l1x, l1y, r1x, r1y, opacity } = computeTrailQuad(
710
+ trail,
711
+ i,
712
+ trailCount,
713
+ px,
714
+ py
715
+ );
750
716
  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`;
751
717
  trailPaths[i].setAttribute("d", d);
752
718
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
@@ -767,33 +733,11 @@ function createSVGRenderer(options) {
767
733
  }
768
734
  let animationId = null;
769
735
  let lastTime = 0;
770
- const prefersReducedMotion =
771
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
736
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
772
737
  let morphResolve = null;
773
- let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
738
+ let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
774
739
  let morphTarget = null;
775
740
  let morphAlpha = 0;
776
- function buildSkeletonPath(target, scale2, offsetX2, offsetY2) {
777
- const period = target.period ?? Math.PI * 2;
778
- const samples = Math.max(50, Math.round(period * 20));
779
- const points = [];
780
- for (let i = 0; i <= samples; i++) {
781
- const t = (i / samples) * period;
782
- const p = target.fn(t, 0, EMPTY_PARAMS2);
783
- points.push(p);
784
- }
785
- if (points.length < 2) {
786
- return "";
787
- }
788
- const px2 = (p) => (p.x * scale2 + offsetX2).toFixed(2);
789
- const py2 = (p) => (p.y * scale2 + offsetY2).toFixed(2);
790
- let d = `M${px2(points[0])} ${py2(points[0])}`;
791
- for (let i = 1; i < points.length; i++) {
792
- d += ` L${px2(points[i])} ${py2(points[i])}`;
793
- }
794
- d += " Z";
795
- return d;
796
- }
797
741
  function renderFrame() {
798
742
  const now = performance.now();
799
743
  const dt = Math.min((now - lastTime) / 1e3, 1 / 30);
@@ -806,16 +750,13 @@ function createSVGRenderer(options) {
806
750
  skeletonPathA.setAttribute("visibility", "visible");
807
751
  skeletonPathA.setAttribute(
808
752
  "stroke-opacity",
809
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
753
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
810
754
  );
811
755
  }
812
756
  if (morphPathBBuilt) {
813
757
  skeletonPathB.setAttribute("d", morphPathBBuilt);
814
758
  skeletonPathB.setAttribute("visibility", "visible");
815
- skeletonPathB.setAttribute(
816
- "stroke-opacity",
817
- String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
818
- );
759
+ skeletonPathB.setAttribute("stroke-opacity", String(morphAlpha * DEFAULT_SKELETON_OPACITY));
819
760
  }
820
761
  if (morphAlpha >= 1) {
821
762
  engine.completeMorph();
@@ -828,7 +769,7 @@ function createSVGRenderer(options) {
828
769
  skeletonPathA.setAttribute("visibility", "hidden");
829
770
  skeletonPathB.setAttribute("visibility", "hidden");
830
771
  const newSkeleton = engine.getSarmalSkeleton();
831
- calculateBoundaries(newSkeleton);
772
+ applyBoundaries(newSkeleton);
832
773
  updateSkeleton(newSkeleton);
833
774
  }
834
775
  }
@@ -836,7 +777,7 @@ function createSVGRenderer(options) {
836
777
  const trailCount = engine.trailCount;
837
778
  if (engine.isLiveSkeleton && engine.morphAlpha === null) {
838
779
  const liveSkeleton = engine.getSarmalSkeleton();
839
- calculateBoundaries(liveSkeleton);
780
+ applyBoundaries(liveSkeleton);
840
781
  updateSkeleton(liveSkeleton);
841
782
  }
842
783
  updateTrail(trail, trailCount);
@@ -870,12 +811,12 @@ function createSVGRenderer(options) {
870
811
  }
871
812
  svg.remove();
872
813
  },
814
+ jump(t, options2) {
815
+ engine.jump(t, options2);
816
+ },
873
817
  seek(t, options2) {
874
818
  engine.seek(t, options2);
875
819
  },
876
- seekWithTrail(t) {
877
- engine.seekWithTrail(t);
878
- },
879
820
  morphTo(target, options2) {
880
821
  if (morphResolve !== null) {
881
822
  engine.completeMorph();
@@ -885,29 +826,20 @@ function createSVGRenderer(options) {
885
826
  skeletonPathA.setAttribute("visibility", "hidden");
886
827
  skeletonPathB.setAttribute("visibility", "hidden");
887
828
  }
888
- morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS2;
829
+ morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
889
830
  morphTarget = target;
890
831
  morphAlpha = 0;
891
832
  const currentSkeleton = engine.getSarmalSkeleton();
892
- if (currentSkeleton.length >= 2) {
893
- const px2 = (p) => (p.x * scale + offsetX).toFixed(2);
894
- const py2 = (p) => (p.y * scale + offsetY).toFixed(2);
895
- morphPathABuilt = `M${px2(currentSkeleton[0])} ${py2(currentSkeleton[0])}`;
896
- for (let i = 1; i < currentSkeleton.length; i++) {
897
- morphPathABuilt += ` L${px2(currentSkeleton[i])} ${py2(currentSkeleton[i])}`;
898
- }
899
- morphPathABuilt += " Z";
900
- } else {
901
- morphPathABuilt = "";
902
- }
833
+ morphPathABuilt = pointsToPathString(currentSkeleton, scale, offsetX, offsetY);
903
834
  engine.startMorph(target, options2?.morphStrategy);
904
835
  if (morphTarget) {
905
- morphPathBBuilt = buildSkeletonPath(morphTarget, scale, offsetX, offsetY);
836
+ const targetSkeleton = sampleCurveSkeleton(target);
837
+ morphPathBBuilt = pointsToPathString(targetSkeleton, scale, offsetX, offsetY);
906
838
  }
907
839
  return new Promise((resolve) => {
908
840
  morphResolve = resolve;
909
841
  });
910
- },
842
+ }
911
843
  };
912
844
  }
913
845
  function createSarmalSVG(container, curveDef, options) {
@@ -919,22 +851,19 @@ function createSarmalSVG(container, curveDef, options) {
919
851
  // src/curves/artemis2.ts
920
852
  var TWO_PI2 = Math.PI * 2;
921
853
  function artemis2Fn(t, _time, _params) {
922
- const a = 0.35,
923
- b = 0.15,
924
- ox = 0.175;
925
- const s = Math.sin(t),
926
- c = Math.cos(t);
854
+ const a = 0.35, b = 0.15, ox = 0.175;
855
+ const s = Math.sin(t), c = Math.cos(t);
927
856
  const denom = 1 + s * s;
928
857
  return {
929
- x: (c * (1 + a * c)) / denom - ox,
930
- y: (s * c * (1 + b * c)) / denom,
858
+ x: c * (1 + a * c) / denom - ox,
859
+ y: s * c * (1 + b * c) / denom
931
860
  };
932
861
  }
933
862
  var artemis2 = {
934
863
  name: "Artemis II",
935
864
  fn: artemis2Fn,
936
865
  period: TWO_PI2,
937
- speed: 0.7,
866
+ speed: 0.7
938
867
  };
939
868
 
940
869
  // src/curves/astroid.ts
@@ -944,14 +873,14 @@ function astroidFn(t, _time, _params) {
944
873
  const s = Math.sin(t);
945
874
  return {
946
875
  x: c * c * c,
947
- y: s * s * s,
876
+ y: s * s * s
948
877
  };
949
878
  }
950
879
  var astroid = {
951
880
  name: "Astroid",
952
881
  fn: astroidFn,
953
882
  period: TWO_PI3,
954
- speed: 1.1,
883
+ speed: 1.1
955
884
  };
956
885
 
957
886
  // src/curves/deltoid.ts
@@ -959,14 +888,14 @@ var TWO_PI4 = Math.PI * 2;
959
888
  function deltoidFn(t, _time, _params) {
960
889
  return {
961
890
  x: 2 * Math.cos(t) + Math.cos(2 * t),
962
- y: 2 * Math.sin(t) - Math.sin(2 * t),
891
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
963
892
  };
964
893
  }
965
894
  var deltoid = {
966
895
  name: "Deltoid",
967
896
  fn: deltoidFn,
968
897
  period: TWO_PI4,
969
- speed: 0.9,
898
+ speed: 0.9
970
899
  };
971
900
 
972
901
  // src/curves/epicycloid3.ts
@@ -974,14 +903,14 @@ var TWO_PI5 = Math.PI * 2;
974
903
  function epicycloid3Fn(t, _time, _params) {
975
904
  return {
976
905
  x: 4 * Math.cos(t) - Math.cos(4 * t),
977
- y: 4 * Math.sin(t) - Math.sin(4 * t),
906
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
978
907
  };
979
908
  }
980
909
  var epicycloid3 = {
981
910
  name: "Epicycloid (n=3)",
982
911
  fn: epicycloid3Fn,
983
912
  period: TWO_PI5,
984
- speed: 0.75,
913
+ speed: 0.75
985
914
  };
986
915
 
987
916
  // src/curves/epitrochoid7.ts
@@ -990,14 +919,14 @@ function epitrochoid7Fn(t, _time, _params) {
990
919
  const d = 1 + 0.55 * Math.sin(t * 0.5);
991
920
  return {
992
921
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
993
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
922
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
994
923
  };
995
924
  }
996
925
  function epitrochoid7SkeletonFn(t) {
997
926
  const d = 1.275;
998
927
  return {
999
928
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1000
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
929
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1001
930
  };
1002
931
  }
1003
932
  var epitrochoid7 = {
@@ -1005,7 +934,7 @@ var epitrochoid7 = {
1005
934
  fn: epitrochoid7Fn,
1006
935
  period: TWO_PI6,
1007
936
  speed: 1.4,
1008
- skeletonFn: epitrochoid7SkeletonFn,
937
+ skeletonFn: epitrochoid7SkeletonFn
1009
938
  };
1010
939
 
1011
940
  // src/curves/lissajous32.ts
@@ -1014,7 +943,7 @@ function lissajous32Fn(t, time, _params) {
1014
943
  const phi = time * 0.45;
1015
944
  return {
1016
945
  x: Math.sin(3 * t + phi),
1017
- y: Math.sin(2 * t),
946
+ y: Math.sin(2 * t)
1018
947
  };
1019
948
  }
1020
949
  var lissajous32 = {
@@ -1022,7 +951,7 @@ var lissajous32 = {
1022
951
  fn: lissajous32Fn,
1023
952
  period: TWO_PI7,
1024
953
  speed: 2,
1025
- skeleton: "live",
954
+ skeleton: "live"
1026
955
  };
1027
956
 
1028
957
  // src/curves/lissajous43.ts
@@ -1031,7 +960,7 @@ function lissajous43Fn(t, time, _params) {
1031
960
  const phi = time * 0.38;
1032
961
  return {
1033
962
  x: Math.sin(4 * t + phi),
1034
- y: Math.sin(3 * t),
963
+ y: Math.sin(3 * t)
1035
964
  };
1036
965
  }
1037
966
  var lissajous43 = {
@@ -1039,18 +968,17 @@ var lissajous43 = {
1039
968
  fn: lissajous43Fn,
1040
969
  period: TWO_PI8,
1041
970
  speed: 1.8,
1042
- skeleton: "live",
971
+ skeleton: "live"
1043
972
  };
1044
973
 
1045
974
  // src/curves/lame.ts
1046
975
  var TWO_PI9 = Math.PI * 2;
1047
976
  function lameFn(t, time, _params) {
1048
977
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1049
- const c = Math.cos(t),
1050
- s = Math.sin(t);
978
+ const c = Math.cos(t), s = Math.sin(t);
1051
979
  return {
1052
980
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1053
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
981
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
1054
982
  };
1055
983
  }
1056
984
  var lame = {
@@ -1058,7 +986,7 @@ var lame = {
1058
986
  fn: lameFn,
1059
987
  period: TWO_PI9,
1060
988
  speed: 1,
1061
- skeleton: "live",
989
+ skeleton: "live"
1062
990
  };
1063
991
 
1064
992
  // src/curves/rose3.ts
@@ -1067,14 +995,14 @@ function rose3Fn(t, _time, _params) {
1067
995
  const r = Math.cos(3 * t);
1068
996
  return {
1069
997
  x: r * Math.cos(t),
1070
- y: r * Math.sin(t),
998
+ y: r * Math.sin(t)
1071
999
  };
1072
1000
  }
1073
1001
  var rose3 = {
1074
1002
  name: "Rose (n=3)",
1075
1003
  fn: rose3Fn,
1076
1004
  period: TWO_PI10,
1077
- speed: 1.15,
1005
+ speed: 1.15
1078
1006
  };
1079
1007
 
1080
1008
  // src/curves/rose5.ts
@@ -1083,14 +1011,14 @@ function rose5Fn(t, _time, _params) {
1083
1011
  const r = Math.cos(5 * t);
1084
1012
  return {
1085
1013
  x: r * Math.cos(t),
1086
- y: r * Math.sin(t),
1014
+ y: r * Math.sin(t)
1087
1015
  };
1088
1016
  }
1089
1017
  var rose5 = {
1090
1018
  name: "Rose (n=5)",
1091
1019
  fn: rose5Fn,
1092
1020
  period: TWO_PI11,
1093
- speed: 1,
1021
+ speed: 1
1094
1022
  };
1095
1023
 
1096
1024
  // src/curves/index.ts
@@ -1104,7 +1032,7 @@ var curves = {
1104
1032
  lissajous32,
1105
1033
  lissajous43,
1106
1034
  epicycloid3,
1107
- lame,
1035
+ lame
1108
1036
  };
1109
1037
 
1110
1038
  // src/index.ts
@@ -1131,4 +1059,4 @@ exports.lissajous43 = lissajous43;
1131
1059
  exports.rose3 = rose3;
1132
1060
  exports.rose5 = rose5;
1133
1061
  //# sourceMappingURL=index.cjs.map
1134
- //# sourceMappingURL=index.cjs.map
1062
+ //# sourceMappingURL=index.cjs.map