@sarmal/core 0.8.0 → 0.9.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.
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;
@@ -51,7 +51,7 @@ function resolveCurve(curveDef) {
51
51
  period: curveDef.period ?? TWO_PI,
52
52
  speed: curveDef.speed ?? 1,
53
53
  skeleton: curveDef.skeleton,
54
- skeletonFn: curveDef.skeletonFn,
54
+ skeletonFn: curveDef.skeletonFn
55
55
  };
56
56
  }
57
57
  function createEngine(curveDef, trailLength = 120) {
@@ -77,7 +77,7 @@ function createEngine(curveDef, trailLength = 120) {
77
77
  actualTime += deltaTime;
78
78
  if (morphCurveB !== null && _morphAlpha !== null) {
79
79
  const a = curve.fn(t, actualTime, {});
80
- const tB = _morphStrategy === "normalized" ? (t / curve.period) * morphCurveB.period : t;
80
+ const tB = _morphStrategy === "normalized" ? t / curve.period * morphCurveB.period : t;
81
81
  const b = morphCurveB.fn(tB, actualTime, {});
82
82
  trail.push(a.x + (b.x - a.x) * _morphAlpha, a.y + (b.y - a.y) * _morphAlpha);
83
83
  } else {
@@ -101,14 +101,14 @@ function createEngine(curveDef, trailLength = 120) {
101
101
  trail.clear();
102
102
  },
103
103
  seek(newT, { clearTrail = false } = {}) {
104
- t = ((newT % curve.period) + curve.period) % curve.period;
104
+ t = (newT % curve.period + curve.period) % curve.period;
105
105
  if (clearTrail) {
106
106
  trail.clear();
107
107
  }
108
108
  },
109
109
  seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
110
110
  const advance = curve.speed * step;
111
- const target = ((targetT % curve.period) + curve.period) % curve.period;
111
+ const target = (targetT % curve.period + curve.period) % curve.period;
112
112
  const targetTime = target / curve.speed;
113
113
  t = target;
114
114
  actualTime = targetTime;
@@ -134,16 +134,13 @@ function createEngine(curveDef, trailLength = 120) {
134
134
  ...frozenB,
135
135
  fn: (sampleT, time, params) => {
136
136
  const a = frozenA.fn(sampleT, time, params);
137
- const tB =
138
- frozenStrategy === "normalized"
139
- ? (sampleT / frozenA.period) * frozenB.period
140
- : sampleT;
137
+ const tB = frozenStrategy === "normalized" ? sampleT / frozenA.period * frozenB.period : sampleT;
141
138
  const b = frozenB.fn(tB, time, params);
142
139
  return {
143
140
  x: a.x + (b.x - a.x) * frozenAlpha,
144
- y: a.y + (b.y - a.y) * frozenAlpha,
141
+ y: a.y + (b.y - a.y) * frozenAlpha
145
142
  };
146
- },
143
+ }
147
144
  };
148
145
  }
149
146
  _morphStrategy = strategy;
@@ -156,7 +153,7 @@ function createEngine(curveDef, trailLength = 120) {
156
153
  completeMorph() {
157
154
  if (morphCurveB !== null) {
158
155
  if (_morphStrategy === "normalized" && curve.period !== morphCurveB.period) {
159
- t = (t / curve.period) * morphCurveB.period;
156
+ t = t / curve.period * morphCurveB.period;
160
157
  }
161
158
  curve = morphCurveB;
162
159
  }
@@ -168,26 +165,23 @@ function createEngine(curveDef, trailLength = 120) {
168
165
  const points = new Array(steps);
169
166
  if (morphCurveB !== null && _morphAlpha !== null) {
170
167
  for (let i = 0; i < steps; i++) {
171
- const sampleT = (i / (steps - 1)) * curve.period;
168
+ const sampleT = i / (steps - 1) * curve.period;
172
169
  const a = sampleSkeleton(curve, sampleT);
173
- const tB =
174
- _morphStrategy === "normalized"
175
- ? (sampleT / curve.period) * morphCurveB.period
176
- : sampleT;
170
+ const tB = _morphStrategy === "normalized" ? sampleT / curve.period * morphCurveB.period : sampleT;
177
171
  const b = sampleSkeleton(morphCurveB, tB);
178
172
  points[i] = {
179
173
  x: a.x + (b.x - a.x) * _morphAlpha,
180
- y: a.y + (b.y - a.y) * _morphAlpha,
174
+ y: a.y + (b.y - a.y) * _morphAlpha
181
175
  };
182
176
  }
183
177
  return points;
184
178
  }
185
179
  for (let i = 0; i < steps; i++) {
186
- const sampleT = (i / (steps - 1)) * curve.period;
180
+ const sampleT = i / (steps - 1) * curve.period;
187
181
  points[i] = sampleSkeleton(curve, sampleT);
188
182
  }
189
183
  return points;
190
- },
184
+ }
191
185
  };
192
186
  }
193
187
 
@@ -201,9 +195,50 @@ var TRAIL_FADE_CURVE = 1.5;
201
195
  var TRAIL_MAX_OPACITY = 0.88;
202
196
  var TRAIL_MIN_WIDTH = 0.5;
203
197
  var TRAIL_MAX_WIDTH = 2.5;
198
+ var GRADIENT = {
199
+ bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
200
+ sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
201
+ ocean: ["#1e3a8a", "#06b6d4", "#22d3ee", "#e0f2fe"],
202
+ ice: ["#1e3a8a", "#67e8f9"],
203
+ fire: ["#7f1d1d", "#fbbf24"],
204
+ forest: ["#14532d", "#86efac"]
205
+ };
206
+ var PRESETS = {
207
+ bard: GRADIENT.bard,
208
+ sunset: GRADIENT.sunset,
209
+ ocean: GRADIENT.ocean,
210
+ ice: GRADIENT.ice,
211
+ fire: GRADIENT.fire,
212
+ forest: GRADIENT.forest
213
+ };
214
+ function hexToRgb(hex) {
215
+ const n = parseInt(hex.slice(1), 16);
216
+ return { r: n >> 16, g: n >> 8 & 255, b: n & 255 };
217
+ }
218
+ var lerpRgb = (a, b, t) => ({
219
+ r: Math.round(a.r + (b.r - a.r) * t),
220
+ g: Math.round(a.g + (b.g - a.g) * t),
221
+ b: Math.round(a.b + (b.b - a.b) * t)
222
+ });
223
+ function getPaletteColor(palette, position, timeOffset = 0) {
224
+ if (palette.length === 0) return { r: 255, g: 255, b: 255 };
225
+ if (palette.length === 1) return hexToRgb(palette[0]);
226
+ const cyclePos = (position + timeOffset) % 1;
227
+ const scaled = cyclePos * palette.length;
228
+ const idx = Math.floor(scaled);
229
+ const t = scaled - idx;
230
+ const c1 = hexToRgb(palette[idx % palette.length]);
231
+ const c2 = hexToRgb(palette[(idx + 1) % palette.length]);
232
+ return lerpRgb(c1, c2, t);
233
+ }
234
+ function resolvePalette(palette, trailStyle) {
235
+ if (Array.isArray(palette)) return palette;
236
+ if (palette && palette in PRESETS) return PRESETS[palette];
237
+ return trailStyle === "gradient-animated" ? GRADIENT.bard : GRADIENT.ice;
238
+ }
204
239
  function hexToRgbComponents(hex) {
205
240
  const n = parseInt(hex.slice(1), 16);
206
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
241
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
207
242
  }
208
243
  function computeTangent(trail, i) {
209
244
  const count = trail.length;
@@ -248,8 +283,10 @@ function createRenderer(options) {
248
283
  skeletonColor: options.skeletonColor ?? DEFAULT_SKELETON_COLOR,
249
284
  trailColor: options.trailColor ?? "#ffffff",
250
285
  headColor: options.headColor ?? "#ffffff",
251
- headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
286
+ headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS
252
287
  };
288
+ const trailStyle = options.trailStyle ?? "default";
289
+ const palette = resolvePalette(options.palette, trailStyle);
253
290
  const trailRgb = hexToRgbComponents(opts.trailColor);
254
291
  const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
255
292
  function setupCanvas() {
@@ -275,13 +312,11 @@ function createRenderer(options) {
275
312
  let morphResolve = null;
276
313
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
277
314
  let morphAlpha = 0;
315
+ let gradientAnimTime = 0;
278
316
  function computeBoundaries(pts) {
279
317
  if (pts.length === 0) return null;
280
318
  const first = pts[0];
281
- let minX = first.x,
282
- maxX = first.x,
283
- minY = first.y,
284
- maxY = first.y;
319
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
285
320
  for (const p of pts) {
286
321
  if (p.x < minX) minX = p.x;
287
322
  if (p.x > maxX) maxX = p.x;
@@ -298,7 +333,7 @@ function createRenderer(options) {
298
333
  return {
299
334
  scale: s,
300
335
  offsetX: (logicalWidth - boundsWidth) / 2 - minX * s,
301
- offsetY: (logicalHeight - boundsHeight) / 2 - minY * s,
336
+ offsetY: (logicalHeight - boundsHeight) / 2 - minY * s
302
337
  };
303
338
  }
304
339
  function calculateBoundaries() {
@@ -386,7 +421,13 @@ function createRenderer(options) {
386
421
  const l1y = next.y * scale + offsetY + n1.y * halfW1;
387
422
  const r1x = next.x * scale + offsetX - n1.x * halfW1;
388
423
  const r1y = next.y * scale + offsetY - n1.y * halfW1;
389
- ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
424
+ if (trailStyle === "default") {
425
+ ctx.fillStyle = `rgba(${trailRgb},${alpha})`;
426
+ } else {
427
+ const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
428
+ const color = getPaletteColor(palette, progress, timeOffset);
429
+ ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${alpha})`;
430
+ }
390
431
  ctx.beginPath();
391
432
  ctx.moveTo(l0x, l0y);
392
433
  ctx.lineTo(l1x, l1y);
@@ -411,6 +452,9 @@ function createRenderer(options) {
411
452
  const now = performance.now();
412
453
  const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
413
454
  lastTime = now;
455
+ if (trailStyle === "gradient-animated") {
456
+ gradientAnimTime += deltaTime * 1e3;
457
+ }
414
458
  if (engine.morphAlpha !== null) {
415
459
  morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
416
460
  engine.setMorphAlpha(morphAlpha);
@@ -495,7 +539,7 @@ function createRenderer(options) {
495
539
  return new Promise((resolve) => {
496
540
  morphResolve = resolve;
497
541
  });
498
- },
542
+ }
499
543
  };
500
544
  }
501
545
 
@@ -518,7 +562,7 @@ function createSVGRenderer(options) {
518
562
  trailColor: options.trailColor ?? "#ffffff",
519
563
  headColor: options.headColor ?? "#ffffff",
520
564
  headRadius: options.headRadius ?? 4,
521
- ariaLabel: options.ariaLabel ?? "Loading",
565
+ ariaLabel: options.ariaLabel ?? "Loading"
522
566
  };
523
567
  const rect = container.getBoundingClientRect();
524
568
  const width = rect.width || 200;
@@ -572,10 +616,7 @@ function createSVGRenderer(options) {
572
616
  return;
573
617
  }
574
618
  const first = skeleton2[0];
575
- let minX = first.x,
576
- maxX = first.x,
577
- minY = first.y,
578
- maxY = first.y;
619
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
579
620
  for (const p of skeleton2) {
580
621
  if (p.x < minX) {
581
622
  minX = p.x;
@@ -599,19 +640,25 @@ function createSVGRenderer(options) {
599
640
  offsetY = (height - h * scale) / 2 - minY * scale;
600
641
  }
601
642
  function px(p) {
602
- return (p.x * scale + offsetX).toFixed(2);
643
+ return p.x * scale + offsetX;
603
644
  }
604
645
  function py(p) {
605
- return (p.y * scale + offsetY).toFixed(2);
646
+ return p.y * scale + offsetY;
647
+ }
648
+ function pxStr(p) {
649
+ return px(p).toFixed(2);
650
+ }
651
+ function pyStr(p) {
652
+ return py(p).toFixed(2);
606
653
  }
607
654
  function updateSkeleton(skeleton2) {
608
655
  if (skeleton2.length < 2) {
609
656
  skeletonPath.setAttribute("d", "");
610
657
  return;
611
658
  }
612
- let d = `M${px(skeleton2[0])} ${py(skeleton2[0])}`;
659
+ let d = `M${pxStr(skeleton2[0])} ${pyStr(skeleton2[0])}`;
613
660
  for (let i = 1; i < skeleton2.length; i++) {
614
- d += ` L${px(skeleton2[i])} ${py(skeleton2[i])}`;
661
+ d += ` L${pxStr(skeleton2[i])} ${pyStr(skeleton2[i])}`;
615
662
  }
616
663
  d += " Z";
617
664
  skeletonPath.setAttribute("d", d);
@@ -663,13 +710,12 @@ function createSVGRenderer(options) {
663
710
  const head = trail[trailCount - 1];
664
711
  const x = px(head);
665
712
  const y = py(head);
666
- headCircle.setAttribute("cx", x);
667
- headCircle.setAttribute("cy", y);
713
+ headCircle.setAttribute("cx", String(x));
714
+ headCircle.setAttribute("cy", String(y));
668
715
  }
669
716
  let animationId = null;
670
717
  let lastTime = 0;
671
- const prefersReducedMotion =
672
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
718
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
673
719
  let morphResolve = null;
674
720
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS2;
675
721
  let morphTarget = null;
@@ -679,7 +725,7 @@ function createSVGRenderer(options) {
679
725
  const samples = Math.max(50, Math.round(period * 20));
680
726
  const points = [];
681
727
  for (let i = 0; i <= samples; i++) {
682
- const t = (i / samples) * period;
728
+ const t = i / samples * period;
683
729
  const p = target.fn(t, 0, {});
684
730
  points.push(p);
685
731
  }
@@ -707,16 +753,13 @@ function createSVGRenderer(options) {
707
753
  skeletonPathA.setAttribute("visibility", "visible");
708
754
  skeletonPathA.setAttribute(
709
755
  "stroke-opacity",
710
- String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2),
756
+ String((1 - morphAlpha) * DEFAULT_SKELETON_OPACITY2)
711
757
  );
712
758
  }
713
759
  if (morphPathBBuilt) {
714
760
  skeletonPathB.setAttribute("d", morphPathBBuilt);
715
761
  skeletonPathB.setAttribute("visibility", "visible");
716
- skeletonPathB.setAttribute(
717
- "stroke-opacity",
718
- String(morphAlpha * DEFAULT_SKELETON_OPACITY2),
719
- );
762
+ skeletonPathB.setAttribute("stroke-opacity", String(morphAlpha * DEFAULT_SKELETON_OPACITY2));
720
763
  }
721
764
  if (morphAlpha >= 1) {
722
765
  engine.completeMorph();
@@ -808,7 +851,7 @@ function createSVGRenderer(options) {
808
851
  return new Promise((resolve) => {
809
852
  morphResolve = resolve;
810
853
  });
811
- },
854
+ }
812
855
  };
813
856
  }
814
857
  function createSarmalSVG(container, curveDef, options) {
@@ -820,29 +863,26 @@ function createSarmalSVG(container, curveDef, options) {
820
863
  // src/curves.ts
821
864
  var TWO_PI2 = Math.PI * 2;
822
865
  function artemis2(t, _time, _params) {
823
- const a = 0.35,
824
- b = 0.15,
825
- ox = 0.175;
826
- const s = Math.sin(t),
827
- c = Math.cos(t);
866
+ const a = 0.35, b = 0.15, ox = 0.175;
867
+ const s = Math.sin(t), c = Math.cos(t);
828
868
  const denom = 1 + s * s;
829
869
  return {
830
- x: (c * (1 + a * c)) / denom - ox,
831
- y: (s * c * (1 + b * c)) / denom,
870
+ x: c * (1 + a * c) / denom - ox,
871
+ y: s * c * (1 + b * c) / denom
832
872
  };
833
873
  }
834
874
  function epitrochoid7(t, _time, _params) {
835
875
  const d = 1 + 0.55 * Math.sin(t * 0.5);
836
876
  return {
837
877
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
838
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
878
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
839
879
  };
840
880
  }
841
881
  function epitrochoid7Skeleton(t) {
842
882
  const d = 1.275;
843
883
  return {
844
884
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
845
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
885
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
846
886
  };
847
887
  }
848
888
  function astroid(t, _time, _params) {
@@ -850,56 +890,55 @@ function astroid(t, _time, _params) {
850
890
  const s = Math.sin(t);
851
891
  return {
852
892
  x: c * c * c,
853
- y: s * s * s,
893
+ y: s * s * s
854
894
  };
855
895
  }
856
896
  function deltoid(t, _time, _params) {
857
897
  return {
858
898
  x: 2 * Math.cos(t) + Math.cos(2 * t),
859
- y: 2 * Math.sin(t) - Math.sin(2 * t),
899
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
860
900
  };
861
901
  }
862
902
  function rose5(t, _time, _params) {
863
903
  const r = Math.cos(5 * t);
864
904
  return {
865
905
  x: r * Math.cos(t),
866
- y: r * Math.sin(t),
906
+ y: r * Math.sin(t)
867
907
  };
868
908
  }
869
909
  function rose3(t, _time, _params) {
870
910
  const r = Math.cos(3 * t);
871
911
  return {
872
912
  x: r * Math.cos(t),
873
- y: r * Math.sin(t),
913
+ y: r * Math.sin(t)
874
914
  };
875
915
  }
876
916
  function lissajous32(t, time, _params) {
877
917
  const phi = time * 0.45;
878
918
  return {
879
919
  x: Math.sin(3 * t + phi),
880
- y: Math.sin(2 * t),
920
+ y: Math.sin(2 * t)
881
921
  };
882
922
  }
883
923
  function lissajous43(t, time, _params) {
884
924
  const phi = time * 0.38;
885
925
  return {
886
926
  x: Math.sin(4 * t + phi),
887
- y: Math.sin(3 * t),
927
+ y: Math.sin(3 * t)
888
928
  };
889
929
  }
890
930
  function epicycloid3(t, _time, _params) {
891
931
  return {
892
932
  x: 4 * Math.cos(t) - Math.cos(4 * t),
893
- y: 4 * Math.sin(t) - Math.sin(4 * t),
933
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
894
934
  };
895
935
  }
896
936
  function lame(t, time, _params) {
897
937
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
898
- const c = Math.cos(t),
899
- s = Math.sin(t);
938
+ const c = Math.cos(t), s = Math.sin(t);
900
939
  return {
901
940
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
902
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
941
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
903
942
  };
904
943
  }
905
944
  var curves = {
@@ -907,66 +946,66 @@ var curves = {
907
946
  name: "Artemis II",
908
947
  fn: artemis2,
909
948
  period: TWO_PI2,
910
- speed: 0.7,
949
+ speed: 0.7
911
950
  },
912
951
  epitrochoid7: {
913
952
  name: "Epitrochoid",
914
953
  fn: epitrochoid7,
915
954
  period: TWO_PI2,
916
955
  speed: 1.4,
917
- skeletonFn: epitrochoid7Skeleton,
956
+ skeletonFn: epitrochoid7Skeleton
918
957
  },
919
958
  astroid: {
920
959
  name: "Astroid",
921
960
  fn: astroid,
922
961
  period: TWO_PI2,
923
- speed: 1.1,
962
+ speed: 1.1
924
963
  },
925
964
  deltoid: {
926
965
  name: "Deltoid",
927
966
  fn: deltoid,
928
967
  period: TWO_PI2,
929
- speed: 0.9,
968
+ speed: 0.9
930
969
  },
931
970
  rose5: {
932
971
  name: "Rose (n=5)",
933
972
  fn: rose5,
934
973
  period: TWO_PI2,
935
- speed: 1,
974
+ speed: 1
936
975
  },
937
976
  rose3: {
938
977
  name: "Rose (n=3)",
939
978
  fn: rose3,
940
979
  period: TWO_PI2,
941
- speed: 1.15,
980
+ speed: 1.15
942
981
  },
943
982
  lissajous32: {
944
983
  name: "Lissajous 3:2",
945
984
  fn: lissajous32,
946
985
  period: TWO_PI2,
947
986
  speed: 2,
948
- skeleton: "live",
987
+ skeleton: "live"
949
988
  },
950
989
  lissajous43: {
951
990
  name: "Lissajous 4:3",
952
991
  fn: lissajous43,
953
992
  period: TWO_PI2,
954
993
  speed: 1.8,
955
- skeleton: "live",
994
+ skeleton: "live"
956
995
  },
957
996
  epicycloid3: {
958
997
  name: "Epicycloid (n=3)",
959
998
  fn: epicycloid3,
960
999
  period: TWO_PI2,
961
- speed: 0.75,
1000
+ speed: 0.75
962
1001
  },
963
1002
  lame: {
964
1003
  name: "Lam\xE9 Curve",
965
1004
  fn: lame,
966
1005
  period: TWO_PI2,
967
1006
  speed: 1,
968
- skeleton: "live",
969
- },
1007
+ skeleton: "live"
1008
+ }
970
1009
  };
971
1010
 
972
1011
  // src/index.ts
@@ -983,4 +1022,4 @@ exports.createSarmal = createSarmal;
983
1022
  exports.createSarmalSVG = createSarmalSVG;
984
1023
  exports.curves = curves;
985
1024
  //# sourceMappingURL=index.cjs.map
986
- //# sourceMappingURL=index.cjs.map
1025
+ //# sourceMappingURL=index.cjs.map