@sarmal/core 0.19.0 → 0.22.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 (45) hide show
  1. package/dist/auto-init.cjs +393 -18
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.d.cts +4 -0
  4. package/dist/auto-init.d.ts +4 -0
  5. package/dist/auto-init.js +393 -18
  6. package/dist/auto-init.js.map +1 -1
  7. package/dist/curves/artemis2.d.cts +1 -1
  8. package/dist/curves/artemis2.d.ts +1 -1
  9. package/dist/curves/astroid.d.cts +1 -1
  10. package/dist/curves/astroid.d.ts +1 -1
  11. package/dist/curves/deltoid.d.cts +1 -1
  12. package/dist/curves/deltoid.d.ts +1 -1
  13. package/dist/curves/epicycloid3.d.cts +1 -1
  14. package/dist/curves/epicycloid3.d.ts +1 -1
  15. package/dist/curves/epitrochoid7.d.cts +1 -1
  16. package/dist/curves/epitrochoid7.d.ts +1 -1
  17. package/dist/curves/index.d.cts +1 -1
  18. package/dist/curves/index.d.ts +1 -1
  19. package/dist/curves/lame.d.cts +1 -1
  20. package/dist/curves/lame.d.ts +1 -1
  21. package/dist/curves/lissajous32.d.cts +1 -1
  22. package/dist/curves/lissajous32.d.ts +1 -1
  23. package/dist/curves/lissajous43.d.cts +1 -1
  24. package/dist/curves/lissajous43.d.ts +1 -1
  25. package/dist/curves/rose3.d.cts +1 -1
  26. package/dist/curves/rose3.d.ts +1 -1
  27. package/dist/curves/rose5.d.cts +1 -1
  28. package/dist/curves/rose5.d.ts +1 -1
  29. package/dist/curves/rose52.d.cts +1 -1
  30. package/dist/curves/rose52.d.ts +1 -1
  31. package/dist/curves/star.d.cts +1 -1
  32. package/dist/curves/star.d.ts +1 -1
  33. package/dist/curves/star4.d.cts +1 -1
  34. package/dist/curves/star4.d.ts +1 -1
  35. package/dist/curves/star7.d.cts +1 -1
  36. package/dist/curves/star7.d.ts +1 -1
  37. package/dist/index.cjs +77 -2
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +45 -3
  40. package/dist/index.d.ts +45 -3
  41. package/dist/index.js +77 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/{types-frtEoAq6.d.cts → types-DVerJ9cl.d.cts} +4 -0
  44. package/dist/{types-frtEoAq6.d.ts → types-DVerJ9cl.d.ts} +4 -0
  45. package/package.json +1 -1
package/dist/auto-init.js CHANGED
@@ -418,6 +418,7 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
418
418
  "headColor",
419
419
  "skeletonColor",
420
420
  "trailStyle",
421
+ "headRadius",
421
422
  ]);
422
423
  function validateRenderOptions(partial) {
423
424
  for (const key of Object.keys(partial)) {
@@ -437,6 +438,9 @@ function validateRenderOptions(partial) {
437
438
  if (partial.trailStyle !== void 0) {
438
439
  assertTrailStyle(partial.trailStyle);
439
440
  }
441
+ if (partial.headRadius !== void 0) {
442
+ assertHeadRadius(partial.headRadius);
443
+ }
440
444
  }
441
445
  function assertTrailColor(value) {
442
446
  if (typeof value === "string") {
@@ -494,6 +498,18 @@ function assertTrailStyle(value) {
494
498
  );
495
499
  }
496
500
  }
501
+ function assertHeadRadius(value) {
502
+ if (typeof value !== "number") {
503
+ throw new TypeError(
504
+ `[sarmal] setRenderOptions: headRadius must be a number, got ${JSON.stringify(value)}`,
505
+ );
506
+ }
507
+ if (!Number.isFinite(value) || value <= 0) {
508
+ throw new TypeError(
509
+ `[sarmal] setRenderOptions: headRadius must be a finite positive number, got ${value}`,
510
+ );
511
+ }
512
+ }
497
513
  function resolveTrailMainColor(trailColor) {
498
514
  return typeof trailColor === "string" ? trailColor : trailColor[0];
499
515
  }
@@ -561,6 +577,7 @@ function createRenderer(options) {
561
577
  setupCanvas();
562
578
  let logicalWidth = canvas.width / dpr;
563
579
  let logicalHeight = canvas.height / dpr;
580
+ let headRadius = options.headRadius ?? getHeadDotRadius(logicalWidth, logicalHeight);
564
581
  let skeleton = [];
565
582
  let skeletonCanvas = null;
566
583
  let trail = [];
@@ -677,7 +694,7 @@ function createRenderer(options) {
677
694
  }
678
695
  const x = head.x * scale + offsetX;
679
696
  const y = head.y * scale + offsetY;
680
- const r = options.headRadius ?? getHeadDotRadius(logicalWidth, logicalHeight);
697
+ const r = headRadius;
681
698
  ctx.fillStyle = headColor;
682
699
  ctx.beginPath();
683
700
  ctx.arc(x, y, r, 0, Math.PI * 2);
@@ -806,6 +823,9 @@ function createRenderer(options) {
806
823
  if (partial.headColor !== void 0) {
807
824
  userHeadColor = partial.headColor;
808
825
  }
826
+ if (partial.headRadius !== void 0) {
827
+ headRadius = partial.headRadius;
828
+ }
809
829
  if (userHeadColor === null) {
810
830
  headColor = resolveHeadColor(trailColor, trailStyle);
811
831
  } else {
@@ -822,6 +842,354 @@ function createRenderer(options) {
822
842
  return instance;
823
843
  }
824
844
 
845
+ // src/renderer-svg.ts
846
+ var EMPTY_PARAMS2 = {};
847
+ var HIGH_TRAIL_LENGTH_THRESHOLD = 5e3;
848
+ function pointsToPathString(pts, scale, offsetX, offsetY) {
849
+ if (pts.length < 2) {
850
+ return "";
851
+ }
852
+ const px = (p) => (p.x * scale + offsetX).toFixed(2);
853
+ const py = (p) => (p.y * scale + offsetY).toFixed(2);
854
+ let d = `M${px(pts[0])} ${py(pts[0])}`;
855
+ for (let i = 1; i < pts.length; i++) {
856
+ d += ` L${px(pts[i])} ${py(pts[i])}`;
857
+ }
858
+ return d + " Z";
859
+ }
860
+ function sampleCurveSkeleton(curveDef) {
861
+ const period = curveDef.period ?? Math.PI * 2;
862
+ const samples = Math.ceil(period * 50);
863
+ const pts = Array.from({ length: samples });
864
+ for (let i = 0; i < samples; i++) {
865
+ const t = (i / (samples - 1)) * period;
866
+ pts[i] = curveDef.skeletonFn ? curveDef.skeletonFn(t) : curveDef.fn(t, 0, EMPTY_PARAMS2);
867
+ }
868
+ return pts;
869
+ }
870
+ function el(tag) {
871
+ return document.createElementNS("http://www.w3.org/2000/svg", tag);
872
+ }
873
+ function createSVGRenderer(options) {
874
+ const { container, engine } = options;
875
+ const poolSize = engine.trailLength;
876
+ if (poolSize > HIGH_TRAIL_LENGTH_THRESHOLD) {
877
+ console.warn(
878
+ `[sarmal] High trailLength in SVG renderer (${poolSize}). Consider using the canvas renderer for long trails.`,
879
+ );
880
+ }
881
+ let trailStyle = options.trailStyle ?? "default";
882
+ let trailColor = options.trailColor ?? "#ffffff";
883
+ let skeletonColor = options.skeletonColor ?? "#ffffff";
884
+ let userHeadColor = options.headColor ?? null;
885
+ let headColor = userHeadColor ?? resolveHeadColor(trailColor, trailStyle);
886
+ let headRadius = options.headRadius ?? 1.5;
887
+ let trailSolid = resolveTrailMainColor(trailColor);
888
+ let trailPalette = resolveTrailPalette(trailColor);
889
+ const ariaLabel = options.ariaLabel ?? "Loading";
890
+ warnIfTrailColorMismatch(trailColor, trailStyle);
891
+ const viewSize = 100;
892
+ const svgTrailMinWidth = 0.25;
893
+ const svgTrailMaxWidth = 1.25;
894
+ const svgSkeletonStrokeWidth = "0.75";
895
+ container.setAttribute("viewBox", `0 0 ${viewSize} ${viewSize}`);
896
+ container.setAttribute("role", "img");
897
+ container.setAttribute("aria-label", ariaLabel);
898
+ const group = el("g");
899
+ const titleEl = el("title");
900
+ titleEl.textContent = ariaLabel;
901
+ group.appendChild(titleEl);
902
+ const skeletonPath = el("path");
903
+ skeletonPath.setAttribute("data-sarmal-role", "skeleton");
904
+ skeletonPath.setAttribute("fill", "none");
905
+ skeletonPath.setAttribute("stroke", skeletonColor);
906
+ skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
907
+ skeletonPath.setAttribute("stroke-width", svgSkeletonStrokeWidth);
908
+ if (skeletonColor === "transparent") {
909
+ skeletonPath.setAttribute("visibility", "hidden");
910
+ }
911
+ group.appendChild(skeletonPath);
912
+ const skeletonPathA = el("path");
913
+ skeletonPathA.setAttribute("fill", "none");
914
+ skeletonPathA.setAttribute("stroke", skeletonColor);
915
+ skeletonPathA.setAttribute("stroke-width", svgSkeletonStrokeWidth);
916
+ skeletonPathA.setAttribute("visibility", "hidden");
917
+ group.appendChild(skeletonPathA);
918
+ const skeletonPathB = el("path");
919
+ skeletonPathB.setAttribute("fill", "none");
920
+ skeletonPathB.setAttribute("stroke", skeletonColor);
921
+ skeletonPathB.setAttribute("stroke-width", svgSkeletonStrokeWidth);
922
+ skeletonPathB.setAttribute("visibility", "hidden");
923
+ group.appendChild(skeletonPathB);
924
+ let morphPathABuilt = "";
925
+ let morphPathBBuilt = "";
926
+ const trailPaths = [];
927
+ for (let i = 0; i < poolSize; i++) {
928
+ const path = el("path");
929
+ path.setAttribute("fill", trailSolid);
930
+ group.appendChild(path);
931
+ trailPaths.push(path);
932
+ }
933
+ const headCircle = el("circle");
934
+ headCircle.setAttribute("data-sarmal-role", "head");
935
+ headCircle.setAttribute("fill", headColor);
936
+ headCircle.setAttribute("r", String(headRadius));
937
+ group.appendChild(headCircle);
938
+ container.appendChild(group);
939
+ let gradientAnimTime = 0;
940
+ let scale = 1;
941
+ let offsetX = 0;
942
+ let offsetY = 0;
943
+ function applyBoundaries(skeleton2) {
944
+ const b = computeBoundaries(skeleton2, viewSize, viewSize);
945
+ if (b) {
946
+ scale = b.scale;
947
+ offsetX = b.offsetX;
948
+ offsetY = b.offsetY;
949
+ }
950
+ }
951
+ function px(p) {
952
+ return p.x * scale + offsetX;
953
+ }
954
+ function py(p) {
955
+ return p.y * scale + offsetY;
956
+ }
957
+ function updateSkeleton(skeleton2) {
958
+ skeletonPath.setAttribute("d", pointsToPathString(skeleton2, scale, offsetX, offsetY));
959
+ }
960
+ const skeleton = engine.getSarmalSkeleton();
961
+ applyBoundaries(skeleton);
962
+ if (!engine.isLiveSkeleton) {
963
+ updateSkeleton(skeleton);
964
+ }
965
+ function updateTrail(trail, trailCount) {
966
+ if (trailCount < 2) {
967
+ for (const p of trailPaths) {
968
+ p.setAttribute("d", "");
969
+ }
970
+ return;
971
+ }
972
+ const drawnCount = trailCount - 1;
973
+ for (let i = 0; i < drawnCount; i++) {
974
+ const { l0x, l0y, r0x, r0y, l1x, l1y, r1x, r1y, opacity, progress } = computeTrailQuad(
975
+ trail,
976
+ i,
977
+ trailCount,
978
+ px,
979
+ py,
980
+ svgTrailMinWidth,
981
+ svgTrailMaxWidth,
982
+ );
983
+ 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`;
984
+ trailPaths[i].setAttribute("d", d);
985
+ trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
986
+ if (trailStyle !== "default") {
987
+ const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
988
+ const { r, g, b } = getPaletteColor(trailPalette, progress, timeOffset);
989
+ trailPaths[i].setAttribute("fill", `rgb(${r},${g},${b})`);
990
+ }
991
+ }
992
+ for (let i = drawnCount; i < trailPaths.length; i++) {
993
+ trailPaths[i].setAttribute("d", "");
994
+ }
995
+ }
996
+ function updateHead(trail, trailCount) {
997
+ if (trailCount === 0) {
998
+ return;
999
+ }
1000
+ const head = trail[trailCount - 1];
1001
+ const x = px(head);
1002
+ const y = py(head);
1003
+ headCircle.setAttribute("cx", String(x));
1004
+ headCircle.setAttribute("cy", String(y));
1005
+ }
1006
+ let animationId = null;
1007
+ let lastTime = 0;
1008
+ const prefersReducedMotion =
1009
+ typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1010
+ let morphResolve = null;
1011
+ let morphReject = null;
1012
+ let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
1013
+ let morphTarget = null;
1014
+ let morphAlpha = 0;
1015
+ function renderFrame(deltaTime) {
1016
+ if (trailStyle === "gradient-animated") {
1017
+ gradientAnimTime += deltaTime * 1e3;
1018
+ }
1019
+ if (engine.morphAlpha !== null) {
1020
+ morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
1021
+ engine.setMorphAlpha(morphAlpha);
1022
+ if (morphPathABuilt) {
1023
+ skeletonPathA.setAttribute("d", morphPathABuilt);
1024
+ skeletonPathA.setAttribute("visibility", "visible");
1025
+ skeletonPathA.setAttribute(
1026
+ "stroke-opacity",
1027
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY),
1028
+ );
1029
+ }
1030
+ if (morphPathBBuilt) {
1031
+ skeletonPathB.setAttribute("d", morphPathBBuilt);
1032
+ skeletonPathB.setAttribute("visibility", "visible");
1033
+ skeletonPathB.setAttribute("stroke-opacity", String(morphAlpha * DEFAULT_SKELETON_OPACITY));
1034
+ }
1035
+ if (morphAlpha >= 1) {
1036
+ engine.completeMorph();
1037
+ morphResolve?.();
1038
+ morphResolve = null;
1039
+ morphReject = null;
1040
+ morphTarget = null;
1041
+ morphAlpha = 0;
1042
+ morphPathABuilt = "";
1043
+ morphPathBBuilt = "";
1044
+ skeletonPathA.setAttribute("visibility", "hidden");
1045
+ skeletonPathB.setAttribute("visibility", "hidden");
1046
+ const newSkeleton = engine.getSarmalSkeleton();
1047
+ applyBoundaries(newSkeleton);
1048
+ updateSkeleton(newSkeleton);
1049
+ }
1050
+ }
1051
+ const trail = engine.tick(deltaTime);
1052
+ const trailCount = engine.trailCount;
1053
+ if (engine.isLiveSkeleton && engine.morphAlpha === null) {
1054
+ const liveSkeleton = engine.getSarmalSkeleton();
1055
+ applyBoundaries(liveSkeleton);
1056
+ updateSkeleton(liveSkeleton);
1057
+ }
1058
+ updateTrail(trail, trailCount);
1059
+ updateHead(trail, trailCount);
1060
+ }
1061
+ function loop() {
1062
+ const now = performance.now();
1063
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
1064
+ lastTime = now;
1065
+ renderFrame(deltaTime);
1066
+ if (!prefersReducedMotion) {
1067
+ animationId = requestAnimationFrame(loop);
1068
+ }
1069
+ }
1070
+ if (options.initialT !== void 0) {
1071
+ engine.seek(options.initialT);
1072
+ }
1073
+ renderFrame(0);
1074
+ const shouldAutoStart = options.autoStart !== false;
1075
+ const instance = {
1076
+ play() {
1077
+ if (animationId !== null) {
1078
+ return;
1079
+ }
1080
+ lastTime = performance.now();
1081
+ loop();
1082
+ },
1083
+ pause() {
1084
+ if (animationId === null) {
1085
+ return;
1086
+ }
1087
+ cancelAnimationFrame(animationId);
1088
+ animationId = null;
1089
+ engine.cancelSpeedTransition();
1090
+ },
1091
+ reset() {
1092
+ engine.reset();
1093
+ },
1094
+ destroy() {
1095
+ if (animationId !== null) {
1096
+ cancelAnimationFrame(animationId);
1097
+ animationId = null;
1098
+ }
1099
+ if (morphReject !== null) {
1100
+ morphReject(new Error("Instance destroyed during morph"));
1101
+ morphResolve = null;
1102
+ morphReject = null;
1103
+ }
1104
+ group.remove();
1105
+ },
1106
+ ...enginePassthroughs(engine),
1107
+ morphTo(target, options2) {
1108
+ if (morphResolve !== null) {
1109
+ engine.completeMorph();
1110
+ morphResolve();
1111
+ morphResolve = null;
1112
+ morphReject = null;
1113
+ morphAlpha = 0;
1114
+ skeletonPathA.setAttribute("visibility", "hidden");
1115
+ skeletonPathB.setAttribute("visibility", "hidden");
1116
+ }
1117
+ morphDurationMs = options2?.duration ?? DEFAULT_MORPH_DURATION_MS;
1118
+ morphTarget = target;
1119
+ morphAlpha = 0;
1120
+ const currentSkeleton = engine.getSarmalSkeleton();
1121
+ morphPathABuilt = pointsToPathString(currentSkeleton, scale, offsetX, offsetY);
1122
+ engine.startMorph(target, options2?.morphStrategy);
1123
+ if (morphTarget) {
1124
+ const targetSkeleton = sampleCurveSkeleton(target);
1125
+ morphPathBBuilt = pointsToPathString(targetSkeleton, scale, offsetX, offsetY);
1126
+ }
1127
+ return new Promise((resolve, reject) => {
1128
+ morphResolve = resolve;
1129
+ morphReject = reject;
1130
+ });
1131
+ },
1132
+ setRenderOptions(partial) {
1133
+ validateRenderOptions(partial);
1134
+ const prevTrailStyle = trailStyle;
1135
+ if (partial.trailColor !== void 0) {
1136
+ trailColor = partial.trailColor;
1137
+ trailSolid = resolveTrailMainColor(trailColor);
1138
+ trailPalette = resolveTrailPalette(trailColor);
1139
+ if (trailStyle === "default") {
1140
+ for (const p of trailPaths) {
1141
+ p.setAttribute("fill", trailSolid);
1142
+ }
1143
+ }
1144
+ }
1145
+ if (partial.skeletonColor !== void 0) {
1146
+ skeletonColor = partial.skeletonColor;
1147
+ if (skeletonColor === "transparent") {
1148
+ skeletonPath.setAttribute("visibility", "hidden");
1149
+ } else {
1150
+ skeletonPath.setAttribute("stroke", skeletonColor);
1151
+ skeletonPath.removeAttribute("visibility");
1152
+ skeletonPathA.setAttribute("stroke", skeletonColor);
1153
+ skeletonPathB.setAttribute("stroke", skeletonColor);
1154
+ }
1155
+ }
1156
+ if (partial.trailStyle !== void 0) {
1157
+ trailStyle = partial.trailStyle;
1158
+ if (prevTrailStyle !== "default" && trailStyle === "default") {
1159
+ for (const p of trailPaths) {
1160
+ p.setAttribute("fill", trailSolid);
1161
+ }
1162
+ }
1163
+ }
1164
+ if (partial.headColor !== void 0) {
1165
+ userHeadColor = partial.headColor;
1166
+ }
1167
+ if (partial.headRadius !== void 0) {
1168
+ headRadius = partial.headRadius;
1169
+ headCircle.setAttribute("r", String(headRadius));
1170
+ }
1171
+ if (userHeadColor === null) {
1172
+ headColor = resolveHeadColor(trailColor, trailStyle);
1173
+ } else {
1174
+ headColor = userHeadColor;
1175
+ }
1176
+ headCircle.setAttribute("fill", headColor);
1177
+ if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1178
+ warnIfTrailColorMismatch(trailColor, trailStyle);
1179
+ }
1180
+ },
1181
+ };
1182
+ if (shouldAutoStart) {
1183
+ instance.play();
1184
+ }
1185
+ return instance;
1186
+ }
1187
+ function createSarmalSVG(container, curveDef, options) {
1188
+ const { trailLength, ...rendererOpts } = options ?? {};
1189
+ const engine = createEngine(curveDef, trailLength);
1190
+ return createSVGRenderer({ container, engine, ...rendererOpts });
1191
+ }
1192
+
825
1193
  // src/curves/artemis2.ts
826
1194
  var TWO_PI2 = Math.PI * 2;
827
1195
  function artemis2Fn(t, _time, _params) {
@@ -1107,10 +1475,24 @@ function parseTrailColor(value) {
1107
1475
  } catch {}
1108
1476
  return value;
1109
1477
  }
1478
+ function buildOptions(el2) {
1479
+ return {
1480
+ ...(el2.dataset.trailColor && {
1481
+ trailColor: parseTrailColor(el2.dataset.trailColor),
1482
+ }),
1483
+ ...(el2.dataset.skeletonColor && { skeletonColor: el2.dataset.skeletonColor }),
1484
+ ...(el2.dataset.headColor && { headColor: el2.dataset.headColor }),
1485
+ ...(el2.dataset.headRadius && { headRadius: parseFloat(el2.dataset.headRadius) }),
1486
+ ...(el2.dataset.trailLength && { trailLength: parseInt(el2.dataset.trailLength, 10) }),
1487
+ ...(el2.dataset.trailStyle && {
1488
+ trailStyle: el2.dataset.trailStyle,
1489
+ }),
1490
+ };
1491
+ }
1110
1492
  function init() {
1111
- const canvases = document.querySelectorAll("canvas[data-sarmal]");
1112
- canvases.forEach((canvas) => {
1113
- const curveName = canvas.getAttribute("data-sarmal");
1493
+ const elements = document.querySelectorAll("canvas[data-sarmal], svg[data-sarmal]");
1494
+ elements.forEach((el2) => {
1495
+ const curveName = el2.getAttribute("data-sarmal");
1114
1496
  if (curveName == null) {
1115
1497
  return console.warn("[sarmal] curveName is required");
1116
1498
  }
@@ -1118,20 +1500,13 @@ function init() {
1118
1500
  if (!curveDef) {
1119
1501
  return console.error(`[sarmal] "${curveName}" is not a valid curve name`);
1120
1502
  }
1121
- const instance = createSarmal(canvas, curveDef, {
1122
- ...(canvas.dataset.trailColor && {
1123
- trailColor: parseTrailColor(canvas.dataset.trailColor),
1124
- }),
1125
- ...(canvas.dataset.skeletonColor && { skeletonColor: canvas.dataset.skeletonColor }),
1126
- ...(canvas.dataset.headColor && { headColor: canvas.dataset.headColor }),
1127
- ...(canvas.dataset.headRadius && { headRadius: parseFloat(canvas.dataset.headRadius) }),
1128
- ...(canvas.dataset.trailLength && { trailLength: parseInt(canvas.dataset.trailLength, 10) }),
1129
- ...(canvas.dataset.trailStyle && {
1130
- trailStyle: canvas.dataset.trailStyle,
1131
- }),
1132
- });
1133
- if (canvas.dataset.speed) {
1134
- instance.setSpeed(parseFloat(canvas.dataset.speed));
1503
+ const options = buildOptions(el2);
1504
+ const instance =
1505
+ el2 instanceof HTMLCanvasElement
1506
+ ? createSarmal(el2, curveDef, options)
1507
+ : createSarmalSVG(el2, curveDef, options);
1508
+ if (el2.dataset.speed) {
1509
+ instance.setSpeed(parseFloat(el2.dataset.speed));
1135
1510
  }
1136
1511
  });
1137
1512
  }