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