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