@sarmal/core 0.12.0 → 0.14.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
@@ -258,6 +258,7 @@ function createEngine(curveDef, trailLength = 120) {
258
258
  var DEFAULT_MORPH_DURATION_MS = 300;
259
259
  var DEFAULT_SKELETON_OPACITY = 0.15;
260
260
  var FIT_PADDING = 0.1;
261
+ var FIT_PADDING_MIN = 4;
261
262
  var TRAIL_FADE_CURVE = 1.5;
262
263
  var TRAIL_MAX_OPACITY = 0.88;
263
264
  var TRAIL_MIN_WIDTH = 0.5;
@@ -340,9 +341,16 @@ function computeBoundaries(pts, logicalWidth, logicalHeight) {
340
341
  "[sarmal] Degenerate curve: all skeleton points are identical. Check that your curve fn returns distinct points for different values of t."
341
342
  );
342
343
  }
343
- const scaleX = logicalWidth / (w * (1 + FIT_PADDING * 2));
344
- const scaleY = logicalHeight / (h * (1 + FIT_PADDING * 2));
345
- const scale = Math.min(scaleX, scaleY);
344
+ const scaleXProportional = logicalWidth / (w * (1 + FIT_PADDING * 2));
345
+ const scaleYProportional = logicalHeight / (h * (1 + FIT_PADDING * 2));
346
+ const scaleXMinPadding = (logicalWidth - FIT_PADDING_MIN * 2) / w;
347
+ const scaleYMinPadding = (logicalHeight - FIT_PADDING_MIN * 2) / h;
348
+ const scale = Math.min(
349
+ scaleXProportional,
350
+ scaleYProportional,
351
+ scaleXMinPadding,
352
+ scaleYMinPadding
353
+ );
346
354
  return {
347
355
  scale,
348
356
  offsetX: (logicalWidth - w * scale) / 2 - minX * scale,
@@ -359,9 +367,6 @@ function enginePassthroughs(engine) {
359
367
  setSpeedOver: engine.setSpeedOver
360
368
  };
361
369
  }
362
-
363
- // src/renderer.ts
364
- var DEFAULT_SKELETON_COLOR = "#ffffff";
365
370
  var GRADIENT = {
366
371
  bard: ["#a855f7", "#3b82f6", "#14b8a6", "#ec4899"],
367
372
  sunset: ["#f97316", "#dc2626", "#9333ea", "#f472b6"],
@@ -388,8 +393,12 @@ var lerpRgb = (a, b, t) => ({
388
393
  b: Math.round(a.b + (b.b - a.b) * t)
389
394
  });
390
395
  function getPaletteColor(palette, position, timeOffset = 0) {
391
- if (palette.length === 0) return { r: 255, g: 255, b: 255 };
392
- if (palette.length === 1) return hexToRgb(palette[0]);
396
+ if (palette.length === 0) {
397
+ return { r: 255, g: 255, b: 255 };
398
+ }
399
+ if (palette.length === 1) {
400
+ return hexToRgb(palette[0]);
401
+ }
393
402
  const cyclePos = (position + timeOffset) % 1;
394
403
  const scaled = cyclePos * palette.length;
395
404
  const idx = Math.floor(scaled);
@@ -399,10 +408,17 @@ function getPaletteColor(palette, position, timeOffset = 0) {
399
408
  return lerpRgb(c1, c2, t);
400
409
  }
401
410
  function resolvePalette(palette, trailStyle) {
402
- if (Array.isArray(palette)) return palette;
403
- if (palette && palette in PRESETS) return PRESETS[palette];
411
+ if (Array.isArray(palette)) {
412
+ return palette;
413
+ }
414
+ if (palette && palette in PRESETS) {
415
+ return PRESETS[palette];
416
+ }
404
417
  return trailStyle === "gradient-animated" ? GRADIENT.bard : GRADIENT.ice;
405
418
  }
419
+
420
+ // src/renderer.ts
421
+ var DEFAULT_SKELETON_COLOR = "#ffffff";
406
422
  function hexToRgbComponents(hex) {
407
423
  const n = parseInt(hex.slice(1), 16);
408
424
  return `${n >> 16},${n >> 8 & 255},${n & 255}`;
@@ -564,10 +580,7 @@ function createRenderer(options) {
564
580
  ctx.arc(x, y, r, 0, Math.PI * 2);
565
581
  ctx.fill();
566
582
  }
567
- function render() {
568
- const now = performance.now();
569
- const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
570
- lastTime = now;
583
+ function renderFrame(deltaTime) {
571
584
  if (trailStyle === "gradient-animated") {
572
585
  gradientAnimTime += deltaTime * 1e3;
573
586
  }
@@ -603,22 +616,33 @@ function createRenderer(options) {
603
616
  drawSkeleton();
604
617
  drawTrail();
605
618
  drawHead();
606
- animationId = requestAnimationFrame(render);
619
+ }
620
+ function loop() {
621
+ const now = performance.now();
622
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
623
+ lastTime = now;
624
+ renderFrame(deltaTime);
625
+ animationId = requestAnimationFrame(loop);
607
626
  }
608
627
  skeleton = engine.getSarmalSkeleton();
609
628
  calculateBoundaries();
610
629
  if (!engine.isLiveSkeleton) {
611
630
  buildSkeletonCanvas();
612
631
  }
613
- return {
614
- start() {
632
+ if (options.initialT !== void 0) {
633
+ engine.seek(options.initialT);
634
+ }
635
+ renderFrame(0);
636
+ const shouldAutoStart = options.autoStart !== false;
637
+ const instance = {
638
+ play() {
615
639
  if (animationId !== null) {
616
640
  return;
617
641
  }
618
642
  lastTime = performance.now();
619
- render();
643
+ loop();
620
644
  },
621
- stop() {
645
+ pause() {
622
646
  if (animationId === null) {
623
647
  return;
624
648
  }
@@ -653,6 +677,10 @@ function createRenderer(options) {
653
677
  });
654
678
  }
655
679
  };
680
+ if (shouldAutoStart) {
681
+ instance.play();
682
+ }
683
+ return instance;
656
684
  }
657
685
 
658
686
  // src/renderer-svg.ts
@@ -684,10 +712,15 @@ function el(tag) {
684
712
  function createSVGRenderer(options) {
685
713
  const { container, engine } = options;
686
714
  const trailColor = options.trailColor ?? "#ffffff";
715
+ const trailStyle = options.trailStyle ?? "default";
716
+ const palette = resolvePalette(options.palette, trailStyle);
687
717
  const opts = {
688
718
  skeletonColor: options.skeletonColor ?? "#ffffff",
689
719
  trailColor,
690
- headColor: options.headColor ?? trailColor,
720
+ headColor: options.headColor ?? (trailStyle !== "default" ? (() => {
721
+ const { r, g, b } = getPaletteColor(palette, 1);
722
+ return `rgb(${r},${g},${b})`;
723
+ })() : trailColor),
691
724
  ariaLabel: options.ariaLabel ?? "Loading"
692
725
  };
693
726
  const rect = container.getBoundingClientRect();
@@ -704,6 +737,7 @@ function createSVGRenderer(options) {
704
737
  titleEl.textContent = opts.ariaLabel;
705
738
  svg.appendChild(titleEl);
706
739
  const skeletonPath = el("path");
740
+ skeletonPath.setAttribute("data-sarmal-role", "skeleton");
707
741
  skeletonPath.setAttribute("fill", "none");
708
742
  skeletonPath.setAttribute("stroke", opts.skeletonColor);
709
743
  skeletonPath.setAttribute("stroke-opacity", String(DEFAULT_SKELETON_OPACITY));
@@ -731,10 +765,12 @@ function createSVGRenderer(options) {
731
765
  trailPaths.push(path);
732
766
  }
733
767
  const headCircle = el("circle");
768
+ headCircle.setAttribute("data-sarmal-role", "head");
734
769
  headCircle.setAttribute("fill", opts.headColor);
735
770
  headCircle.setAttribute("r", String(headRadius));
736
771
  svg.appendChild(headCircle);
737
772
  container.appendChild(svg);
773
+ let gradientAnimTime = 0;
738
774
  let scale = 1;
739
775
  let offsetX = 0;
740
776
  let offsetY = 0;
@@ -768,7 +804,7 @@ function createSVGRenderer(options) {
768
804
  return;
769
805
  }
770
806
  for (let i = 0; i < trailCount - 1; i++) {
771
- const { l0x, l0y, r0x, r0y, l1x, l1y, r1x, r1y, opacity } = computeTrailQuad(
807
+ const { l0x, l0y, r0x, r0y, l1x, l1y, r1x, r1y, opacity, progress } = computeTrailQuad(
772
808
  trail,
773
809
  i,
774
810
  trailCount,
@@ -778,6 +814,11 @@ function createSVGRenderer(options) {
778
814
  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`;
779
815
  trailPaths[i].setAttribute("d", d);
780
816
  trailPaths[i].setAttribute("fill-opacity", opacity.toFixed(3));
817
+ if (trailStyle !== "default") {
818
+ const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
819
+ const { r, g, b } = getPaletteColor(palette, progress, timeOffset);
820
+ trailPaths[i].setAttribute("fill", `rgb(${r},${g},${b})`);
821
+ }
781
822
  }
782
823
  for (let i = trailCount - 1; i < trailPaths.length; i++) {
783
824
  trailPaths[i].setAttribute("d", "");
@@ -800,12 +841,12 @@ function createSVGRenderer(options) {
800
841
  let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
801
842
  let morphTarget = null;
802
843
  let morphAlpha = 0;
803
- function renderFrame() {
804
- const now = performance.now();
805
- const dt = Math.min((now - lastTime) / 1e3, 1 / 30);
806
- lastTime = now;
844
+ function renderFrame(deltaTime) {
845
+ if (trailStyle === "gradient-animated") {
846
+ gradientAnimTime += deltaTime * 1e3;
847
+ }
807
848
  if (engine.morphAlpha !== null) {
808
- morphAlpha = Math.min(1, morphAlpha + dt / (morphDurationMs / 1e3));
849
+ morphAlpha = Math.min(1, morphAlpha + deltaTime / (morphDurationMs / 1e3));
809
850
  engine.setMorphAlpha(morphAlpha);
810
851
  if (morphPathABuilt) {
811
852
  skeletonPathA.setAttribute("d", morphPathABuilt);
@@ -835,7 +876,7 @@ function createSVGRenderer(options) {
835
876
  updateSkeleton(newSkeleton);
836
877
  }
837
878
  }
838
- const trail = engine.tick(dt);
879
+ const trail = engine.tick(deltaTime);
839
880
  const trailCount = engine.trailCount;
840
881
  if (engine.isLiveSkeleton && engine.morphAlpha === null) {
841
882
  const liveSkeleton = engine.getSarmalSkeleton();
@@ -844,19 +885,30 @@ function createSVGRenderer(options) {
844
885
  }
845
886
  updateTrail(trail, trailCount);
846
887
  updateHead(trail, trailCount);
888
+ }
889
+ function loop() {
890
+ const now = performance.now();
891
+ const deltaTime = Math.min((now - lastTime) / 1e3, 1 / 30);
892
+ lastTime = now;
893
+ renderFrame(deltaTime);
847
894
  if (!prefersReducedMotion) {
848
- animationId = requestAnimationFrame(renderFrame);
895
+ animationId = requestAnimationFrame(loop);
849
896
  }
850
897
  }
851
- return {
852
- start() {
898
+ if (options.initialT !== void 0) {
899
+ engine.seek(options.initialT);
900
+ }
901
+ renderFrame(0);
902
+ const shouldAutoStart = options.autoStart !== false;
903
+ const instance = {
904
+ play() {
853
905
  if (animationId !== null) {
854
906
  return;
855
907
  }
856
908
  lastTime = performance.now();
857
- renderFrame();
909
+ loop();
858
910
  },
859
- stop() {
911
+ pause() {
860
912
  if (animationId === null) {
861
913
  return;
862
914
  }
@@ -899,6 +951,10 @@ function createSVGRenderer(options) {
899
951
  });
900
952
  }
901
953
  };
954
+ if (shouldAutoStart) {
955
+ instance.play();
956
+ }
957
+ return instance;
902
958
  }
903
959
  function createSarmalSVG(container, curveDef, options) {
904
960
  const { trailLength, ...rendererOpts } = options ?? {};