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