@sarmal/core 0.35.0 → 0.36.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.
Files changed (54) hide show
  1. package/README.md +5 -3
  2. package/dist/auto-init.cjs +3 -1
  3. package/dist/auto-init.cjs.map +1 -1
  4. package/dist/auto-init.js +3 -1
  5. package/dist/auto-init.js.map +1 -1
  6. package/dist/cli.js +9 -3
  7. package/dist/cli.js.map +1 -1
  8. package/dist/curves/artemis2.d.cts +1 -1
  9. package/dist/curves/artemis2.d.ts +1 -1
  10. package/dist/curves/astroid.d.cts +1 -1
  11. package/dist/curves/astroid.d.ts +1 -1
  12. package/dist/curves/deltoid.d.cts +1 -1
  13. package/dist/curves/deltoid.d.ts +1 -1
  14. package/dist/curves/epicycloid3.d.cts +1 -1
  15. package/dist/curves/epicycloid3.d.ts +1 -1
  16. package/dist/curves/epitrochoid7.d.cts +1 -1
  17. package/dist/curves/epitrochoid7.d.ts +1 -1
  18. package/dist/curves/index.d.cts +1 -1
  19. package/dist/curves/index.d.ts +1 -1
  20. package/dist/curves/lame.d.cts +1 -1
  21. package/dist/curves/lame.d.ts +1 -1
  22. package/dist/curves/lissajous32.d.cts +1 -1
  23. package/dist/curves/lissajous32.d.ts +1 -1
  24. package/dist/curves/lissajous43.d.cts +1 -1
  25. package/dist/curves/lissajous43.d.ts +1 -1
  26. package/dist/curves/rose3.d.cts +1 -1
  27. package/dist/curves/rose3.d.ts +1 -1
  28. package/dist/curves/rose5.d.cts +1 -1
  29. package/dist/curves/rose5.d.ts +1 -1
  30. package/dist/curves/rose52.d.cts +1 -1
  31. package/dist/curves/rose52.d.ts +1 -1
  32. package/dist/curves/star.d.cts +1 -1
  33. package/dist/curves/star.d.ts +1 -1
  34. package/dist/curves/star4.d.cts +1 -1
  35. package/dist/curves/star4.d.ts +1 -1
  36. package/dist/curves/star7.d.cts +1 -1
  37. package/dist/curves/star7.d.ts +1 -1
  38. package/dist/index.cjs +109 -50
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +4 -4
  41. package/dist/index.d.ts +4 -4
  42. package/dist/index.js +109 -50
  43. package/dist/index.js.map +1 -1
  44. package/dist/{renderer-shared-DyOI68gd.d.ts → renderer-shared-BZV9ELOa.d.ts} +1 -1
  45. package/dist/{renderer-shared-C3KCEABq.d.cts → renderer-shared-CFimm7VD.d.cts} +1 -1
  46. package/dist/terminal.cjs +9 -3
  47. package/dist/terminal.cjs.map +1 -1
  48. package/dist/terminal.d.cts +2 -2
  49. package/dist/terminal.d.ts +2 -2
  50. package/dist/terminal.js +9 -3
  51. package/dist/terminal.js.map +1 -1
  52. package/dist/{types-_f27GDkU.d.cts → types-CCgSK31t.d.cts} +8 -4
  53. package/dist/{types-_f27GDkU.d.ts → types-CCgSK31t.d.ts} +8 -4
  54. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
  export { artemis2 } from './artemis2.js';
3
3
  export { astroid } from './astroid.js';
4
4
  export { deltoid } from './deltoid.js';
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.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-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.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-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.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-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.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-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.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-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.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-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.cjs';
2
2
 
3
3
  /**
4
4
  * Rose curve with 3 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
 
3
3
  /**
4
4
  * Rose curve with 3 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.cjs';
2
2
 
3
3
  /**
4
4
  * Rose curve with 5 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
 
3
3
  /**
4
4
  * Rose curve with 5 petals
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.cjs';
2
2
 
3
3
  /**
4
4
  * Rose curve with n=5/2 that traces 5 petals over two full revolutions
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
 
3
3
  /**
4
4
  * Rose curve with n=5/2 that traces 5 petals over two full revolutions
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.cjs';
2
2
 
3
3
  /**
4
4
  * 5-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
 
3
3
  /**
4
4
  * 5-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.cjs';
2
2
 
3
3
  /**
4
4
  * 4-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
 
3
3
  /**
4
4
  * 4-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.cjs';
1
+ import { C as CurveDef } from '../types-CCgSK31t.cjs';
2
2
 
3
3
  /**
4
4
  * 7-pointed star based on Fourier harmonics.
@@ -1,4 +1,4 @@
1
- import { C as CurveDef } from '../types-_f27GDkU.js';
1
+ import { C as CurveDef } from '../types-CCgSK31t.js';
2
2
 
3
3
  /**
4
4
  * 7-pointed star based on Fourier harmonics.
package/dist/index.cjs CHANGED
@@ -322,7 +322,9 @@ function computeTrailQuad(trail, i, trailCount, toX, toY, minWidth = TRAIL_MIN_W
322
322
  };
323
323
  }
324
324
  function computeBoundaries(pts, logicalWidth, logicalHeight, minPaddingPx = FIT_PADDING_MIN) {
325
- if (pts.length === 0) return null;
325
+ if (pts.length === 0) {
326
+ return null;
327
+ }
326
328
  const first = pts[0];
327
329
  let minX = first.x, maxX = first.x, minY = first.y, maxY = first.y;
328
330
  for (const p of pts) {
@@ -520,7 +522,11 @@ function getPaletteColor(palette, position, timeOffset = 0) {
520
522
  return lerpOklab(c1, c2, t);
521
523
  }
522
524
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
523
- var BASE_RENDER_OPTION_KEYS = /* @__PURE__ */ new Set(["trailColor", "trailStyle"]);
525
+ var BASE_RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
526
+ "trailColor",
527
+ "trailStyle",
528
+ "skeletonColor"
529
+ ]);
524
530
  var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
525
531
  "trailColor",
526
532
  "headColor",
@@ -541,6 +547,9 @@ function validateBaseRenderOptions(partial) {
541
547
  if (partial.trailStyle !== void 0) {
542
548
  assertTrailStyle(partial.trailStyle);
543
549
  }
550
+ if (partial.skeletonColor !== void 0) {
551
+ assertSkeletonColor(partial.skeletonColor);
552
+ }
544
553
  }
545
554
  function validateRenderOptions(partial) {
546
555
  for (const key of Object.keys(partial)) {
@@ -1426,6 +1435,7 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1426
1435
  trailLength: trailLengthOpt,
1427
1436
  trailColor: initialColor = "#ffffff",
1428
1437
  trailStyle: initialTrailStyle = "default",
1438
+ skeletonColor: skeletonColorOpt = "#ffffff",
1429
1439
  autoStart = true,
1430
1440
  pauseOnHidden: pauseOnHiddenOpt = true,
1431
1441
  initialPhase
@@ -1441,14 +1451,8 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1441
1451
  const cellW = W / cols;
1442
1452
  const cellH = H / rows;
1443
1453
  const dotR = Math.min(cellW, cellH) * 0.36;
1444
- let gradientRgb;
1445
- if (Array.isArray(initialColor)) {
1446
- validateBaseRenderOptions({ trailColor: initialColor });
1447
- gradientRgb = initialColor.map(colorToRgb);
1448
- } else {
1449
- gradientRgb = null;
1450
- }
1451
- let colorRgb = gradientRgb ? gradientRgb[0] : colorToRgb(initialColor);
1454
+ let gradientOklab = null;
1455
+ let colorRgb = { r: 255, g: 255, b: 255 };
1452
1456
  let currentTrailStyle = initialTrailStyle;
1453
1457
  let animTime = 0;
1454
1458
  const ANIM_PERIOD = 6;
@@ -1462,6 +1466,8 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1462
1466
  let pixelMaskCoverages = new Float32Array(0);
1463
1467
  let bgImageData = null;
1464
1468
  let frameImageData = null;
1469
+ let skeletonColorOklab = null;
1470
+ const skeletonDotGrid = new Uint8Array(cols * rows);
1465
1471
  let animationId = null;
1466
1472
  let lastTime = 0;
1467
1473
  let pausedByVisibility = false;
@@ -1520,7 +1526,7 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1520
1526
  }
1521
1527
  function buildBgImageData() {
1522
1528
  bgImageData = new ImageData(W, H);
1523
- const bg = gradientRgb ? gradientRgb[0] : colorRgb;
1529
+ const bg = colorRgb;
1524
1530
  const baseAlpha = 0.05 * 255;
1525
1531
  const { data } = bgImageData;
1526
1532
  const n = cols * rows;
@@ -1537,18 +1543,61 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1537
1543
  }
1538
1544
  }
1539
1545
  }
1540
- function sampleGradientRgb(stops, t) {
1541
- const n = stops.length;
1542
- const scaled = Math.max(0, Math.min(1, t)) * (n - 1);
1543
- const i = Math.min(Math.floor(scaled), n - 2);
1544
- const a = stops[i];
1545
- const bStop = stops[i + 1];
1546
- const mix = scaled - i;
1547
- return {
1548
- r: Math.round(a.r + (bStop.r - a.r) * mix),
1549
- g: Math.round(a.g + (bStop.g - a.g) * mix),
1550
- b: Math.round(a.b + (bStop.b - a.b) * mix)
1551
- };
1546
+ function applyColor(color) {
1547
+ if (Array.isArray(color)) {
1548
+ gradientOklab = color.map((c) => parseColorToOklab(c));
1549
+ colorRgb = oklabToRgb(gradientOklab[0]);
1550
+ } else {
1551
+ gradientOklab = null;
1552
+ colorRgb = colorToRgb(color);
1553
+ }
1554
+ }
1555
+ function applySkeletonColor(color) {
1556
+ skeletonColorOklab = color === "transparent" ? null : parseColorToOklab(color);
1557
+ }
1558
+ function computeSkeletonGrid(skel) {
1559
+ skeletonDotGrid.fill(0);
1560
+ const count = skel.length;
1561
+ for (let i = 0; i < count; i++) {
1562
+ const pt = skel[i];
1563
+ const [c, r] = mapPt(pt.x, pt.y);
1564
+ skeletonDotGrid[r * cols + c] = 1;
1565
+ if (i < count - 1) {
1566
+ const next = skel[i + 1];
1567
+ const [nc, nr] = mapPt(next.x, next.y);
1568
+ const steps = Math.ceil(Math.max(Math.abs(nc - c), Math.abs(nr - r))) * 2;
1569
+ for (let s = 1; s < steps; s++) {
1570
+ const t = s / steps;
1571
+ const ix = pt.x + (next.x - pt.x) * t;
1572
+ const iy = pt.y + (next.y - pt.y) * t;
1573
+ const [ic, ir] = mapPt(ix, iy);
1574
+ skeletonDotGrid[ir * cols + ic] = 1;
1575
+ }
1576
+ }
1577
+ }
1578
+ }
1579
+ function writeSkeletonPixels(data) {
1580
+ if (skeletonColorOklab === null) {
1581
+ return;
1582
+ }
1583
+ const { r, g, b } = oklabToRgb(skeletonColorOklab);
1584
+ const skelBaseAlpha = DEFAULT_SKELETON_OPACITY * 255;
1585
+ const n = cols * rows;
1586
+ for (let dotIdx = 0; dotIdx < n; dotIdx++) {
1587
+ if (!skeletonDotGrid[dotIdx]) {
1588
+ continue;
1589
+ }
1590
+ const start = pixelMaskStarts[dotIdx];
1591
+ const len = pixelMaskLengths[dotIdx];
1592
+ for (let k = 0; k < len; k++) {
1593
+ const px = pixelMaskIndices[start + k];
1594
+ const coverage = pixelMaskCoverages[start + k];
1595
+ data[px] = r;
1596
+ data[px + 1] = g;
1597
+ data[px + 2] = b;
1598
+ data[px + 3] = Math.round(skelBaseAlpha * coverage);
1599
+ }
1600
+ }
1552
1601
  }
1553
1602
  function calculateBoundaries(skel) {
1554
1603
  const b = computeBoundaries(skel, W, H);
@@ -1602,7 +1651,8 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1602
1651
  }
1603
1652
  frameImageData.data.set(bgImageData.data);
1604
1653
  const { data } = frameImageData;
1605
- const sineOffset = currentTrailStyle === "gradient-animated" ? 0.15 * Math.sin(animTime / ANIM_PERIOD * 2 * Math.PI) : 0;
1654
+ writeSkeletonPixels(data);
1655
+ const timeOffset = currentTrailStyle === "gradient-animated" ? animTime / ANIM_PERIOD : 0;
1606
1656
  const n = cols * rows;
1607
1657
  for (let dotIdx = 0; dotIdx < n; dotIdx++) {
1608
1658
  const intensity = grid[dotIdx];
@@ -1610,9 +1660,8 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1610
1660
  continue;
1611
1661
  }
1612
1662
  let r, g, b;
1613
- if (gradientRgb !== null) {
1614
- const t = Math.max(0, Math.min(1, intensity + sineOffset));
1615
- ({ r, g, b } = sampleGradientRgb(gradientRgb, t));
1663
+ if (gradientOklab !== null) {
1664
+ ({ r, g, b } = oklabToRgb(getPaletteColor(gradientOklab, intensity, timeOffset)));
1616
1665
  } else {
1617
1666
  ({ r, g, b } = colorRgb);
1618
1667
  }
@@ -1630,21 +1679,26 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1630
1679
  }
1631
1680
  ctx.putImageData(frameImageData, 0, 0);
1632
1681
  }
1682
+ function completeMorphNow() {
1683
+ engine.completeMorph();
1684
+ morphResolve?.();
1685
+ morphResolve = null;
1686
+ morphReject = null;
1687
+ morphProgress = 0;
1688
+ }
1633
1689
  function renderFrame(deltaTime) {
1634
1690
  if (engine.morphAlpha !== null) {
1635
1691
  morphProgress = Math.min(1, morphProgress + deltaTime / (morphDurationMs / 1e3));
1636
1692
  engine.setMorphAlpha(morphProgress);
1637
1693
  calculateBoundaries(engine.getSarmalSkeleton());
1638
1694
  if (morphProgress >= 1) {
1639
- engine.completeMorph();
1640
- morphResolve?.();
1641
- morphResolve = null;
1642
- morphReject = null;
1643
- morphProgress = 0;
1695
+ completeMorphNow();
1644
1696
  calculateBoundaries(engine.getSarmalSkeleton());
1645
1697
  }
1698
+ computeSkeletonGrid(engine.getSarmalSkeleton());
1646
1699
  } else if (engine.isLiveSkeleton) {
1647
1700
  calculateBoundaries(engine.getSarmalSkeleton());
1701
+ computeSkeletonGrid(engine.getSarmalSkeleton());
1648
1702
  }
1649
1703
  if (currentTrailStyle === "gradient-animated") {
1650
1704
  animTime += deltaTime;
@@ -1658,8 +1712,12 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1658
1712
  renderFrame(deltaTime);
1659
1713
  animationId = requestAnimationFrame(loop);
1660
1714
  }
1715
+ validateBaseRenderOptions({ trailColor: initialColor, skeletonColor: skeletonColorOpt });
1716
+ applyColor(initialColor);
1717
+ applySkeletonColor(skeletonColorOpt);
1661
1718
  calculateBoundaries(engine.getSarmalSkeleton());
1662
1719
  computePixelMask();
1720
+ computeSkeletonGrid(engine.getSarmalSkeleton());
1663
1721
  frameImageData = new ImageData(W, H);
1664
1722
  buildBgImageData();
1665
1723
  if (initialPhase !== void 0) {
@@ -1669,13 +1727,17 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1669
1727
  const instance = {
1670
1728
  /** Starts the animation loop. Does nothing if already running. */
1671
1729
  play() {
1672
- if (animationId !== null) return;
1730
+ if (animationId !== null) {
1731
+ return;
1732
+ }
1673
1733
  lastTime = performance.now();
1674
1734
  loop();
1675
1735
  },
1676
1736
  /** Pauses the animation loop. Preserves current trail state. */
1677
1737
  pause() {
1678
- if (animationId === null) return;
1738
+ if (animationId === null) {
1739
+ return;
1740
+ }
1679
1741
  cancelAnimationFrame(animationId);
1680
1742
  animationId = null;
1681
1743
  engine.cancelSpeedTransition();
@@ -1706,11 +1768,7 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1706
1768
  */
1707
1769
  morphTo(target, opts) {
1708
1770
  if (morphResolve !== null) {
1709
- engine.completeMorph();
1710
- morphResolve();
1711
- morphResolve = null;
1712
- morphReject = null;
1713
- morphProgress = 0;
1771
+ completeMorphNow();
1714
1772
  }
1715
1773
  morphDurationMs = opts?.duration ?? DEFAULT_MORPH_DURATION_MS;
1716
1774
  morphProgress = 0;
@@ -1723,23 +1781,20 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1723
1781
  /**
1724
1782
  * Updates render options on a live instance without stopping the animation.
1725
1783
  *
1726
- * Supported: `trailColor` and `trailStyle`.
1727
- * ! Unsupported fields (`headColor`, `skeletonColor`, `headRadius`, `trailWidth`) throw.
1784
+ * Supported: `trailColor`, `trailStyle`, and `skeletonColor`.
1785
+ * ! Unsupported fields (`headColor`, `headRadius`, `trailWidth`) throw.
1728
1786
  * ! Validation fails the entire call if any field is invalid, leaving options unchanged.
1729
1787
  */
1730
1788
  setRenderOptions(partial) {
1731
1789
  validateBaseRenderOptions(partial);
1732
1790
  let needsRebuildBg = false;
1733
1791
  if (partial.trailColor !== void 0) {
1734
- if (Array.isArray(partial.trailColor)) {
1735
- gradientRgb = partial.trailColor.map(colorToRgb);
1736
- colorRgb = gradientRgb[0];
1737
- } else {
1738
- gradientRgb = null;
1739
- colorRgb = colorToRgb(partial.trailColor);
1740
- }
1792
+ applyColor(partial.trailColor);
1741
1793
  needsRebuildBg = true;
1742
1794
  }
1795
+ if (partial.skeletonColor !== void 0) {
1796
+ applySkeletonColor(partial.skeletonColor);
1797
+ }
1743
1798
  if (partial.trailStyle !== void 0) {
1744
1799
  currentTrailStyle = partial.trailStyle;
1745
1800
  if (currentTrailStyle === "default") {
@@ -1749,9 +1804,13 @@ function createSarmalDotMatrix(canvas, curveDef, options) {
1749
1804
  if (needsRebuildBg) {
1750
1805
  buildBgImageData();
1751
1806
  }
1752
- if (currentTrailStyle !== "default" && gradientRgb === null) {
1807
+ if (currentTrailStyle !== "default" && gradientOklab === null) {
1808
+ console.warn(
1809
+ `[sarmal] dot matrix: trailColor is a single color but trailStyle is "${currentTrailStyle}"; the trail will render as a solid color. Pass an array of hex colors to use a real gradient.`
1810
+ );
1811
+ } else if (currentTrailStyle === "default" && gradientOklab !== null) {
1753
1812
  console.warn(
1754
- "[sarmal] dot matrix: gradient trailStyle has no effect without a trailColor array"
1813
+ '[sarmal] dot matrix: trailColor is an array but trailStyle is "default"; only the first color will be used. Pass a gradient trailStyle to use the whole palette.'
1755
1814
  );
1756
1815
  }
1757
1816
  }