@sarmal/core 0.4.2 → 0.5.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;
@@ -50,6 +50,8 @@ function createEngine(curveDef, trailLength = 120) {
50
50
  fn: curveDef.fn,
51
51
  period: curveDef.period ?? TWO_PI,
52
52
  speed: curveDef.speed ?? 1,
53
+ skeleton: curveDef.skeleton,
54
+ skeletonFn: curveDef.skeletonFn
53
55
  };
54
56
  const trail = new CircularBuffer(trailLength);
55
57
  let t = 0;
@@ -65,20 +67,23 @@ function createEngine(curveDef, trailLength = 120) {
65
67
  get trailCount() {
66
68
  return trail.length;
67
69
  },
70
+ get isLiveSkeleton() {
71
+ return curve.skeleton === "live";
72
+ },
68
73
  reset() {
69
74
  t = 0;
70
75
  actualTime = 0;
71
76
  trail.clear();
72
77
  },
73
78
  seek(newT, { clearTrail = false } = {}) {
74
- t = ((newT % curve.period) + curve.period) % curve.period;
79
+ t = (newT % curve.period + curve.period) % curve.period;
75
80
  if (clearTrail) {
76
81
  trail.clear();
77
82
  }
78
83
  },
79
84
  seekWithTrail(targetT, { wrap = false, step = curve.period / trailLength } = {}) {
80
85
  const advance = curve.speed * step;
81
- const target = ((targetT % curve.period) + curve.period) % curve.period;
86
+ const target = (targetT % curve.period + curve.period) % curve.period;
82
87
  const targetTime = target / curve.speed;
83
88
  t = target;
84
89
  actualTime = targetTime;
@@ -96,13 +101,24 @@ function createEngine(curveDef, trailLength = 120) {
96
101
  getSarmalSkeleton() {
97
102
  const steps = Math.ceil(curve.period * POINTS_PER_PERIOD_UNIT);
98
103
  const points = new Array(steps);
99
- for (let i = 0; i < steps; i++) {
100
- const sampleT = (i / (steps - 1)) * curve.period;
101
- const point = curve.fn(sampleT, 0, {});
102
- points[i] = point;
104
+ if (curve.skeletonFn) {
105
+ for (let i = 0; i < steps; i++) {
106
+ const sampleT = i / (steps - 1) * curve.period;
107
+ points[i] = curve.skeletonFn(sampleT);
108
+ }
109
+ } else if (curve.skeleton === "live") {
110
+ for (let i = 0; i < steps; i++) {
111
+ const sampleT = i / (steps - 1) * curve.period;
112
+ points[i] = curve.fn(sampleT, actualTime, {});
113
+ }
114
+ } else {
115
+ for (let i = 0; i < steps; i++) {
116
+ const sampleT = i / (steps - 1) * curve.period;
117
+ points[i] = curve.fn(sampleT, 0, {});
118
+ }
103
119
  }
104
120
  return points;
105
- },
121
+ }
106
122
  };
107
123
  }
108
124
 
@@ -121,7 +137,7 @@ var GLOW_INNER_EDGE = 0.4;
121
137
  var GLOW_FALLOFF_OPACITY = 0.53;
122
138
  function hexToRgbComponents(hex) {
123
139
  const n = parseInt(hex.slice(1), 16);
124
- return `${n >> 16},${(n >> 8) & 255},${n & 255}`;
140
+ return `${n >> 16},${n >> 8 & 255},${n & 255}`;
125
141
  }
126
142
  function createRenderer(options) {
127
143
  const canvas = options.canvas;
@@ -135,7 +151,7 @@ function createRenderer(options) {
135
151
  trailColor: options.trailColor ?? "#ffffff",
136
152
  headColor: options.headColor ?? "#ffffff",
137
153
  headRadius: options.headRadius ?? DEFAULT_HEAD_RADIUS,
138
- glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE,
154
+ glowSize: options.glowSize ?? DEFAULT_GLOW_SIZE
139
155
  };
140
156
  const trailRgb = hexToRgbComponents(opts.trailColor);
141
157
  const headRgbFalloff = `rgba(${hexToRgbComponents(opts.headColor)},${GLOW_FALLOFF_OPACITY})`;
@@ -154,10 +170,7 @@ function createRenderer(options) {
154
170
  return;
155
171
  }
156
172
  const first = skeleton[0];
157
- let minX = first.x,
158
- maxX = first.x,
159
- minY = first.y,
160
- maxY = first.y;
173
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
161
174
  for (const p of skeleton) {
162
175
  if (p.x < minX) {
163
176
  minX = p.x;
@@ -200,10 +213,26 @@ function createRenderer(options) {
200
213
  skeletonCtx.stroke();
201
214
  }
202
215
  function drawSkeleton() {
203
- if (!skeletonCanvas || opts.skeletonColor === "transparent") {
216
+ if (opts.skeletonColor === "transparent") {
204
217
  return;
205
218
  }
206
- ctx.drawImage(skeletonCanvas, 0, 0);
219
+ if (engine.isLiveSkeleton) {
220
+ if (skeleton.length < 2) {
221
+ return;
222
+ }
223
+ ctx.strokeStyle = `rgba(${hexToRgbComponents(opts.skeletonColor)},${DEFAULT_SKELETON_OPACITY})`;
224
+ ctx.lineWidth = 1.5;
225
+ ctx.beginPath();
226
+ const first = skeleton[0];
227
+ ctx.moveTo(first.x * scale + offsetX, first.y * scale + offsetY);
228
+ for (let i = 1; i < skeleton.length; i++) {
229
+ const p = skeleton[i];
230
+ ctx.lineTo(p.x * scale + offsetX, p.y * scale + offsetY);
231
+ }
232
+ ctx.stroke();
233
+ } else if (skeletonCanvas) {
234
+ ctx.drawImage(skeletonCanvas, 0, 0);
235
+ }
207
236
  }
208
237
  function drawTrail() {
209
238
  if (trailCount < 2) {
@@ -257,6 +286,10 @@ function createRenderer(options) {
257
286
  trailCount = engine.trailCount;
258
287
  head = trailCount > 0 ? trail[trailCount - 1] : null;
259
288
  ctx.clearRect(0, 0, canvas.width, canvas.height);
289
+ if (engine.isLiveSkeleton) {
290
+ skeleton = engine.getSarmalSkeleton();
291
+ calculateBoundaries();
292
+ }
260
293
  drawSkeleton();
261
294
  drawTrail();
262
295
  drawHead();
@@ -264,7 +297,9 @@ function createRenderer(options) {
264
297
  }
265
298
  skeleton = engine.getSarmalSkeleton();
266
299
  calculateBoundaries();
267
- buildSkeletonCanvas();
300
+ if (!engine.isLiveSkeleton) {
301
+ buildSkeletonCanvas();
302
+ }
268
303
  return {
269
304
  start() {
270
305
  if (animationId !== null) {
@@ -296,7 +331,7 @@ function createRenderer(options) {
296
331
  },
297
332
  seekWithTrail(t) {
298
333
  engine.seekWithTrail(t);
299
- },
334
+ }
300
335
  };
301
336
  }
302
337
 
@@ -322,7 +357,7 @@ function createSVGRenderer(options) {
322
357
  headColor: options.headColor ?? "#ffffff",
323
358
  headRadius: options.headRadius ?? 4,
324
359
  glowSize: options.glowSize ?? 20,
325
- ariaLabel: options.ariaLabel ?? "Loading",
360
+ ariaLabel: options.ariaLabel ?? "Loading"
326
361
  };
327
362
  const uid = ++instanceCount;
328
363
  const gradientId = `sarmal-glow-${uid}`;
@@ -392,10 +427,7 @@ function createSVGRenderer(options) {
392
427
  return;
393
428
  }
394
429
  const first = skeleton2[0];
395
- let minX = first.x,
396
- maxX = first.x,
397
- minY = first.y,
398
- maxY = first.y;
430
+ let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
399
431
  for (const p of skeleton2) {
400
432
  if (p.x < minX) {
401
433
  minX = p.x;
@@ -424,16 +456,23 @@ function createSVGRenderer(options) {
424
456
  function py(p) {
425
457
  return (p.y * scale + offsetY).toFixed(2);
426
458
  }
427
- const skeleton = engine.getSarmalSkeleton();
428
- calculateBoundaries(skeleton);
429
- if (skeleton.length >= 2) {
430
- let d = `M${px(skeleton[0])} ${py(skeleton[0])}`;
431
- for (let i = 1; i < skeleton.length; i++) {
432
- d += ` L${px(skeleton[i])} ${py(skeleton[i])}`;
459
+ function updateSkeleton(skeleton2) {
460
+ if (skeleton2.length < 2) {
461
+ skeletonPath.setAttribute("d", "");
462
+ return;
463
+ }
464
+ let d = `M${px(skeleton2[0])} ${py(skeleton2[0])}`;
465
+ for (let i = 1; i < skeleton2.length; i++) {
466
+ d += ` L${px(skeleton2[i])} ${py(skeleton2[i])}`;
433
467
  }
434
468
  d += " Z";
435
469
  skeletonPath.setAttribute("d", d);
436
470
  }
471
+ const skeleton = engine.getSarmalSkeleton();
472
+ calculateBoundaries(skeleton);
473
+ if (!engine.isLiveSkeleton) {
474
+ updateSkeleton(skeleton);
475
+ }
437
476
  function updateTrail(trail, trailCount) {
438
477
  if (trailCount < 2) {
439
478
  for (const p of trailPaths) {
@@ -475,14 +514,18 @@ function createSVGRenderer(options) {
475
514
  }
476
515
  let animationId = null;
477
516
  let lastTime = 0;
478
- const prefersReducedMotion =
479
- typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
517
+ const prefersReducedMotion = typeof window !== "undefined" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
480
518
  function renderFrame() {
481
519
  const now = performance.now();
482
520
  const dt = Math.min((now - lastTime) / 1e3, 1 / 30);
483
521
  lastTime = now;
484
522
  const trail = engine.tick(dt);
485
523
  const trailCount = engine.trailCount;
524
+ if (engine.isLiveSkeleton) {
525
+ const liveSkeleton = engine.getSarmalSkeleton();
526
+ calculateBoundaries(liveSkeleton);
527
+ updateSkeleton(liveSkeleton);
528
+ }
486
529
  updateTrail(trail, trailCount);
487
530
  updateHead(trail, trailCount);
488
531
  if (!prefersReducedMotion) {
@@ -519,7 +562,7 @@ function createSVGRenderer(options) {
519
562
  },
520
563
  seekWithTrail(t) {
521
564
  engine.seekWithTrail(t);
522
- },
565
+ }
523
566
  };
524
567
  }
525
568
  function createSarmalSVG(container, curveDef, options) {
@@ -531,22 +574,26 @@ function createSarmalSVG(container, curveDef, options) {
531
574
  // src/curves.ts
532
575
  var TWO_PI2 = Math.PI * 2;
533
576
  function artemis2(t, _time, _params) {
534
- const a = 0.35,
535
- b = 0.15,
536
- ox = 0.175;
537
- const s = Math.sin(t),
538
- c = Math.cos(t);
577
+ const a = 0.35, b = 0.15, ox = 0.175;
578
+ const s = Math.sin(t), c = Math.cos(t);
539
579
  const denom = 1 + s * s;
540
580
  return {
541
- x: (c * (1 + a * c)) / denom - ox,
542
- y: (s * c * (1 + b * c)) / denom,
581
+ x: c * (1 + a * c) / denom - ox,
582
+ y: s * c * (1 + b * c) / denom
543
583
  };
544
584
  }
545
585
  function epitrochoid7(t, _time, _params) {
546
586
  const d = 1 + 0.55 * Math.sin(t * 0.5);
547
587
  return {
548
588
  x: 7 * Math.cos(t) - d * Math.cos(7 * t),
549
- y: 7 * Math.sin(t) - d * Math.sin(7 * t),
589
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
590
+ };
591
+ }
592
+ function epitrochoid7Skeleton(t) {
593
+ const d = 1.275;
594
+ return {
595
+ x: 7 * Math.cos(t) - d * Math.cos(7 * t),
596
+ y: 7 * Math.sin(t) - d * Math.sin(7 * t)
550
597
  };
551
598
  }
552
599
  function astroid(t, _time, _params) {
@@ -554,56 +601,55 @@ function astroid(t, _time, _params) {
554
601
  const s = Math.sin(t);
555
602
  return {
556
603
  x: c * c * c,
557
- y: s * s * s,
604
+ y: s * s * s
558
605
  };
559
606
  }
560
607
  function deltoid(t, _time, _params) {
561
608
  return {
562
609
  x: 2 * Math.cos(t) + Math.cos(2 * t),
563
- y: 2 * Math.sin(t) - Math.sin(2 * t),
610
+ y: 2 * Math.sin(t) - Math.sin(2 * t)
564
611
  };
565
612
  }
566
613
  function rose5(t, _time, _params) {
567
614
  const r = Math.cos(5 * t);
568
615
  return {
569
616
  x: r * Math.cos(t),
570
- y: r * Math.sin(t),
617
+ y: r * Math.sin(t)
571
618
  };
572
619
  }
573
620
  function rose3(t, _time, _params) {
574
621
  const r = Math.cos(3 * t);
575
622
  return {
576
623
  x: r * Math.cos(t),
577
- y: r * Math.sin(t),
624
+ y: r * Math.sin(t)
578
625
  };
579
626
  }
580
627
  function lissajous32(t, time, _params) {
581
628
  const phi = time * 0.45;
582
629
  return {
583
630
  x: Math.sin(3 * t + phi),
584
- y: Math.sin(2 * t),
631
+ y: Math.sin(2 * t)
585
632
  };
586
633
  }
587
634
  function lissajous43(t, time, _params) {
588
635
  const phi = time * 0.38;
589
636
  return {
590
637
  x: Math.sin(4 * t + phi),
591
- y: Math.sin(3 * t),
638
+ y: Math.sin(3 * t)
592
639
  };
593
640
  }
594
641
  function epicycloid3(t, _time, _params) {
595
642
  return {
596
643
  x: 4 * Math.cos(t) - Math.cos(4 * t),
597
- y: 4 * Math.sin(t) - Math.sin(4 * t),
644
+ y: 4 * Math.sin(t) - Math.sin(4 * t)
598
645
  };
599
646
  }
600
647
  function lame(t, time, _params) {
601
648
  const p = 1.75 + 1.25 * Math.sin(time * 0.48);
602
- const c = Math.cos(t),
603
- s = Math.sin(t);
649
+ const c = Math.cos(t), s = Math.sin(t);
604
650
  return {
605
651
  x: Math.sign(c) * Math.pow(Math.abs(c), p),
606
- y: Math.sign(s) * Math.pow(Math.abs(s), p),
652
+ y: Math.sign(s) * Math.pow(Math.abs(s), p)
607
653
  };
608
654
  }
609
655
  var curves = {
@@ -611,62 +657,66 @@ var curves = {
611
657
  name: "Artemis II",
612
658
  fn: artemis2,
613
659
  period: TWO_PI2,
614
- speed: 0.7,
660
+ speed: 0.7
615
661
  },
616
662
  epitrochoid7: {
617
663
  name: "Epitrochoid",
618
664
  fn: epitrochoid7,
619
665
  period: TWO_PI2,
620
666
  speed: 1.4,
667
+ skeletonFn: epitrochoid7Skeleton
621
668
  },
622
669
  astroid: {
623
670
  name: "Astroid",
624
671
  fn: astroid,
625
672
  period: TWO_PI2,
626
- speed: 1.1,
673
+ speed: 1.1
627
674
  },
628
675
  deltoid: {
629
676
  name: "Deltoid",
630
677
  fn: deltoid,
631
678
  period: TWO_PI2,
632
- speed: 0.9,
679
+ speed: 0.9
633
680
  },
634
681
  rose5: {
635
682
  name: "Rose (n=5)",
636
683
  fn: rose5,
637
684
  period: TWO_PI2,
638
- speed: 1,
685
+ speed: 1
639
686
  },
640
687
  rose3: {
641
688
  name: "Rose (n=3)",
642
689
  fn: rose3,
643
690
  period: TWO_PI2,
644
- speed: 1.15,
691
+ speed: 1.15
645
692
  },
646
693
  lissajous32: {
647
694
  name: "Lissajous 3:2",
648
695
  fn: lissajous32,
649
696
  period: TWO_PI2,
650
697
  speed: 2,
698
+ skeleton: "live"
651
699
  },
652
700
  lissajous43: {
653
701
  name: "Lissajous 4:3",
654
702
  fn: lissajous43,
655
703
  period: TWO_PI2,
656
704
  speed: 1.8,
705
+ skeleton: "live"
657
706
  },
658
707
  epicycloid3: {
659
708
  name: "Epicycloid (n=3)",
660
709
  fn: epicycloid3,
661
710
  period: TWO_PI2,
662
- speed: 0.75,
711
+ speed: 0.75
663
712
  },
664
713
  lame: {
665
714
  name: "Lam\xE9 Curve",
666
715
  fn: lame,
667
716
  period: TWO_PI2,
668
717
  speed: 1,
669
- },
718
+ skeleton: "live"
719
+ }
670
720
  };
671
721
 
672
722
  // src/index.ts
@@ -683,4 +733,4 @@ exports.createSarmal = createSarmal;
683
733
  exports.createSarmalSVG = createSarmalSVG;
684
734
  exports.curves = curves;
685
735
  //# sourceMappingURL=index.cjs.map
686
- //# sourceMappingURL=index.cjs.map
736
+ //# sourceMappingURL=index.cjs.map