@sarmal/core 0.14.0 → 0.15.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 +239 -115
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.d.cts +1 -2
  4. package/dist/auto-init.d.ts +1 -2
  5. package/dist/auto-init.js +238 -114
  6. package/dist/auto-init.js.map +1 -1
  7. package/dist/curves/artemis2.cjs +10 -7
  8. package/dist/curves/artemis2.d.cts +1 -1
  9. package/dist/curves/artemis2.d.ts +1 -1
  10. package/dist/curves/artemis2.js +9 -6
  11. package/dist/curves/astroid.cjs +4 -4
  12. package/dist/curves/astroid.d.cts +1 -1
  13. package/dist/curves/astroid.d.ts +1 -1
  14. package/dist/curves/astroid.js +3 -3
  15. package/dist/curves/deltoid.cjs +4 -4
  16. package/dist/curves/deltoid.d.cts +1 -1
  17. package/dist/curves/deltoid.d.ts +1 -1
  18. package/dist/curves/deltoid.js +3 -3
  19. package/dist/curves/epicycloid3.cjs +4 -4
  20. package/dist/curves/epicycloid3.d.cts +1 -1
  21. package/dist/curves/epicycloid3.d.ts +1 -1
  22. package/dist/curves/epicycloid3.js +3 -3
  23. package/dist/curves/epitrochoid7.cjs +5 -5
  24. package/dist/curves/epitrochoid7.d.cts +1 -1
  25. package/dist/curves/epitrochoid7.d.ts +1 -1
  26. package/dist/curves/epitrochoid7.js +4 -4
  27. package/dist/curves/index.cjs +32 -28
  28. package/dist/curves/index.d.cts +21 -21
  29. package/dist/curves/index.d.ts +21 -21
  30. package/dist/curves/index.js +44 -28
  31. package/dist/curves/lame.cjs +6 -5
  32. package/dist/curves/lame.d.cts +1 -1
  33. package/dist/curves/lame.d.ts +1 -1
  34. package/dist/curves/lame.js +5 -4
  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 +303 -123
  52. package/dist/index.cjs.map +1 -1
  53. package/dist/index.d.cts +73 -23
  54. package/dist/index.d.ts +73 -23
  55. package/dist/index.js +320 -123
  56. package/dist/index.js.map +1 -1
  57. package/dist/types-BL9HhEmk.d.cts +312 -0
  58. package/dist/types-BL9HhEmk.d.ts +312 -0
  59. package/package.json +1 -1
  60. package/dist/types-BQosOzlf.d.cts +0 -276
  61. package/dist/types-BQosOzlf.d.ts +0 -276
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);
@@ -110,7 +110,7 @@ function createEngine(curveDef, trailLength = 120) {
110
110
  actualTime += deltaTime;
111
111
  if (morphCurveB !== null && _morphAlpha !== null) {
112
112
  const a = curve.fn(t, actualTime, EMPTY_PARAMS);
113
- const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
113
+ const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
114
114
  const b = morphCurveB.fn(tB, actualTime, EMPTY_PARAMS);
115
115
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
116
116
  } else {
@@ -134,14 +134,14 @@ function createEngine(curveDef, trailLength = 120) {
134
134
  trail.clear();
135
135
  },
136
136
  jump(newT, { clearTrail = false } = {}) {
137
- t = (newT % curve.period + curve.period) % curve.period;
137
+ t = ((newT % curve.period) + curve.period) % curve.period;
138
138
  if (clearTrail) {
139
139
  trail.clear();
140
140
  }
141
141
  },
142
142
  seek(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
143
143
  const advance = curve.speed * step;
144
- const target = (targetT % curve.period + curve.period) % curve.period;
144
+ const target = ((targetT % curve.period) + curve.period) % curve.period;
145
145
  const targetTime = target / curve.speed;
146
146
  t = target;
147
147
  actualTime = targetTime;
@@ -150,7 +150,7 @@ function createEngine(curveDef, trailLength = 120) {
150
150
  const count = wrap ? trailLength : Math.min(trailLength, pointsFromStart);
151
151
  for (let i = count - 1; i >= 0; i--) {
152
152
  const sampleT = target - i * advance;
153
- const wrappedT = (sampleT % curve.period + curve.period) % curve.period;
153
+ const wrappedT = ((sampleT % curve.period) + curve.period) % curve.period;
154
154
  const time = targetTime - i * step;
155
155
  const point = curve.fn(wrappedT, time, EMPTY_PARAMS);
156
156
  trail.push(point.x, point.y);
@@ -167,13 +167,16 @@ function createEngine(curveDef, trailLength = 120) {
167
167
  ...frozenB,
168
168
  fn: (sampleT, time, params) => {
169
169
  const a = frozenA.fn(sampleT, time, params);
170
- const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
170
+ const tB =
171
+ frozenStrategy === "normalized"
172
+ ? (sampleT / frozenA.period) * frozenB.period
173
+ : sampleT;
171
174
  const b = frozenB.fn(tB, time, params);
172
175
  return {
173
176
  x: a.x + (b.x - a.x) * frozenAlpha,
174
- y: a.y + (b.y - a.y) * frozenAlpha
177
+ y: a.y + (b.y - a.y) * frozenAlpha,
175
178
  };
176
- }
179
+ },
177
180
  };
178
181
  }
179
182
  _morphStrategy = strategy;
@@ -186,7 +189,7 @@ function createEngine(curveDef, trailLength = 120) {
186
189
  completeMorph() {
187
190
  if (morphCurveB !== null) {
188
191
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
189
- t = t / curve.period * morphCurveB.period;
192
+ t = (t / curve.period) * morphCurveB.period;
190
193
  }
191
194
  curve = morphCurveB;
192
195
  }
@@ -198,19 +201,22 @@ function createEngine(curveDef, trailLength = 120) {
198
201
  const points = new Array(steps);
199
202
  if (morphCurveB !== null && _morphAlpha !== null) {
200
203
  for (let i = 0; i < steps; i++) {
201
- const sampleT = i / (steps - 1) * curve.period;
204
+ const sampleT = (i / (steps - 1)) * curve.period;
202
205
  const a = sampleSkeleton(curve, sampleT);
203
- const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
206
+ const tB =
207
+ _morphStrategy === "normalized"
208
+ ? (sampleT / curve.period) * morphCurveB.period
209
+ : sampleT;
204
210
  const b = sampleSkeleton(morphCurveB, tB);
205
211
  points[i] = {
206
212
  x: a.x + (b.x - a.x) * _morphAlpha,
207
- y: a.y + (b.y - a.y) * _morphAlpha
213
+ y: a.y + (b.y - a.y) * _morphAlpha,
208
214
  };
209
215
  }
210
216
  return points;
211
217
  }
212
218
  for (let i = 0; i < steps; i++) {
213
- const sampleT = i / (steps - 1) * curve.period;
219
+ const sampleT = (i / (steps - 1)) * curve.period;
214
220
  points[i] = sampleSkeleton(curve, sampleT);
215
221
  }
216
222
  return points;
@@ -252,7 +258,7 @@ function createEngine(curveDef, trailLength = 120) {
252
258
  _speedTransition.reject(new Error("Speed transition cancelled"));
253
259
  _speedTransition = null;
254
260
  }
255
- }
261
+ },
256
262
  };
257
263
  }
258
264
 
@@ -315,13 +321,16 @@ function computeTrailQuad(trail, i, trailCount, toX, toY) {
315
321
  r1x: nx - n1.x * w1,
316
322
  r1y: ny - n1.y * w1,
317
323
  opacity,
318
- progress
324
+ progress,
319
325
  };
320
326
  }
321
327
  function computeBoundaries(pts, logicalWidth, logicalHeight) {
322
328
  if (pts.length === 0) return null;
323
329
  const first = pts[0];
324
- let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
330
+ let minX = first.x,
331
+ maxX = first.x,
332
+ minY = first.y,
333
+ maxY = first.y;
325
334
  for (const p of pts) {
326
335
  if (p.x < minX) {
327
336
  minX = p.x;
@@ -340,7 +349,7 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
340
349
  const h = maxY - minY;
341
350
  if (w === 0 && h === 0) {
342
351
  throw new Error(
343
- "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
352
+ "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t.",
344
353
  );
345
354
  }
346
355
  const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
@@ -351,12 +360,12 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
351
360
  scaleXProportional,
352
361
  scaleYProportional,
353
362
  scaleXMinPadding,
354
- scaleYMinPadding
363
+ scaleYMinPadding,
355
364
  );
356
365
  return {
357
366
  scale,
358
367
  offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
359
- offsetY: (logicalHeight - h * scale) / 2 - minY * scale
368
+ offsetY: (logicalHeight - h * scale) / 2 - minY * scale,
360
369
  };
361
370
  }
362
371
  function enginePassthroughs(engine) {
@@ -366,33 +375,25 @@ function enginePassthroughs(engine) {
366
375
  setSpeed: engine.setSpeed,
367
376
  getSpeed: engine.getSpeed,
368
377
  resetSpeed: engine.resetSpeed,
369
- setSpeedOver: engine.setSpeedOver
378
+ setSpeedOver: engine.setSpeedOver,
370
379
  };
371
380
  }
372
- var GRADIENT = {
381
+ var palettes = {
373
382
  bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
374
383
  sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
375
384
  ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
376
385
  ice: ["#1e3a8a", "#67e8f9"],
377
386
  fire: ["#7f1d1d", "#fbbf24"],
378
- forest: ["#14532d", "#86efac"]
379
- };
380
- var PRESETS = {
381
- bard: GRADIENT.bard,
382
- sunset: GRADIENT.sunset,
383
- ocean: GRADIENT.ocean,
384
- ice: GRADIENT.ice,
385
- fire: GRADIENT.fire,
386
- forest: GRADIENT.forest
387
+ forest: ["#14532d", "#86efac"],
387
388
  };
388
389
  function hexToRgb(hex) {
389
390
  const n = parseInt(hex.slice(1), 16);
390
- return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
391
+ return { r: n >> 16, g: (n >> 8) & 255, b: n & 255 };
391
392
  }
392
393
  var lerpRgb = (a, b, t) => ({
393
394
  r: Math.round(a.r + (b.r - a.r) * t),
394
395
  g: Math.round(a.g + (b.g - a.g) * t),
395
- b: Math.round(a.b + (b.b - a.b) * t)
396
+ b: Math.round(a.b + (b.b - a.b) * t),
396
397
  });
397
398
  function getPaletteColor(palette, position, timeOffset = 0) {
398
399
  if (palette.length === 0) {
@@ -409,21 +410,123 @@ function getPaletteColor(palette, position, timeOffset = 0) {
409
410
  const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
410
411
  return lerpRgb(c1, c2, t);
411
412
  }
412
- function resolvePalette(palette, trailStyle) {
413
- if (Array.isArray(palette)) {
414
- return palette;
413
+ var HEX_COLOR_RE = /^#[0-9a-fA-F]{6}$/;
414
+ var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
415
+ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
416
+ "trailColor",
417
+ "headColor",
418
+ "skeletonColor",
419
+ "trailStyle",
420
+ ]);
421
+ function validateRenderOptions(partial) {
422
+ for (const key of Object.keys(partial)) {
423
+ if (!RENDER_OPTION_KEYS.has(key)) {
424
+ throw new TypeError(`[sarmal] setRenderOptions: unknown key "${key}"`);
425
+ }
426
+ }
427
+ if (partial.trailColor !== void 0) {
428
+ assertTrailColor(partial.trailColor);
429
+ }
430
+ if (partial.headColor !== void 0) {
431
+ assertHeadColor(partial.headColor);
432
+ }
433
+ if (partial.skeletonColor !== void 0) {
434
+ assertSkeletonColor(partial.skeletonColor);
435
+ }
436
+ if (partial.trailStyle !== void 0) {
437
+ assertTrailStyle(partial.trailStyle);
438
+ }
439
+ }
440
+ function assertTrailColor(value) {
441
+ if (typeof value === "string") {
442
+ if (!HEX_COLOR_RE.test(value)) {
443
+ throw new TypeError(
444
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string, got "${value}"`,
445
+ );
446
+ }
447
+ return;
448
+ }
449
+ if (Array.isArray(value)) {
450
+ if (value.length < 2) {
451
+ throw new RangeError(
452
+ `[sarmal] setRenderOptions: trailColor array must have at least 2 entries, got ${value.length}`,
453
+ );
454
+ }
455
+ for (let i = 0; i < value.length; i++) {
456
+ const entry = value[i];
457
+ if (typeof entry !== "string" || !HEX_COLOR_RE.test(entry)) {
458
+ throw new TypeError(
459
+ `[sarmal] setRenderOptions: trailColor[${i}] must be a 6-digit hex string, got ${JSON.stringify(entry)}`,
460
+ );
461
+ }
462
+ }
463
+ return;
464
+ }
465
+ throw new TypeError(
466
+ `[sarmal] setRenderOptions: trailColor must be a 6-digit hex string or an array of hex strings, got ${JSON.stringify(value)}`,
467
+ );
468
+ }
469
+ function assertHeadColor(value) {
470
+ if (value === null) {
471
+ return;
472
+ }
473
+ if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
474
+ throw new TypeError(
475
+ `[sarmal] setRenderOptions: headColor must be a 6-digit hex string or null, got ${JSON.stringify(value)}`,
476
+ );
477
+ }
478
+ }
479
+ function assertSkeletonColor(value) {
480
+ if (value === "transparent") {
481
+ return;
482
+ }
483
+ if (typeof value !== "string" || !HEX_COLOR_RE.test(value)) {
484
+ throw new TypeError(
485
+ `[sarmal] setRenderOptions: skeletonColor must be a 6-digit hex string or "transparent", got ${JSON.stringify(value)}`,
486
+ );
487
+ }
488
+ }
489
+ function assertTrailStyle(value) {
490
+ if (!TRAIL_STYLES.includes(value)) {
491
+ throw new RangeError(
492
+ `[sarmal] setRenderOptions: trailStyle must be one of "default", "gradient-static", "gradient-animated", got ${JSON.stringify(value)}`,
493
+ );
494
+ }
495
+ }
496
+ function resolveTrailMainColor(trailColor) {
497
+ return typeof trailColor === "string" ? trailColor : trailColor[0];
498
+ }
499
+ function resolveTrailPalette(trailColor) {
500
+ return typeof trailColor === "string" ? [trailColor] : trailColor;
501
+ }
502
+ function resolveHeadColor(trailColor, trailStyle) {
503
+ if (trailStyle === "default") {
504
+ return resolveTrailMainColor(trailColor);
505
+ }
506
+ const palette = resolveTrailPalette(trailColor);
507
+ const last = palette[palette.length - 1];
508
+ const { r, g, b } = hexToRgb(last);
509
+ return `rgb(${r},${g},${b})`;
510
+ }
511
+ function warnIfTrailColorMismatch(trailColor, trailStyle) {
512
+ if (trailStyle === "default" && Array.isArray(trailColor)) {
513
+ console.warn(
514
+ '[sarmal] trailColor is an array but trailStyle is "default"; only the first color will be used. Pass a gradient trailStyle to use the whole palette.',
515
+ );
516
+ return;
415
517
  }
416
- if (palette && palette in PRESETS) {
417
- return PRESETS[palette];
518
+ if (trailStyle !== "default" && typeof trailColor === "string") {
519
+ console.warn(
520
+ `[sarmal] trailColor is a single color but trailStyle is "${trailStyle}"; the trail will render as a solid color. Pass an array of hex colors to use a real gradient.`,
521
+ );
418
522
  }
419
- return trailStyle === "gradient-animated" ? GRADIENT.bard : GRADIENT.ice;
420
523
  }
421
524
 
422
525
  // src/renderer.ts
423
- var DEFAULT_SKELETON_COLOR = "#ffffff";
526
+ var WHITE_HEX = "#ffffff";
424
527
  function hexToRgbComponents(hex) {
425
528
  const n = parseInt(hex.slice(1), 16);
426
- return `${n >> 16},${n >> 8 & 255},${n & 255}`;
529
+ return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
427
530
  }
428
531
  function applyDprSizing(target, logicalWidth, logicalHeight, dpr) {
429
532
  target.style.width = `${logicalWidth}px`;
@@ -438,22 +541,14 @@ function createRenderer(options) {
438
541
  }
439
542
  const ctx = canvas.getContext("2d");
440
543
  const engine = options.engine;
441
- const trailStyle = options.trailStyle ?? "default";
442
- const trailColor = options.trailColor ?? "#ffffff";
443
- const palette = resolvePalette(options.palette, trailStyle);
444
- function defaultHeadColor() {
445
- if (trailStyle !== "default") {
446
- const { r, g, b } = getPaletteColor(palette, 1);
447
- return `rgb(${r},${g},${b})`;
448
- }
449
- return trailColor;
450
- }
451
- const opts = {
452
- skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
453
- trailColor,
454
- headColor: options.headColor ?? defaultHeadColor()
455
- };
456
- const trailRgb = hexToRgbComponents(opts.trailColor);
544
+ let trailStyle = options.trailStyle ?? "default";
545
+ let trailColor = options.trailColor ?? WHITE_HEX;
546
+ let skeletonColor = options.skeletonColor ?? WHITE_HEX;
547
+ let userHeadColor = options.headColor ?? null;
548
+ let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
549
+ let trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
550
+ let trailPalette = resolveTrailPalette(trailColor);
551
+ warnIfTrailColorMismatch(trailColor, trailStyle);
457
552
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
458
553
  function setupCanvas() {
459
554
  const rect = canvas.getBoundingClientRect();
@@ -488,11 +583,13 @@ function createRenderer(options) {
488
583
  }
489
584
  }
490
585
  function buildSkeletonCanvas() {
491
- if (skeleton.length < 2) return;
586
+ if (skeleton.length < 2) {
587
+ return;
588
+ }
492
589
  skeletonCanvas = new OffscreenCanvas(canvas.width, canvas.height);
493
590
  const skeletonCtx = skeletonCanvas.getContext("2d");
494
591
  skeletonCtx.setTransform(dpr, 0, 0, dpr, 0, 0);
495
- skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
592
+ skeletonCtx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
496
593
  skeletonCtx.lineWidth = 1.5;
497
594
  skeletonCtx.beginPath();
498
595
  const first = skeleton[0];
@@ -504,8 +601,10 @@ function createRenderer(options) {
504
601
  skeletonCtx.stroke();
505
602
  }
506
603
  function drawSkeletonPath(pts, opacity) {
507
- if (pts.length < 2) return;
508
- ctx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${opacity})`;
604
+ if (pts.length < 2) {
605
+ return;
606
+ }
607
+ ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${opacity})`;
509
608
  ctx.lineWidth = 1.5;
510
609
  ctx.beginPath();
511
610
  ctx.moveTo(pts[0].x * scale + offsetX, pts[0].y * scale + offsetY);
@@ -515,7 +614,7 @@ function createRenderer(options) {
515
614
  ctx.stroke();
516
615
  }
517
616
  function drawSkeleton() {
518
- if (opts.skeletonColor === "transparent") {
617
+ if (skeletonColor === "transparent") {
519
618
  return;
520
619
  }
521
620
  if (engine.morphAlpha !== null) {
@@ -526,7 +625,7 @@ function createRenderer(options) {
526
625
  if (skeleton.length < 2) {
527
626
  return;
528
627
  }
529
- ctx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
628
+ ctx.strokeStyle = `rgba(${hexToRgbComponents(skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
530
629
  ctx.lineWidth = 1.5;
531
630
  ctx.beginPath();
532
631
  const first = skeleton[0];
@@ -552,13 +651,13 @@ function createRenderer(options) {
552
651
  i,
553
652
  trailCount,
554
653
  toX,
555
- toY
654
+ toY,
556
655
  );
557
656
  if (trailStyle === "default") {
558
- ctx.fillStyle = `rgba(${trailRgb},${opacity})`;
657
+ ctx.fillStyle = `rgba(${trailSolidRgb},${opacity})`;
559
658
  } else {
560
659
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
561
- const color = getPaletteColor(palette, progress, timeOffset);
660
+ const color = getPaletteColor(trailPalette, progress, timeOffset);
562
661
  ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${opacity})`;
563
662
  }
564
663
  ctx.beginPath();
@@ -576,8 +675,9 @@ function createRenderer(options) {
576
675
  }
577
676
  const x = head.x * scale + offsetX;
578
677
  const y = head.y * scale + offsetY;
579
- const r = options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(logicalWidth, logicalHeight) / 160));
580
- ctx.fillStyle = opts.headColor;
678
+ const r =
679
+ options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(logicalWidth, logicalHeight) / 160));
680
+ ctx.fillStyle = headColor;
581
681
  ctx.beginPath();
582
682
  ctx.arc(x, y, r, 0, Math.PI * 2);
583
683
  ctx.fill();
@@ -677,7 +777,35 @@ function createRenderer(options) {
677
777
  return new Promise((resolve) => {
678
778
  morphResolve = resolve;
679
779
  });
680
- }
780
+ },
781
+ setRenderOptions(partial) {
782
+ validateRenderOptions(partial);
783
+ if (partial.trailColor !== void 0) {
784
+ trailColor = partial.trailColor;
785
+ trailSolidRgb = hexToRgbComponents(resolveTrailMainColor(trailColor));
786
+ trailPalette = resolveTrailPalette(trailColor);
787
+ }
788
+ if (partial.skeletonColor !== void 0) {
789
+ skeletonColor = partial.skeletonColor;
790
+ if (skeletonColor !== "transparent" && !engine.isLiveSkeleton) {
791
+ buildSkeletonCanvas();
792
+ }
793
+ }
794
+ if (partial.trailStyle !== void 0) {
795
+ trailStyle = partial.trailStyle;
796
+ }
797
+ if (partial.headColor !== void 0) {
798
+ userHeadColor = partial.headColor;
799
+ }
800
+ if (userHeadColor === null) {
801
+ headColor = resolveHeadColor(trailColor, trailStyle);
802
+ } else {
803
+ headColor = userHeadColor;
804
+ }
805
+ if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
806
+ warnIfTrailColorMismatch(trailColor, trailStyle);
807
+ }
808
+ },
681
809
  };
682
810
  if (shouldAutoStart) {
683
811
  instance.play();
@@ -703,7 +831,7 @@ function sampleCurveSkeleton(curveDef) {
703
831
  const samples = Math.ceil(period * 50);
704
832
  const pts = Array.from({ length: samples });
705
833
  for (let i = 0; i < samples; i++) {
706
- const t = i / (samples - 1) * period;
834
+ const t = (i / (samples - 1)) * period;
707
835
  pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
708
836
  }
709
837
  return pts;
@@ -713,47 +841,48 @@ function el(tag) {
713
841
  }
714
842
  function createSVGRenderer(options) {
715
843
  const { container, engine } = options;
716
- const trailColor = options.trailColor ?? "#ffffff";
717
- const trailStyle = options.trailStyle ?? "default";
718
- const palette = resolvePalette(options.palette, trailStyle);
719
- const opts = {
720
- skeletonColor: options.skeletonColor ?? "#ffffff",
721
- trailColor,
722
- headColor: options.headColor ?? (trailStyle !== "default" ? (() => {
723
- const { r, g, b } = getPaletteColor(palette, 1);
724
- return `rgb(${r},${g},${b})`;
725
- })() : trailColor),
726
- ariaLabel: options.ariaLabel ?? "Loading"
727
- };
844
+ let trailStyle = options.trailStyle ?? "default";
845
+ let trailColor = options.trailColor ?? "#ffffff";
846
+ let skeletonColor = options.skeletonColor ?? "#ffffff";
847
+ let userHeadColor = options.headColor ?? null;
848
+ let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
849
+ let trailSolid = resolveTrailMainColor(trailColor);
850
+ let trailPalette = resolveTrailPalette(trailColor);
851
+ const ariaLabel = options.ariaLabel ?? "Loading";
852
+ warnIfTrailColorMismatch(trailColor, trailStyle);
728
853
  const rect = container.getBoundingClientRect();
729
854
  const width = rect.width || 200;
730
855
  const height = rect.height || 200;
731
- const headRadius = options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(width, height) / 160));
856
+ const headRadius =
857
+ options.headRadius ?? Math.max(2, 3 * Math.sqrt(Math.min(width, height) / 160));
732
858
  const svg = el("svg");
733
859
  svg.setAttribute("width", String(width));
734
860
  svg.setAttribute("height", String(height));
735
861
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
736
862
  svg.setAttribute("role", "img");
737
- svg.setAttribute("aria-label", opts.ariaLabel);
863
+ svg.setAttribute("aria-label", ariaLabel);
738
864
  const titleEl = el("title");
739
- titleEl.textContent = opts.ariaLabel;
865
+ titleEl.textContent = ariaLabel;
740
866
  svg.appendChild(titleEl);
741
867
  const skeletonPath = el("path");
742
868
  skeletonPath.setAttribute("data-sarmal-role", "skeleton");
743
869
  skeletonPath.setAttribute("fill", "none");
744
- skeletonPath.setAttribute("stroke", opts.skeletonColor);
870
+ skeletonPath.setAttribute("stroke", skeletonColor);
745
871
  skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
746
872
  skeletonPath.setAttribute("stroke-width", "1.5");
873
+ if (skeletonColor === "transparent") {
874
+ skeletonPath.setAttribute("visibility", "hidden");
875
+ }
747
876
  svg.appendChild(skeletonPath);
748
877
  const skeletonPathA = el("path");
749
878
  skeletonPathA.setAttribute("fill", "none");
750
- skeletonPathA.setAttribute("stroke", opts.skeletonColor);
879
+ skeletonPathA.setAttribute("stroke", skeletonColor);
751
880
  skeletonPathA.setAttribute("stroke-width", "1.5");
752
881
  skeletonPathA.setAttribute("visibility", "hidden");
753
882
  svg.appendChild(skeletonPathA);
754
883
  const skeletonPathB = el("path");
755
884
  skeletonPathB.setAttribute("fill", "none");
756
- skeletonPathB.setAttribute("stroke", opts.skeletonColor);
885
+ skeletonPathB.setAttribute("stroke", skeletonColor);
757
886
  skeletonPathB.setAttribute("stroke-width", "1.5");
758
887
  skeletonPathB.setAttribute("visibility", "hidden");
759
888
  svg.appendChild(skeletonPathB);
@@ -762,13 +891,13 @@ function createSVGRenderer(options) {
762
891
  const trailPaths = [];
763
892
  for (let i = 0; i < MAX_TRAIL_SEGMENTS; i++) {
764
893
  const path = el("path");
765
- path.setAttribute("fill", opts.trailColor);
894
+ path.setAttribute("fill", trailSolid);
766
895
  svg.appendChild(path);
767
896
  trailPaths.push(path);
768
897
  }
769
898
  const headCircle = el("circle");
770
899
  headCircle.setAttribute("data-sarmal-role", "head");
771
- headCircle.setAttribute("fill", opts.headColor);
900
+ headCircle.setAttribute("fill", headColor);
772
901
  headCircle.setAttribute("r", String(headRadius));
773
902
  svg.appendChild(headCircle);
774
903
  container.appendChild(svg);
@@ -811,14 +940,14 @@ function createSVGRenderer(options) {
811
940
  i,
812
941
  trailCount,
813
942
  px,
814
- py
943
+ py,
815
944
  );
816
945
  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`;
817
946
  trailPaths[i].setAttribute("d", d);
818
947
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
819
948
  if (trailStyle !== "default") {
820
949
  const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
821
- const { r, g, b } = getPaletteColor(palette, progress, timeOffset);
950
+ const { r, g, b } = getPaletteColor(trailPalette, progress, timeOffset);
822
951
  trailPaths[i].setAttribute("fill", `rgb(${r},${g},${b})`);
823
952
  }
824
953
  }
@@ -838,7 +967,8 @@ function createSVGRenderer(options) {
838
967
  }
839
968
  let animationId = null;
840
969
  let lastTime = 0;
841
- const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
970
+ const prefersReducedMotion =
971
+ typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
842
972
  let morphResolve = null;
843
973
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
844
974
  let morphTarget = null;
@@ -855,7 +985,7 @@ function createSVGRenderer(options) {
855
985
  skeletonPathA.setAttribute("visibility", "visible");
856
986
  skeletonPathA.setAttribute(
857
987
  "stroke-opacity",
858
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY)
988
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
859
989
  );
860
990
  }
861
991
  if (morphPathBBuilt) {
@@ -951,7 +1081,52 @@ function createSVGRenderer(options) {
951
1081
  return new Promise((resolve) => {
952
1082
  morphResolve = resolve;
953
1083
  });
954
- }
1084
+ },
1085
+ setRenderOptions(partial) {
1086
+ validateRenderOptions(partial);
1087
+ const prevTrailStyle = trailStyle;
1088
+ if (partial.trailColor !== void 0) {
1089
+ trailColor = partial.trailColor;
1090
+ trailSolid = resolveTrailMainColor(trailColor);
1091
+ trailPalette = resolveTrailPalette(trailColor);
1092
+ if (trailStyle === "default") {
1093
+ for (const p of trailPaths) {
1094
+ p.setAttribute("fill", trailSolid);
1095
+ }
1096
+ }
1097
+ }
1098
+ if (partial.skeletonColor !== void 0) {
1099
+ skeletonColor = partial.skeletonColor;
1100
+ if (skeletonColor === "transparent") {
1101
+ skeletonPath.setAttribute("visibility", "hidden");
1102
+ } else {
1103
+ skeletonPath.setAttribute("stroke", skeletonColor);
1104
+ skeletonPath.removeAttribute("visibility");
1105
+ skeletonPathA.setAttribute("stroke", skeletonColor);
1106
+ skeletonPathB.setAttribute("stroke", skeletonColor);
1107
+ }
1108
+ }
1109
+ if (partial.trailStyle !== void 0) {
1110
+ trailStyle = partial.trailStyle;
1111
+ if (prevTrailStyle !== "default" && trailStyle === "default") {
1112
+ for (const p of trailPaths) {
1113
+ p.setAttribute("fill", trailSolid);
1114
+ }
1115
+ }
1116
+ }
1117
+ if (partial.headColor !== void 0) {
1118
+ userHeadColor = partial.headColor;
1119
+ }
1120
+ if (userHeadColor === null) {
1121
+ headColor = resolveHeadColor(trailColor, trailStyle);
1122
+ } else {
1123
+ headColor = userHeadColor;
1124
+ }
1125
+ headCircle.setAttribute("fill", headColor);
1126
+ if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1127
+ warnIfTrailColorMismatch(trailColor, trailStyle);
1128
+ }
1129
+ },
955
1130
  };
956
1131
  if (shouldAutoStart) {
957
1132
  instance.play();
@@ -967,19 +1142,22 @@ function createSarmalSVG(container, curveDef, options) {
967
1142
  // src/curves/artemis2.ts
968
1143
  var TWO_PI2 = Math.PI * 2;
969
1144
  function artemis2Fn(t, _time, _params) {
970
- const a = 0.35, b = 0.15, ox = 0.175;
971
- const s = Math.sin(t), c = Math.cos(t);
1145
+ const a = 0.35,
1146
+ b = 0.15,
1147
+ ox = 0.175;
1148
+ const s = Math.sin(t),
1149
+ c = Math.cos(t);
972
1150
  const denom = 1 + s * s;
973
1151
  return {
974
- x: c * (1 + a * c) / denom - ox,
975
- y: s * c * (1 + b * c) / denom
1152
+ x: (c * (1 + a * c)) / denom - ox,
1153
+ y: (s * c * (1 + b * c)) / denom,
976
1154
  };
977
1155
  }
978
1156
  var artemis2 = {
979
1157
  name: "Artemis II",
980
1158
  fn: artemis2Fn,
981
1159
  period: TWO_PI2,
982
- speed: 0.7
1160
+ speed: 0.7,
983
1161
  };
984
1162
 
985
1163
  // src/curves/astroid.ts
@@ -989,14 +1167,14 @@ function astroidFn(t, _time, _params) {
989
1167
  const s = Math.sin(t);
990
1168
  return {
991
1169
  x: c * c * c,
992
- y: s * s * s
1170
+ y: s * s * s,
993
1171
  };
994
1172
  }
995
1173
  var astroid = {
996
1174
  name: "Astroid",
997
1175
  fn: astroidFn,
998
1176
  period: TWO_PI3,
999
- speed: 1.1
1177
+ speed: 1.1,
1000
1178
  };
1001
1179
 
1002
1180
  // src/curves/deltoid.ts
@@ -1004,14 +1182,14 @@ var TWO_PI4 = Math.PI * 2;
1004
1182
  function deltoidFn(t, _time, _params) {
1005
1183
  return {
1006
1184
  x: 2 * Math.cos(t) + Math.cos(2 * t),
1007
- y: 2 * Math.sin(t) - Math.sin(2 * t)
1185
+ y: 2 * Math.sin(t) - Math.sin(2 * t),
1008
1186
  };
1009
1187
  }
1010
1188
  var deltoid = {
1011
1189
  name: "Deltoid",
1012
1190
  fn: deltoidFn,
1013
1191
  period: TWO_PI4,
1014
- speed: 0.9
1192
+ speed: 0.9,
1015
1193
  };
1016
1194
 
1017
1195
  // src/curves/epicycloid3.ts
@@ -1019,14 +1197,14 @@ var TWO_PI5 = Math.PI * 2;
1019
1197
  function epicycloid3Fn(t, _time, _params) {
1020
1198
  return {
1021
1199
  x: 4 * Math.cos(t) - Math.cos(4 * t),
1022
- y: 4 * Math.sin(t) - Math.sin(4 * t)
1200
+ y: 4 * Math.sin(t) - Math.sin(4 * t),
1023
1201
  };
1024
1202
  }
1025
1203
  var epicycloid3 = {
1026
1204
  name: "Epicycloid (n=3)",
1027
1205
  fn: epicycloid3Fn,
1028
1206
  period: TWO_PI5,
1029
- speed: 0.75
1207
+ speed: 0.75,
1030
1208
  };
1031
1209
 
1032
1210
  // src/curves/epitrochoid7.ts
@@ -1035,14 +1213,14 @@ function epitrochoid7Fn(t, _time, _params) {
1035
1213
  const d = 1 + 0.55 * Math.sin(t * 0.5);
1036
1214
  return {
1037
1215
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1038
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1216
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1039
1217
  };
1040
1218
  }
1041
1219
  function epitrochoid7SkeletonFn(t) {
1042
1220
  const d = 1.275;
1043
1221
  return {
1044
1222
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
1045
- y: 7 * Math.sin(t) - d * Math.sin(7 * t)
1223
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t),
1046
1224
  };
1047
1225
  }
1048
1226
  var epitrochoid7 = {
@@ -1050,7 +1228,7 @@ var epitrochoid7 = {
1050
1228
  fn: epitrochoid7Fn,
1051
1229
  period: TWO_PI6,
1052
1230
  speed: 1.4,
1053
- skeletonFn: epitrochoid7SkeletonFn
1231
+ skeletonFn: epitrochoid7SkeletonFn,
1054
1232
  };
1055
1233
 
1056
1234
  // src/curves/lissajous32.ts
@@ -1059,7 +1237,7 @@ function lissajous32Fn(t, time, _params) {
1059
1237
  const phi = time * 0.45;
1060
1238
  return {
1061
1239
  x: Math.sin(3 * t + phi),
1062
- y: Math.sin(2 * t)
1240
+ y: Math.sin(2 * t),
1063
1241
  };
1064
1242
  }
1065
1243
  var lissajous32 = {
@@ -1067,7 +1245,7 @@ var lissajous32 = {
1067
1245
  fn: lissajous32Fn,
1068
1246
  period: TWO_PI7,
1069
1247
  speed: 2,
1070
- skeleton: "live"
1248
+ skeleton: "live",
1071
1249
  };
1072
1250
 
1073
1251
  // src/curves/lissajous43.ts
@@ -1076,7 +1254,7 @@ function lissajous43Fn(t, time, _params) {
1076
1254
  const phi = time * 0.38;
1077
1255
  return {
1078
1256
  x: Math.sin(4 * t + phi),
1079
- y: Math.sin(3 * t)
1257
+ y: Math.sin(3 * t),
1080
1258
  };
1081
1259
  }
1082
1260
  var lissajous43 = {
@@ -1084,17 +1262,18 @@ var lissajous43 = {
1084
1262
  fn: lissajous43Fn,
1085
1263
  period: TWO_PI8,
1086
1264
  speed: 1.8,
1087
- skeleton: "live"
1265
+ skeleton: "live",
1088
1266
  };
1089
1267
 
1090
1268
  // src/curves/lame.ts
1091
1269
  var TWO_PI9 = Math.PI * 2;
1092
1270
  function lameFn(t, time, _params) {
1093
1271
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
1094
- const c = Math.cos(t), s = Math.sin(t);
1272
+ const c = Math.cos(t),
1273
+ s = Math.sin(t);
1095
1274
  return {
1096
1275
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
1097
- y: Math.sign(s) * Math.pow(Math.abs(s), p)
1276
+ y: Math.sign(s) * Math.pow(Math.abs(s), p),
1098
1277
  };
1099
1278
  }
1100
1279
  var lame = {
@@ -1102,7 +1281,7 @@ var lame = {
1102
1281
  fn: lameFn,
1103
1282
  period: TWO_PI9,
1104
1283
  speed: 1,
1105
- skeleton: "live"
1284
+ skeleton: "live",
1106
1285
  };
1107
1286
 
1108
1287
  // src/curves/rose3.ts
@@ -1111,14 +1290,14 @@ function rose3Fn(t, _time, _params) {
1111
1290
  const r = Math.cos(3 * t);
1112
1291
  return {
1113
1292
  x: r * Math.cos(t),
1114
- y: r * Math.sin(t)
1293
+ y: r * Math.sin(t),
1115
1294
  };
1116
1295
  }
1117
1296
  var rose3 = {
1118
1297
  name: "Rose (n=3)",
1119
1298
  fn: rose3Fn,
1120
1299
  period: TWO_PI10,
1121
- speed: 1.15
1300
+ speed: 1.15,
1122
1301
  };
1123
1302
 
1124
1303
  // src/curves/rose5.ts
@@ -1127,14 +1306,14 @@ function rose5Fn(t, _time, _params) {
1127
1306
  const r = Math.cos(5 * t);
1128
1307
  return {
1129
1308
  x: r * Math.cos(t),
1130
- y: r * Math.sin(t)
1309
+ y: r * Math.sin(t),
1131
1310
  };
1132
1311
  }
1133
1312
  var rose5 = {
1134
1313
  name: "Rose (n=5)",
1135
1314
  fn: rose5Fn,
1136
1315
  period: TWO_PI11,
1137
- speed: 1
1316
+ speed: 1,
1138
1317
  };
1139
1318
 
1140
1319
  // src/curves/index.ts
@@ -1148,7 +1327,7 @@ var curves = {
1148
1327
  lissajous32,
1149
1328
  lissajous43,
1150
1329
  epicycloid3,
1151
- lame
1330
+ lame,
1152
1331
  };
1153
1332
 
1154
1333
  // src/index.ts
@@ -1172,7 +1351,8 @@ exports.epitrochoid7 = epitrochoid7;
1172
1351
  exports.lame = lame;
1173
1352
  exports.lissajous32 = lissajous32;
1174
1353
  exports.lissajous43 = lissajous43;
1354
+ exports.palettes = palettes;
1175
1355
  exports.rose3 = rose3;
1176
1356
  exports.rose5 = rose5;
1177
1357
  //# sourceMappingURL=index.cjs.map
1178
- //# sourceMappingURL=index.cjs.map
1358
+ //# sourceMappingURL=index.cjs.map