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