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