@sarmal/core 0.9.1 → 0.9.10

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 +196 -157
  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 +195 -156
  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 +233 -265
  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 +233 -282
  56. package/dist/index.js.map +1 -1
  57. package/dist/types-cR2xOewv.d.cts +223 -0
  58. package/dist/types-cR2xOewv.d.ts +223 -0
  59. package/package.json +5 -3
  60. package/dist/types-DX8VfIVK.d.cts +0 -226
  61. package/dist/types-DX8VfIVK.d.ts +0 -226
package/dist/index.cjs CHANGED
@@ -1,8 +1,12 @@
1
- "use strict";
1
+ 'use strict';
2
2
 
3
3
  // src/engine.ts
4
4
  var TWO_PI = Math.PI * 2;
5
5
  var POINTS_PER_PERIOD_UNIT = 50;
6
+ function lerp(start, end, t) {
7
+ return start + (end - start) * t;
8
+ }
9
+ var EMPTY_PARAMS = {};
6
10
  var CircularBuffer = class {
7
11
  constructor(capacity) {
8
12
  this.head = 0;
@@ -45,16 +49,29 @@ var CircularBuffer = class {
45
49
  }
46
50
  };
47
51
  function resolveCurve(curveDef) {
52
+ const period = curveDef.period ?? TWO_PI;
53
+ if (!Number.isFinite(period) || period <= 0) {
54
+ throw new RangeError(`[sarmal] period must be a positive finite number, got ${period}`);
55
+ }
56
+ const speed = curveDef.speed ?? 1;
57
+ if (!Number.isFinite(speed)) {
58
+ throw new RangeError(`[sarmal] speed must be a finite number, got ${speed}`);
59
+ }
48
60
  return {
49
61
  name: curveDef.name,
50
62
  fn: curveDef.fn,
51
- period: curveDef.period ?? TWO_PI,
52
- speed: curveDef.speed ?? 1,
63
+ period,
64
+ speed,
53
65
  skeleton: curveDef.skeleton,
54
- skeletonFn: curveDef.skeletonFn,
66
+ skeletonFn: curveDef.skeletonFn
55
67
  };
56
68
  }
57
69
  function createEngine(curveDef, trailLength = 120) {
70
+ if (!Number.isFinite(trailLength) || trailLength <= 0) {
71
+ throw new RangeError(
72
+ `[sarmal] trailLength must be a positive finite number, got ${trailLength}`
73
+ );
74
+ }
58
75
  let curve = resolveCurve(curveDef);
59
76
  const trail = new CircularBuffer(trailLength);
60
77
  let t = 0;
@@ -67,21 +84,25 @@ function createEngine(curveDef, trailLength = 120) {
67
84
  return c.skeletonFn(sampleT);
68
85
  }
69
86
  if (c.skeleton === "live") {
70
- return c.fn(sampleT, actualTime, {});
87
+ return c.fn(sampleT, actualTime, EMPTY_PARAMS);
71
88
  }
72
- return c.fn(sampleT, 0, {});
89
+ return c.fn(sampleT, 0, EMPTY_PARAMS);
73
90
  }
74
91
  return {
75
92
  tick(deltaTime) {
76
- t = (t + curve.speed * deltaTime) % curve.period;
93
+ let effectiveSpeed = curve.speed;
94
+ if (morphCurveB !== null && _morphAlpha !== null) {
95
+ effectiveSpeed = lerp(curve.speed, morphCurveB.speed, _morphAlpha);
96
+ }
97
+ t = (t + effectiveSpeed * deltaTime) % curve.period;
77
98
  actualTime += deltaTime;
78
99
  if (morphCurveB !== null && _morphAlpha !== null) {
79
- const a = curve.fn(t, actualTime, {});
80
- const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
81
- const b = morphCurveB.fn(tB, actualTime, {});
100
+ const a = curve.fn(t, actualTime, EMPTY_PARAMS);
101
+ const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
102
+ const b = morphCurveB.fn(tB, actualTime, EMPTY_PARAMS);
82
103
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
83
104
  } else {
84
- const point = curve.fn(t, actualTime, {});
105
+ const point = curve.fn(t, actualTime, EMPTY_PARAMS);
85
106
  trail.push(point.x, point.y);
86
107
  }
87
108
  return trail.toArray();
@@ -101,14 +122,14 @@ function createEngine(curveDef, trailLength = 120) {
101
122
  trail.clear();
102
123
  },
103
124
  seek(newT, { clearTrail = false } = {}) {
104
- t = ((newT % curve.period) + curve.period) % curve.period;
125
+ t = (newT % curve.period + curve.period) % curve.period;
105
126
  if (clearTrail) {
106
127
  trail.clear();
107
128
  }
108
129
  },
109
130
  seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
110
131
  const advance = curve.speed * step;
111
- const target = ((targetT % curve.period) + curve.period) % curve.period;
132
+ const target = (targetT % curve.period + curve.period) % curve.period;
112
133
  const targetTime = target / curve.speed;
113
134
  t = target;
114
135
  actualTime = targetTime;
@@ -117,9 +138,9 @@ function createEngine(curveDef, trailLength = 120) {
117
138
  const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
118
139
  for (let i = count - 1; i >= 0; i--) {
119
140
  const sampleT = target - i * advance;
120
- const wrappedT = sampleT < 0 ? sampleT + curve.period : sampleT;
141
+ const wrappedT = (sampleT % curve.period + curve.period) % curve.period;
121
142
  const time = targetTime - i * step;
122
- const point = curve.fn(wrappedT, time, {});
143
+ const point = curve.fn(wrappedT, time, EMPTY_PARAMS);
123
144
  trail.push(point.x, point.y);
124
145
  }
125
146
  },
@@ -134,16 +155,13 @@ function createEngine(curveDef, trailLength = 120) {
134
155
  ...frozenB,
135
156
  fn: (sampleT, time, params) => {
136
157
  const a = frozenA.fn(sampleT, time, params);
137
- const tB =
138
- frozenStrategy === "normalized"
139
- ? (sampleT / frozenA.period) * frozenB.period
140
- : sampleT;
158
+ const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
141
159
  const b = frozenB.fn(tB, time, params);
142
160
  return {
143
161
  x: a.x + (b.x - a.x) * frozenAlpha,
144
- y: a.y + (b.y - a.y) * frozenAlpha,
162
+ y: a.y + (b.y - a.y) * frozenAlpha
145
163
  };
146
- },
164
+ }
147
165
  };
148
166
  }
149
167
  _morphStrategy = strategy;
@@ -156,7 +174,7 @@ function createEngine(curveDef, trailLength = 120) {
156
174
  completeMorph() {
157
175
  if (morphCurveB !== null) {
158
176
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
159
- t = (t / curve.period) * morphCurveB.period;
177
+ t = t / curve.period * morphCurveB.period;
160
178
  }
161
179
  curve = morphCurveB;
162
180
  }
@@ -168,46 +186,132 @@ function createEngine(curveDef, trailLength = 120) {
168
186
  const points = new Array(steps);
169
187
  if (morphCurveB !== null && _morphAlpha !== null) {
170
188
  for (let i = 0; i < steps; i++) {
171
- const sampleT = (i / (steps - 1)) * curve.period;
189
+ const sampleT = i / (steps - 1) * curve.period;
172
190
  const a = sampleSkeleton(curve, sampleT);
173
- const tB =
174
- _morphStrategy === "normalized"
175
- ? (sampleT / curve.period) * morphCurveB.period
176
- : sampleT;
191
+ const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
177
192
  const b = sampleSkeleton(morphCurveB, tB);
178
193
  points[i] = {
179
194
  x: a.x + (b.x - a.x) * _morphAlpha,
180
- y: a.y + (b.y - a.y) * _morphAlpha,
195
+ y: a.y + (b.y - a.y) * _morphAlpha
181
196
  };
182
197
  }
183
198
  return points;
184
199
  }
185
200
  for (let i = 0; i < steps; i++) {
186
- const sampleT = (i / (steps - 1)) * curve.period;
201
+ const sampleT = i / (steps - 1) * curve.period;
187
202
  points[i] = sampleSkeleton(curve, sampleT);
188
203
  }
189
204
  return points;
190
- },
205
+ }
191
206
  };
192
207
  }
193
208
 
194
- // src/renderer.ts
209
+ // src/renderer-shared.ts
195
210
  var DEFAULT_MORPH_DURATION_MS = 300;
196
- var DEFAULT_HEAD_RADIUS = 4;
197
- var DEFAULT_SKELETON_COLOR = "#ffffff";
198
211
  var DEFAULT_SKELETON_OPACITY = 0.15;
199
212
  var FIT_PADDING = 0.1;
200
213
  var TRAIL_FADE_CURVE = 1.5;
201
214
  var TRAIL_MAX_OPACITY = 0.88;
202
215
  var TRAIL_MIN_WIDTH = 0.5;
203
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";
204
308
  var GRADIENT = {
205
309
  bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
206
310
  sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
207
311
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
208
312
  ice: ["#1e3a8a", "#67e8f9"],
209
313
  fire: ["#7f1d1d", "#fbbf24"],
210
- forest: ["#14532d", "#86efac"],
314
+ forest: ["#14532d", "#86efac"]
211
315
  };
212
316
  var PRESETS = {
213
317
  bard: GRADIENT.bard,
@@ -215,16 +319,16 @@ var PRESETS = {
215
319
  ocean: GRADIENT.ocean,
216
320
  ice: GRADIENT.ice,
217
321
  fire: GRADIENT.fire,
218
- forest: GRADIENT.forest,
322
+ forest: GRADIENT.forest
219
323
  };
220
324
  function hexToRgb(hex) {
221
325
  const n = parseInt(hex.slice(1), 16);
222
- return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
326
+ return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
223
327
  }
224
328
  var lerpRgb = (a, b, t) => ({
225
329
  r: Math.round(a.r + (b.r - a.r) * t),
226
330
  g: Math.round(a.g + (b.g - a.g) * t),
227
- b: Math.round(a.b + (b.b - a.b) * t),
331
+ b: Math.round(a.b + (b.b - a.b) * t)
228
332
  });
229
333
  function getPaletteColor(palette, position, timeOffset = 0) {
230
334
  if (palette.length === 0) return { r: 255, g: 255, b: 255 };
@@ -244,33 +348,7 @@ function resolvePalette(palette, trailStyle) {
244
348
  }
245
349
  function hexToRgbComponents(hex) {
246
350
  const n = parseInt(hex.slice(1), 16);
247
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
248
- }
249
- function computeTangent(trail, i) {
250
- const count = trail.length;
251
- if (count < 2) {
252
- return { x: 1, y: 0 };
253
- }
254
- if (i === 0) {
255
- const dx2 = trail[1].x - trail[0].x;
256
- const dy2 = trail[1].y - trail[0].y;
257
- const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
258
- return { x: dx2 / len2, y: dy2 / len2 };
259
- }
260
- if (i === count - 1) {
261
- const dx2 = trail[count - 1].x - trail[count - 2].x;
262
- const dy2 = trail[count - 1].y - trail[count - 2].y;
263
- const len2 = Math.sqrt(dx2 * dx2 + dy2 * dy2) || 1;
264
- return { x: dx2 / len2, y: dy2 / len2 };
265
- }
266
- const dx = trail[i + 1].x - trail[i - 1].x;
267
- const dy = trail[i + 1].y - trail[i - 1].y;
268
- const len = Math.sqrt(dx * dx + dy * dy) || 1;
269
- return { x: dx / len, y: dy / len };
270
- }
271
- function computeNormal(trail, i) {
272
- const tangent = computeTangent(trail, i);
273
- return { x: -tangent.y, y: tangent.x };
351
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
274
352
  }
275
353
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
276
354
  target.style.width = `${logicalWidth}px`;
@@ -289,7 +367,7 @@ function createRenderer(options) {
289
367
  skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
290
368
  trailColor: options.trailColor ?? "#ffffff",
291
369
  headColor: options.headColor ?? "#ffffff",
292
- headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
370
+ headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS
293
371
  };
294
372
  const trailStyle = options.trailStyle ?? "default";
295
373
  const palette = resolvePalette(options.palette, trailStyle);
@@ -319,34 +397,8 @@ function createRenderer(options) {
319
397
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
320
398
  let morphAlpha = 0;
321
399
  let gradientAnimTime = 0;
322
- function computeBoundaries(pts) {
323
- if (pts.length === 0) return null;
324
- const first = pts[0];
325
- let minX = first.x,
326
- maxX = first.x,
327
- minY = first.y,
328
- maxY = first.y;
329
- for (const p of pts) {
330
- if (p.x < minX) minX = p.x;
331
- if (p.x > maxX) maxX = p.x;
332
- if (p.y < minY) minY = p.y;
333
- if (p.y > maxY) maxY = p.y;
334
- }
335
- const width = maxX - minX;
336
- const height = maxY - minY;
337
- const scaleX = logicalWidth / (width * (1 + FIT_PADDING * 2));
338
- const scaleY = logicalHeight / (height * (1 + FIT_PADDING * 2));
339
- const s = Math.min(scaleX, scaleY);
340
- const boundsWidth = width * s;
341
- const boundsHeight = height * s;
342
- return {
343
- scale: s,
344
- offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
345
- offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
346
- };
347
- }
348
400
  function calculateBoundaries() {
349
- const b = computeBoundaries(skeleton);
401
+ const b = computeBoundaries(skeleton, logicalWidth, logicalHeight);
350
402
  if (b) {
351
403
  scale = b.scale;
352
404
  offsetX = b.offsetX;
@@ -410,32 +462,22 @@ function createRenderer(options) {
410
462
  if (trailCount < 2) {
411
463
  return;
412
464
  }
465
+ const toX = (p) => p.x * scale + offsetX;
466
+ const toY = (p) => p.y * scale + offsetY;
413
467
  for (let i = 0; i < trailCount - 1; i++) {
414
- const progress = i / (trailCount - 1);
415
- const nextProgress = (i + 1) / (trailCount - 1);
416
- const alpha = Math.pow(progress, TRAIL_FADE_CURVE) * TRAIL_MAX_OPACITY;
417
- const width = TRAIL_MIN_WIDTH + progress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
418
- const nextWidth = TRAIL_MIN_WIDTH + nextProgress * (TRAIL_MAX_WIDTH - TRAIL_MIN_WIDTH);
419
- const curr = trail[i];
420
- const next = trail[i + 1];
421
- const n0 = computeNormal(trail, i);
422
- const n1 = computeNormal(trail, i + 1);
423
- const halfW0 = width / 2;
424
- const halfW1 = nextWidth / 2;
425
- const l0x = curr.x * scale + offsetX + n0.x * halfW0;
426
- const l0y = curr.y * scale + offsetY + n0.y * halfW0;
427
- const r0x = curr.x * scale + offsetX - n0.x * halfW0;
428
- const r0y = curr.y * scale + offsetY - n0.y * halfW0;
429
- const l1x = next.x * scale + offsetX + n1.x * halfW1;
430
- const l1y = next.y * scale + offsetY + n1.y * halfW1;
431
- const r1x = next.x * scale + offsetX - n1.x * halfW1;
432
- 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
+ );
433
475
  if (trailStyle === "default") {
434
- ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
476
+ ctx.fillStyle = `rgba(${trailRgb},${opacity})`;
435
477
  } else {
436
478
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
437
479
  const color = getPaletteColor(palette, progress, timeOffset);
438
- ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`;
480
+ ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
439
481
  }
440
482
  ctx.beginPath();
441
483
  ctx.moveTo(l0x, l0y);
@@ -468,7 +510,7 @@ function createRenderer(options) {
468
510
  morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
469
511
  engine.setMorphAlpha(morphAlpha);
470
512
  const interpolatedSkeleton = engine.getSarmalSkeleton();
471
- const bounds = computeBoundaries(interpolatedSkeleton);
513
+ const bounds = computeBoundaries(interpolatedSkeleton, logicalWidth, logicalHeight);
472
514
  if (bounds) {
473
515
  scale = bounds.scale;
474
516
  offsetX = bounds.offsetX;
@@ -548,19 +590,33 @@ function createRenderer(options) {
548
590
  return new Promise((resolve) => {
549
591
  morphResolve = resolve;
550
592
  });
551
- },
593
+ }
552
594
  };
553
595
  }
554
596
 
555
597
  // src/renderer-svg.ts
556
- var DEFAULT_MORPH_DURATION_MS2 = 300;
557
598
  var MAX_TRAIL_SEGMENTS = 200;
558
- var TRAIL_FADE_CURVE2 = 1.5;
559
- var TRAIL_MAX_OPACITY2 = 0.88;
560
- var TRAIL_MIN_WIDTH2 = 0.5;
561
- var TRAIL_MAX_WIDTH2 = 2.5;
562
- var DEFAULT_SKELETON_OPACITY2 = 0.15;
563
- var FIT_PADDING2 = 0.1;
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
+ }
564
620
  function el(tag) {
565
621
  return document.createElementNS("http://www.w3.org/2000/svg", tag);
566
622
  }
@@ -571,7 +627,7 @@ function createSVGRenderer(options) {
571
627
  trailColor: options.trailColor ?? "#ffffff",
572
628
  headColor: options.headColor ?? "#ffffff",
573
629
  headRadius: options.headRadius ?? 4,
574
- ariaLabel: options.ariaLabel ?? "Loading",
630
+ ariaLabel: options.ariaLabel ?? "Loading"
575
631
  };
576
632
  const rect = container.getBoundingClientRect();
577
633
  const width = rect.width || 200;
@@ -588,7 +644,7 @@ function createSVGRenderer(options) {
588
644
  const skeletonPath = el("path");
589
645
  skeletonPath.setAttribute("fill", "none");
590
646
  skeletonPath.setAttribute("stroke", opts.skeletonColor);
591
- skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY2));
647
+ skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
592
648
  skeletonPath.setAttribute("stroke-width", "1.5");
593
649
  svg.appendChild(skeletonPath);
594
650
  const skeletonPathA = el("path");
@@ -620,36 +676,13 @@ function createSVGRenderer(options) {
620
676
  let scale = 1;
621
677
  let offsetX = 0;
622
678
  let offsetY = 0;
623
- function calculateBoundaries(skeleton2) {
624
- if (skeleton2.length === 0) {
625
- return;
626
- }
627
- const first = skeleton2[0];
628
- let minX = first.x,
629
- maxX = first.x,
630
- minY = first.y,
631
- maxY = first.y;
632
- for (const p of skeleton2) {
633
- if (p.x < minX) {
634
- minX = p.x;
635
- }
636
- if (p.x > maxX) {
637
- maxX = p.x;
638
- }
639
- if (p.y < minY) {
640
- minY = p.y;
641
- }
642
- if (p.y > maxY) {
643
- maxY = p.y;
644
- }
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;
645
685
  }
646
- const w = maxX - minX;
647
- const h = maxY - minY;
648
- const scaleX = width / (w * (1 + FIT_PADDING2 * 2));
649
- const scaleY = height / (h * (1 + FIT_PADDING2 * 2));
650
- scale = Math.min(scaleX, scaleY);
651
- offsetX = (width - w * scale) / 2 - minX * scale;
652
- offsetY = (height - h * scale) / 2 - minY * scale;
653
686
  }
654
687
  function px(p) {
655
688
  return p.x * scale + offsetX;
@@ -657,26 +690,11 @@ function createSVGRenderer(options) {
657
690
  function py(p) {
658
691
  return p.y * scale + offsetY;
659
692
  }
660
- function pxStr(p) {
661
- return px(p).toFixed(2);
662
- }
663
- function pyStr(p) {
664
- return py(p).toFixed(2);
665
- }
666
693
  function updateSkeleton(skeleton2) {
667
- if (skeleton2.length < 2) {
668
- skeletonPath.setAttribute("d", "");
669
- return;
670
- }
671
- let d = `M${pxStr(skeleton2[0])} ${pyStr(skeleton2[0])}`;
672
- for (let i = 1; i < skeleton2.length; i++) {
673
- d += ` L${pxStr(skeleton2[i])} ${pyStr(skeleton2[i])}`;
674
- }
675
- d += " Z";
676
- skeletonPath.setAttribute("d", d);
694
+ skeletonPath.setAttribute("d", pointsToPathString(skeleton2, scale, offsetX, offsetY));
677
695
  }
678
696
  const skeleton = engine.getSarmalSkeleton();
679
- calculateBoundaries(skeleton);
697
+ applyBoundaries(skeleton);
680
698
  if (!engine.isLiveSkeleton) {
681
699
  updateSkeleton(skeleton);
682
700
  }
@@ -688,25 +706,13 @@ function createSVGRenderer(options) {
688
706
  return;
689
707
  }
690
708
  for (let i = 0; i < trailCount - 1; i++) {
691
- const progress = i / (trailCount - 1);
692
- const nextProgress = (i + 1) / (trailCount - 1);
693
- const opacity = Math.pow(progress, TRAIL_FADE_CURVE2) * TRAIL_MAX_OPACITY2;
694
- const width2 = TRAIL_MIN_WIDTH2 + progress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
695
- const nextWidth = TRAIL_MIN_WIDTH2 + nextProgress * (TRAIL_MAX_WIDTH2 - TRAIL_MIN_WIDTH2);
696
- const curr = trail[i];
697
- const next = trail[i + 1];
698
- const n0 = computeNormal(trail, i);
699
- const n1 = computeNormal(trail, i + 1);
700
- const halfW0 = width2 / 2;
701
- const halfW1 = nextWidth / 2;
702
- const l0x = px(curr) + n0.x * halfW0;
703
- const l0y = py(curr) + n0.y * halfW0;
704
- const r0x = px(curr) - n0.x * halfW0;
705
- const r0y = py(curr) - n0.y * halfW0;
706
- const l1x = px(next) + n1.x * halfW1;
707
- const l1y = py(next) + n1.y * halfW1;
708
- const r1x = px(next) - n1.x * halfW1;
709
- 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
+ );
710
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`;
711
717
  trailPaths[i].setAttribute("d", d);
712
718
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
@@ -727,33 +733,11 @@ function createSVGRenderer(options) {
727
733
  }
728
734
  let animationId = null;
729
735
  let lastTime = 0;
730
- const prefersReducedMotion =
731
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
736
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
732
737
  let morphResolve = null;
733
- let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
738
+ let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
734
739
  let morphTarget = null;
735
740
  let morphAlpha = 0;
736
- function buildSkeletonPath(target, scale2, offsetX2, offsetY2) {
737
- const period = target.period ?? Math.PI * 2;
738
- const samples = Math.max(50, Math.round(period * 20));
739
- const points = [];
740
- for (let i = 0; i <= samples; i++) {
741
- const t = (i / samples) * period;
742
- const p = target.fn(t, 0, {});
743
- points.push(p);
744
- }
745
- if (points.length < 2) {
746
- return "";
747
- }
748
- const px2 = (p) => (p.x * scale2 + offsetX2).toFixed(2);
749
- const py2 = (p) => (p.y * scale2 + offsetY2).toFixed(2);
750
- let d = `M${px2(points[0])} ${py2(points[0])}`;
751
- for (let i = 1; i < points.length; i++) {
752
- d += ` L${px2(points[i])} ${py2(points[i])}`;
753
- }
754
- d += " Z";
755
- return d;
756
- }
757
741
  function renderFrame() {
758
742
  const now = performance.now();
759
743
  const dt = Math.min((now - lastTime) / 1e3, 1 / 30);
@@ -766,16 +750,13 @@ function createSVGRenderer(options) {
766
750
  skeletonPathA.setAttribute("visibility", "visible");
767
751
  skeletonPathA.setAttribute(
768
752
  "stroke-opacity",
769
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
753
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
770
754
  );
771
755
  }
772
756
  if (morphPathBBuilt) {
773
757
  skeletonPathB.setAttribute("d", morphPathBBuilt);
774
758
  skeletonPathB.setAttribute("visibility", "visible");
775
- skeletonPathB.setAttribute(
776
- "stroke-opacity",
777
- String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
778
- );
759
+ skeletonPathB.setAttribute("stroke-opacity", String(morphAlpha * DEFAULT_SKELETON_OPACITY));
779
760
  }
780
761
  if (morphAlpha >= 1) {
781
762
  engine.completeMorph();
@@ -788,7 +769,7 @@ function createSVGRenderer(options) {
788
769
  skeletonPathA.setAttribute("visibility", "hidden");
789
770
  skeletonPathB.setAttribute("visibility", "hidden");
790
771
  const newSkeleton = engine.getSarmalSkeleton();
791
- calculateBoundaries(newSkeleton);
772
+ applyBoundaries(newSkeleton);
792
773
  updateSkeleton(newSkeleton);
793
774
  }
794
775
  }
@@ -796,7 +777,7 @@ function createSVGRenderer(options) {
796
777
  const trailCount = engine.trailCount;
797
778
  if (engine.isLiveSkeleton && engine.morphAlpha === null) {
798
779
  const liveSkeleton = engine.getSarmalSkeleton();
799
- calculateBoundaries(liveSkeleton);
780
+ applyBoundaries(liveSkeleton);
800
781
  updateSkeleton(liveSkeleton);
801
782
  }
802
783
  updateTrail(trail, trailCount);
@@ -845,29 +826,20 @@ function createSVGRenderer(options) {
845
826
  skeletonPathA.setAttribute("visibility", "hidden");
846
827
  skeletonPathB.setAttribute("visibility", "hidden");
847
828
  }
848
- morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS2;
829
+ morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
849
830
  morphTarget = target;
850
831
  morphAlpha = 0;
851
832
  const currentSkeleton = engine.getSarmalSkeleton();
852
- if (currentSkeleton.length >= 2) {
853
- const px2 = (p) => (p.x * scale + offsetX).toFixed(2);
854
- const py2 = (p) => (p.y * scale + offsetY).toFixed(2);
855
- morphPathABuilt = `M${px2(currentSkeleton[0])} ${py2(currentSkeleton[0])}`;
856
- for (let i = 1; i < currentSkeleton.length; i++) {
857
- morphPathABuilt += ` L${px2(currentSkeleton[i])} ${py2(currentSkeleton[i])}`;
858
- }
859
- morphPathABuilt += " Z";
860
- } else {
861
- morphPathABuilt = "";
862
- }
833
+ morphPathABuilt = pointsToPathString(currentSkeleton, scale, offsetX, offsetY);
863
834
  engine.startMorph(target, options2?.morphStrategy);
864
835
  if (morphTarget) {
865
- morphPathBBuilt = buildSkeletonPath(morphTarget, scale, offsetX, offsetY);
836
+ const targetSkeleton = sampleCurveSkeleton(target);
837
+ morphPathBBuilt = pointsToPathString(targetSkeleton, scale, offsetX, offsetY);
866
838
  }
867
839
  return new Promise((resolve) => {
868
840
  morphResolve = resolve;
869
841
  });
870
- },
842
+ }
871
843
  };
872
844
  }
873
845
  function createSarmalSVG(container, curveDef, options) {
@@ -879,22 +851,19 @@ function createSarmalSVG(container, curveDef, options) {
879
851
  // src/curves/artemis2.ts
880
852
  var TWO_PI2 = Math.PI * 2;
881
853
  function artemis2Fn(t, _time, _params) {
882
- const a = 0.35,
883
- b = 0.15,
884
- ox = 0.175;
885
- const s = Math.sin(t),
886
- 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);
887
856
  const denom = 1 + s * s;
888
857
  return {
889
- x: (c * (1 + a * c)) / denom - ox,
890
- y: (s * c * (1 + b * c)) / denom,
858
+ x: c * (1 + a * c) / denom - ox,
859
+ y: s * c * (1 + b * c) / denom
891
860
  };
892
861
  }
893
862
  var artemis2 = {
894
863
  name: "Artemis II",
895
864
  fn: artemis2Fn,
896
865
  period: TWO_PI2,
897
- speed: 0.7,
866
+ speed: 0.7
898
867
  };
899
868
 
900
869
  // src/curves/astroid.ts
@@ -904,14 +873,14 @@ function astroidFn(t, _time, _params) {
904
873
  const s = Math.sin(t);
905
874
  return {
906
875
  x: c * c * c,
907
- y: s * s * s,
876
+ y: s * s * s
908
877
  };
909
878
  }
910
879
  var astroid = {
911
880
  name: "Astroid",
912
881
  fn: astroidFn,
913
882
  period: TWO_PI3,
914
- speed: 1.1,
883
+ speed: 1.1
915
884
  };
916
885
 
917
886
  // src/curves/deltoid.ts
@@ -919,14 +888,14 @@ var TWO_PI4 = Math.PI * 2;
919
888
  function deltoidFn(t, _time, _params) {
920
889
  return {
921
890
  x: 2 * Math.cos(t) + Math.cos(2 * t),
922
- y: 2 * Math.sin(t) - Math.sin(2 * t),
891
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
923
892
  };
924
893
  }
925
894
  var deltoid = {
926
895
  name: "Deltoid",
927
896
  fn: deltoidFn,
928
897
  period: TWO_PI4,
929
- speed: 0.9,
898
+ speed: 0.9
930
899
  };
931
900
 
932
901
  // src/curves/epicycloid3.ts
@@ -934,14 +903,14 @@ var TWO_PI5 = Math.PI * 2;
934
903
  function epicycloid3Fn(t, _time, _params) {
935
904
  return {
936
905
  x: 4 * Math.cos(t) - Math.cos(4 * t),
937
- y: 4 * Math.sin(t) - Math.sin(4 * t),
906
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
938
907
  };
939
908
  }
940
909
  var epicycloid3 = {
941
910
  name: "Epicycloid (n=3)",
942
911
  fn: epicycloid3Fn,
943
912
  period: TWO_PI5,
944
- speed: 0.75,
913
+ speed: 0.75
945
914
  };
946
915
 
947
916
  // src/curves/epitrochoid7.ts
@@ -950,14 +919,14 @@ function epitrochoid7Fn(t, _time, _params) {
950
919
  const d = 1 + 0.55 * Math.sin(t * 0.5);
951
920
  return {
952
921
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
953
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
922
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
954
923
  };
955
924
  }
956
925
  function epitrochoid7SkeletonFn(t) {
957
926
  const d = 1.275;
958
927
  return {
959
928
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
960
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
929
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
961
930
  };
962
931
  }
963
932
  var epitrochoid7 = {
@@ -965,7 +934,7 @@ var epitrochoid7 = {
965
934
  fn: epitrochoid7Fn,
966
935
  period: TWO_PI6,
967
936
  speed: 1.4,
968
- skeletonFn: epitrochoid7SkeletonFn,
937
+ skeletonFn: epitrochoid7SkeletonFn
969
938
  };
970
939
 
971
940
  // src/curves/lissajous32.ts
@@ -974,7 +943,7 @@ function lissajous32Fn(t, time, _params) {
974
943
  const phi = time * 0.45;
975
944
  return {
976
945
  x: Math.sin(3 * t + phi),
977
- y: Math.sin(2 * t),
946
+ y: Math.sin(2 * t)
978
947
  };
979
948
  }
980
949
  var lissajous32 = {
@@ -982,7 +951,7 @@ var lissajous32 = {
982
951
  fn: lissajous32Fn,
983
952
  period: TWO_PI7,
984
953
  speed: 2,
985
- skeleton: "live",
954
+ skeleton: "live"
986
955
  };
987
956
 
988
957
  // src/curves/lissajous43.ts
@@ -991,7 +960,7 @@ function lissajous43Fn(t, time, _params) {
991
960
  const phi = time * 0.38;
992
961
  return {
993
962
  x: Math.sin(4 * t + phi),
994
- y: Math.sin(3 * t),
963
+ y: Math.sin(3 * t)
995
964
  };
996
965
  }
997
966
  var lissajous43 = {
@@ -999,18 +968,17 @@ var lissajous43 = {
999
968
  fn: lissajous43Fn,
1000
969
  period: TWO_PI8,
1001
970
  speed: 1.8,
1002
- skeleton: "live",
971
+ skeleton: "live"
1003
972
  };
1004
973
 
1005
974
  // src/curves/lame.ts
1006
975
  var TWO_PI9 = Math.PI * 2;
1007
976
  function lameFn(t, time, _params) {
1008
977
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1009
- const c = Math.cos(t),
1010
- s = Math.sin(t);
978
+ const c = Math.cos(t), s = Math.sin(t);
1011
979
  return {
1012
980
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1013
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
981
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
1014
982
  };
1015
983
  }
1016
984
  var lame = {
@@ -1018,7 +986,7 @@ var lame = {
1018
986
  fn: lameFn,
1019
987
  period: TWO_PI9,
1020
988
  speed: 1,
1021
- skeleton: "live",
989
+ skeleton: "live"
1022
990
  };
1023
991
 
1024
992
  // src/curves/rose3.ts
@@ -1027,14 +995,14 @@ function rose3Fn(t, _time, _params) {
1027
995
  const r = Math.cos(3 * t);
1028
996
  return {
1029
997
  x: r * Math.cos(t),
1030
- y: r * Math.sin(t),
998
+ y: r * Math.sin(t)
1031
999
  };
1032
1000
  }
1033
1001
  var rose3 = {
1034
1002
  name: "Rose (n=3)",
1035
1003
  fn: rose3Fn,
1036
1004
  period: TWO_PI10,
1037
- speed: 1.15,
1005
+ speed: 1.15
1038
1006
  };
1039
1007
 
1040
1008
  // src/curves/rose5.ts
@@ -1043,14 +1011,14 @@ function rose5Fn(t, _time, _params) {
1043
1011
  const r = Math.cos(5 * t);
1044
1012
  return {
1045
1013
  x: r * Math.cos(t),
1046
- y: r * Math.sin(t),
1014
+ y: r * Math.sin(t)
1047
1015
  };
1048
1016
  }
1049
1017
  var rose5 = {
1050
1018
  name: "Rose (n=5)",
1051
1019
  fn: rose5Fn,
1052
1020
  period: TWO_PI11,
1053
- speed: 1,
1021
+ speed: 1
1054
1022
  };
1055
1023
 
1056
1024
  // src/curves/index.ts
@@ -1064,7 +1032,7 @@ var curves = {
1064
1032
  lissajous32,
1065
1033
  lissajous43,
1066
1034
  epicycloid3,
1067
- lame,
1035
+ lame
1068
1036
  };
1069
1037
 
1070
1038
  // src/index.ts
@@ -1091,4 +1059,4 @@ exports.lissajous43 = lissajous43;
1091
1059
  exports.rose3 = rose3;
1092
1060
  exports.rose5 = rose5;
1093
1061
  //# sourceMappingURL=index.cjs.map
1094
- //# sourceMappingURL=index.cjs.map
1062
+ //# sourceMappingURL=index.cjs.map