@sarmal/core 0.36.4 → 0.37.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 (48) hide show
  1. package/dist/auto-init.cjs +472 -2
  2. package/dist/auto-init.cjs.map +1 -1
  3. package/dist/auto-init.d.cts +6 -0
  4. package/dist/auto-init.d.ts +6 -0
  5. package/dist/auto-init.js +472 -2
  6. package/dist/auto-init.js.map +1 -1
  7. package/dist/curves/artemis2.d.cts +1 -1
  8. package/dist/curves/artemis2.d.ts +1 -1
  9. package/dist/curves/astroid.d.cts +1 -1
  10. package/dist/curves/astroid.d.ts +1 -1
  11. package/dist/curves/deltoid.d.cts +1 -1
  12. package/dist/curves/deltoid.d.ts +1 -1
  13. package/dist/curves/epicycloid3.d.cts +1 -1
  14. package/dist/curves/epicycloid3.d.ts +1 -1
  15. package/dist/curves/epitrochoid7.d.cts +1 -1
  16. package/dist/curves/epitrochoid7.d.ts +1 -1
  17. package/dist/curves/index.d.cts +1 -1
  18. package/dist/curves/index.d.ts +1 -1
  19. package/dist/curves/lame.d.cts +1 -1
  20. package/dist/curves/lame.d.ts +1 -1
  21. package/dist/curves/lissajous32.d.cts +1 -1
  22. package/dist/curves/lissajous32.d.ts +1 -1
  23. package/dist/curves/lissajous43.d.cts +1 -1
  24. package/dist/curves/lissajous43.d.ts +1 -1
  25. package/dist/curves/rose3.d.cts +1 -1
  26. package/dist/curves/rose3.d.ts +1 -1
  27. package/dist/curves/rose5.d.cts +1 -1
  28. package/dist/curves/rose5.d.ts +1 -1
  29. package/dist/curves/rose52.d.cts +1 -1
  30. package/dist/curves/rose52.d.ts +1 -1
  31. package/dist/curves/star.d.cts +1 -1
  32. package/dist/curves/star.d.ts +1 -1
  33. package/dist/curves/star4.d.cts +1 -1
  34. package/dist/curves/star4.d.ts +1 -1
  35. package/dist/curves/star7.d.cts +1 -1
  36. package/dist/curves/star7.d.ts +1 -1
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +3 -3
  39. package/dist/index.d.ts +3 -3
  40. package/dist/index.js.map +1 -1
  41. package/dist/{renderer-shared-CFimm7VD.d.cts → renderer-shared-BDDSZhB6.d.cts} +1 -1
  42. package/dist/{renderer-shared-BZV9ELOa.d.ts → renderer-shared-D7ZDFZqK.d.ts} +1 -1
  43. package/dist/terminal.d.cts +2 -2
  44. package/dist/terminal.d.ts +2 -2
  45. package/dist/{types-CCgSK31t.d.cts → types-DS2VYCEa.d.cts} +3 -2
  46. package/dist/{types-CCgSK31t.d.ts → types-DS2VYCEa.d.ts} +3 -2
  47. package/package.json +2 -1
  48. package/skills/core/SKILL.md +180 -15
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Scans for `<canvas data-sarmal="curveName">` and `<svg data-sarmal="curveName">`
3
3
  * when **DOMContentLoaded** is triggered, and creates a Sarmal instance for each one.
4
+ *
5
+ * Renderer selection:
6
+ * - `svg[data-sarmal]` → SVG renderer (`createSarmalSVG`)
7
+ * - `canvas[data-sarmal]` with no `data-renderer`, or `data-renderer="canvas"` → canvas ribbon renderer (`createSarmal`)
8
+ * - `canvas[data-sarmal][data-renderer="dot-matrix"]` → dot matrix renderer (`createSarmalDotMatrix`)
9
+ * - `canvas[data-sarmal]` with any other `data-renderer` value → `console.error`, element skipped
4
10
  */
5
11
  declare function init(): void;
6
12
 
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * Scans for `<canvas data-sarmal="curveName">` and `<svg data-sarmal="curveName">`
3
3
  * when **DOMContentLoaded** is triggered, and creates a Sarmal instance for each one.
4
+ *
5
+ * Renderer selection:
6
+ * - `svg[data-sarmal]` → SVG renderer (`createSarmalSVG`)
7
+ * - `canvas[data-sarmal]` with no `data-renderer`, or `data-renderer="canvas"` → canvas ribbon renderer (`createSarmal`)
8
+ * - `canvas[data-sarmal][data-renderer="dot-matrix"]` → dot matrix renderer (`createSarmalDotMatrix`)
9
+ * - `canvas[data-sarmal]` with any other `data-renderer` value → `console.error`, element skipped
4
10
  */
5
11
  declare function init(): void;
6
12
 
package/dist/auto-init.js CHANGED
@@ -508,6 +508,11 @@ function getPaletteColor(palette, position, timeOffset = 0) {
508
508
  return lerpOklab(c1, c2, t);
509
509
  }
510
510
  var TRAIL_STYLES = ["default", "gradient-static", "gradient-animated"];
511
+ var BASE_RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
512
+ "trailColor",
513
+ "trailStyle",
514
+ "skeletonColor"
515
+ ]);
511
516
  var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
512
517
  "trailColor",
513
518
  "headColor",
@@ -516,6 +521,22 @@ var RENDER_OPTION_KEYS = /* @__PURE__ */ new Set([
516
521
  "headRadius",
517
522
  "trailWidth"
518
523
  ]);
524
+ function validateBaseRenderOptions(partial) {
525
+ for (const key of Object.keys(partial)) {
526
+ if (!BASE_RENDER_OPTION_KEYS.has(key)) {
527
+ throw new TypeError(`[sarmal] setRenderOptions: unsupported key "${key}" for this renderer`);
528
+ }
529
+ }
530
+ if (partial.trailColor !== void 0) {
531
+ assertTrailColor(partial.trailColor);
532
+ }
533
+ if (partial.trailStyle !== void 0) {
534
+ assertTrailStyle(partial.trailStyle);
535
+ }
536
+ if (partial.skeletonColor !== void 0) {
537
+ assertSkeletonColor(partial.skeletonColor);
538
+ }
539
+ }
519
540
  function validateRenderOptions(partial) {
520
541
  for (const key of Object.keys(partial)) {
521
542
  if (!RENDER_OPTION_KEYS.has(key)) {
@@ -1391,6 +1412,413 @@ function createSarmalSVG(container, curveDef, options) {
1391
1412
  return createSVGRenderer({ container, engine, ...rendererOpts });
1392
1413
  }
1393
1414
 
1415
+ // src/renderer-dot-matrix.ts
1416
+ function createSarmalDotMatrix(canvas, curveDef, options) {
1417
+ const {
1418
+ cols = 32,
1419
+ rows = 32,
1420
+ roundness = 1,
1421
+ trailLength: trailLengthOpt,
1422
+ trailColor: initialColor = "#ffffff",
1423
+ trailStyle: initialTrailStyle = "default",
1424
+ skeletonColor: skeletonColorOpt = "#ffffff",
1425
+ autoStart = true,
1426
+ pauseOnHidden: pauseOnHiddenOpt = true,
1427
+ initialPhase
1428
+ } = options ?? {};
1429
+ const trailLength = trailLengthOpt ?? cols * 3;
1430
+ const engine = createEngine(curveDef, trailLength);
1431
+ if (!canvas.getContext("2d")) {
1432
+ throw new Error("[sarmal] Could not get 2d context from canvas");
1433
+ }
1434
+ const ctx = canvas.getContext("2d");
1435
+ const W = canvas.width;
1436
+ const H = canvas.height;
1437
+ const cellW = W / cols;
1438
+ const cellH = H / rows;
1439
+ const dotR = Math.min(cellW, cellH) * 0.36;
1440
+ let gradientOklab = [];
1441
+ let colorRgb = { r: 255, g: 255, b: 255 };
1442
+ let trailStyle = initialTrailStyle;
1443
+ let trailColor = initialColor;
1444
+ let gradientAnimTime = 0;
1445
+ const grid = new Float32Array(cols * rows);
1446
+ let scale = 1;
1447
+ let offsetX = 0;
1448
+ let offsetY = 0;
1449
+ let pixelMaskStarts = new Uint32Array(0);
1450
+ let pixelMaskLengths = new Uint32Array(0);
1451
+ let pixelMaskIndices = new Uint32Array(0);
1452
+ let pixelMaskCoverages = new Float32Array(0);
1453
+ let bgImageData = null;
1454
+ let frameImageData = null;
1455
+ let skeletonColorOklab = null;
1456
+ const skeletonDotGrid = new Uint8Array(cols * rows);
1457
+ let animationId = null;
1458
+ let lastTime = 0;
1459
+ let pausedByVisibility = false;
1460
+ let morphResolve = null;
1461
+ let morphReject = null;
1462
+ let morphDurationMs = DEFAULT_MORPH_DURATION_MS;
1463
+ let morphProgress = 0;
1464
+ function computePixelMask() {
1465
+ const starts = new Uint32Array(cols * rows);
1466
+ const lengths = new Uint32Array(cols * rows);
1467
+ const allIndices = [];
1468
+ const allCoverages = [];
1469
+ const cornerR = roundness * dotR;
1470
+ const cornerR2 = cornerR * cornerR;
1471
+ const SSAA = 4;
1472
+ const SSAA2 = SSAA * SSAA;
1473
+ for (let row = 0; row < rows; row++) {
1474
+ for (let col = 0; col < cols; col++) {
1475
+ const dotIdx = row * cols + col;
1476
+ const cx = (col + 0.5) * cellW;
1477
+ const cy = (row + 0.5) * cellH;
1478
+ const x0 = Math.max(0, Math.floor(cx - dotR - 1));
1479
+ const x1 = Math.min(W - 1, Math.ceil(cx + dotR + 1));
1480
+ const y0 = Math.max(0, Math.floor(cy - dotR - 1));
1481
+ const y1 = Math.min(H - 1, Math.ceil(cy + dotR + 1));
1482
+ starts[dotIdx] = allIndices.length;
1483
+ let count = 0;
1484
+ for (let py = y0; py <= y1; py++) {
1485
+ for (let px = x0; px <= x1; px++) {
1486
+ let hits = 0;
1487
+ for (let sy = 0; sy < SSAA; sy++) {
1488
+ const spyCenter = py + (sy + 0.5) / SSAA;
1489
+ for (let sx = 0; sx < SSAA; sx++) {
1490
+ const spxCenter = px + (sx + 0.5) / SSAA;
1491
+ const dx = Math.max(Math.abs(spxCenter - cx) - (dotR - cornerR), 0);
1492
+ const dy = Math.max(Math.abs(spyCenter - cy) - (dotR - cornerR), 0);
1493
+ if (dx * dx + dy * dy <= cornerR2) {
1494
+ hits++;
1495
+ }
1496
+ }
1497
+ }
1498
+ if (hits > 0) {
1499
+ allIndices.push((py * W + px) * 4);
1500
+ allCoverages.push(hits / SSAA2);
1501
+ count++;
1502
+ }
1503
+ }
1504
+ }
1505
+ lengths[dotIdx] = count;
1506
+ }
1507
+ }
1508
+ pixelMaskStarts = starts;
1509
+ pixelMaskLengths = lengths;
1510
+ pixelMaskIndices = new Uint32Array(allIndices);
1511
+ pixelMaskCoverages = new Float32Array(allCoverages);
1512
+ }
1513
+ function buildBgImageData() {
1514
+ bgImageData = new ImageData(W, H);
1515
+ const bg = colorRgb;
1516
+ const baseAlpha = 0.05 * 255;
1517
+ const { data } = bgImageData;
1518
+ const n = cols * rows;
1519
+ for (let dotIdx = 0; dotIdx < n; dotIdx++) {
1520
+ const start = pixelMaskStarts[dotIdx];
1521
+ const len = pixelMaskLengths[dotIdx];
1522
+ for (let k = 0; k < len; k++) {
1523
+ const px = pixelMaskIndices[start + k];
1524
+ const coverage = pixelMaskCoverages[start + k];
1525
+ data[px] = bg.r;
1526
+ data[px + 1] = bg.g;
1527
+ data[px + 2] = bg.b;
1528
+ data[px + 3] = Math.round(baseAlpha * coverage);
1529
+ }
1530
+ }
1531
+ }
1532
+ function applyColor(color) {
1533
+ colorRgb = colorToRgb(resolveTrailMainColor(color));
1534
+ gradientOklab = resolveTrailPalette(color).map((c) => parseColorToOklab(c));
1535
+ }
1536
+ function applySkeletonColor(color) {
1537
+ skeletonColorOklab = color === "transparent" ? null : parseColorToOklab(color);
1538
+ }
1539
+ function computeSkeletonGrid(skel) {
1540
+ skeletonDotGrid.fill(0);
1541
+ const count = skel.length;
1542
+ for (let i = 0; i < count; i++) {
1543
+ const pt = skel[i];
1544
+ const [c, r] = mapPt(pt.x, pt.y);
1545
+ skeletonDotGrid[r * cols + c] = 1;
1546
+ if (i < count - 1) {
1547
+ const next = skel[i + 1];
1548
+ const [nc, nr] = mapPt(next.x, next.y);
1549
+ const steps = Math.ceil(Math.max(Math.abs(nc - c), Math.abs(nr - r))) * 2;
1550
+ for (let s = 1; s < steps; s++) {
1551
+ const t = s / steps;
1552
+ const ix = pt.x + (next.x - pt.x) * t;
1553
+ const iy = pt.y + (next.y - pt.y) * t;
1554
+ const [ic, ir] = mapPt(ix, iy);
1555
+ skeletonDotGrid[ir * cols + ic] = 1;
1556
+ }
1557
+ }
1558
+ }
1559
+ }
1560
+ function writeSkeletonPixels(data) {
1561
+ if (skeletonColorOklab === null) {
1562
+ return;
1563
+ }
1564
+ const { r, g, b } = oklabToRgb(skeletonColorOklab);
1565
+ const skelBaseAlpha = DEFAULT_SKELETON_OPACITY * 255;
1566
+ const n = cols * rows;
1567
+ for (let dotIdx = 0; dotIdx < n; dotIdx++) {
1568
+ if (!skeletonDotGrid[dotIdx]) {
1569
+ continue;
1570
+ }
1571
+ const start = pixelMaskStarts[dotIdx];
1572
+ const len = pixelMaskLengths[dotIdx];
1573
+ for (let k = 0; k < len; k++) {
1574
+ const px = pixelMaskIndices[start + k];
1575
+ const coverage = pixelMaskCoverages[start + k];
1576
+ data[px] = r;
1577
+ data[px + 1] = g;
1578
+ data[px + 2] = b;
1579
+ data[px + 3] = Math.round(skelBaseAlpha * coverage);
1580
+ }
1581
+ }
1582
+ }
1583
+ function calculateBoundaries(skel) {
1584
+ const b = computeBoundaries(skel, W, H);
1585
+ if (b) {
1586
+ scale = b.scale;
1587
+ offsetX = b.offsetX;
1588
+ offsetY = b.offsetY;
1589
+ }
1590
+ }
1591
+ function mapPt(x, y) {
1592
+ const px = x * scale + offsetX;
1593
+ const py = y * scale + offsetY;
1594
+ return [
1595
+ Math.max(0, Math.min(cols - 1, Math.round(px / W * (cols - 1)))),
1596
+ Math.max(0, Math.min(rows - 1, Math.round(py / H * (rows - 1))))
1597
+ ];
1598
+ }
1599
+ function stamp(c, r, intensity) {
1600
+ const idx = r * cols + c;
1601
+ if (intensity > grid[idx]) {
1602
+ grid[idx] = intensity;
1603
+ }
1604
+ }
1605
+ function buildGrid(deltaTime) {
1606
+ const trail = engine.tick(deltaTime);
1607
+ const count = engine.trailCount;
1608
+ grid.fill(0);
1609
+ for (let i = 0; i < count; i++) {
1610
+ const pt = trail[i];
1611
+ const intensity = (i + 1) / count;
1612
+ const [c, r] = mapPt(pt.x, pt.y);
1613
+ stamp(c, r, intensity);
1614
+ if (i < count - 1) {
1615
+ const next = trail[i + 1];
1616
+ const [nc, nr] = mapPt(next.x, next.y);
1617
+ const steps = Math.ceil(Math.max(Math.abs(nc - c), Math.abs(nr - r))) * 2;
1618
+ for (let s = 1; s < steps; s++) {
1619
+ const t = s / steps;
1620
+ const ix = pt.x + (next.x - pt.x) * t;
1621
+ const iy = pt.y + (next.y - pt.y) * t;
1622
+ const ii = intensity + 1 / count * t;
1623
+ const [ic, ir] = mapPt(ix, iy);
1624
+ stamp(ic, ir, ii);
1625
+ }
1626
+ }
1627
+ }
1628
+ }
1629
+ function draw() {
1630
+ if (!bgImageData || !frameImageData) {
1631
+ return;
1632
+ }
1633
+ frameImageData.data.set(bgImageData.data);
1634
+ const { data } = frameImageData;
1635
+ writeSkeletonPixels(data);
1636
+ const timeOffset = trailStyle === "gradient-animated" ? gradientAnimTime * 5e-4 : 0;
1637
+ const n = cols * rows;
1638
+ for (let dotIdx = 0; dotIdx < n; dotIdx++) {
1639
+ const intensity = grid[dotIdx];
1640
+ if (intensity <= 0) {
1641
+ continue;
1642
+ }
1643
+ let r, g, b;
1644
+ if (trailStyle !== "default") {
1645
+ ({ r, g, b } = oklabToRgb(getPaletteColor(gradientOklab, intensity, timeOffset)));
1646
+ } else {
1647
+ ({ r, g, b } = colorRgb);
1648
+ }
1649
+ const skelFloor = skeletonColorOklab !== null && skeletonDotGrid[dotIdx] === 1 ? DEFAULT_SKELETON_OPACITY * 255 : 0;
1650
+ const baseA = Math.max(skelFloor, (0.08 + intensity * 0.92) * 255);
1651
+ const start = pixelMaskStarts[dotIdx];
1652
+ const len = pixelMaskLengths[dotIdx];
1653
+ for (let k = 0; k < len; k++) {
1654
+ const px = pixelMaskIndices[start + k];
1655
+ const coverage = pixelMaskCoverages[start + k];
1656
+ data[px] = r;
1657
+ data[px + 1] = g;
1658
+ data[px + 2] = b;
1659
+ data[px + 3] = Math.round(baseA * coverage);
1660
+ }
1661
+ }
1662
+ ctx.putImageData(frameImageData, 0, 0);
1663
+ }
1664
+ function completeMorphNow() {
1665
+ engine.completeMorph();
1666
+ morphResolve?.();
1667
+ morphResolve = null;
1668
+ morphReject = null;
1669
+ morphProgress = 0;
1670
+ }
1671
+ function renderFrame(deltaTime) {
1672
+ if (engine.morphAlpha !== null) {
1673
+ morphProgress = Math.min(1, morphProgress + deltaTime / (morphDurationMs / 1e3));
1674
+ engine.setMorphAlpha(morphProgress);
1675
+ calculateBoundaries(engine.getSarmalSkeleton());
1676
+ if (morphProgress >= 1) {
1677
+ completeMorphNow();
1678
+ calculateBoundaries(engine.getSarmalSkeleton());
1679
+ }
1680
+ computeSkeletonGrid(engine.getSarmalSkeleton());
1681
+ } else if (engine.isLiveSkeleton) {
1682
+ calculateBoundaries(engine.getSarmalSkeleton());
1683
+ computeSkeletonGrid(engine.getSarmalSkeleton());
1684
+ }
1685
+ if (trailStyle === "gradient-animated") {
1686
+ gradientAnimTime += deltaTime * 1e3;
1687
+ }
1688
+ buildGrid(deltaTime);
1689
+ draw();
1690
+ }
1691
+ function loop(timestamp = performance.now()) {
1692
+ const deltaTime = Math.min((timestamp - lastTime) / 1e3, 1 / 30);
1693
+ lastTime = timestamp;
1694
+ renderFrame(deltaTime);
1695
+ animationId = requestAnimationFrame(loop);
1696
+ }
1697
+ validateBaseRenderOptions({ trailColor: initialColor, skeletonColor: skeletonColorOpt });
1698
+ applyColor(initialColor);
1699
+ applySkeletonColor(skeletonColorOpt);
1700
+ warnIfTrailColorMismatch(trailColor, trailStyle);
1701
+ calculateBoundaries(engine.getSarmalSkeleton());
1702
+ computePixelMask();
1703
+ computeSkeletonGrid(engine.getSarmalSkeleton());
1704
+ frameImageData = new ImageData(W, H);
1705
+ buildBgImageData();
1706
+ if (initialPhase !== void 0) {
1707
+ engine.seek(initialPhase);
1708
+ }
1709
+ renderFrame(0);
1710
+ const instance = {
1711
+ /** Starts the animation loop. Does nothing if already running. */
1712
+ play() {
1713
+ if (animationId !== null) {
1714
+ return;
1715
+ }
1716
+ lastTime = performance.now();
1717
+ loop();
1718
+ },
1719
+ /** Pauses the animation loop. Preserves current trail state. */
1720
+ pause() {
1721
+ if (animationId === null) {
1722
+ return;
1723
+ }
1724
+ cancelAnimationFrame(animationId);
1725
+ animationId = null;
1726
+ engine.cancelSpeedTransition();
1727
+ },
1728
+ /** Resets the animation to the start of the curve and clears the grid. */
1729
+ reset() {
1730
+ engine.reset();
1731
+ grid.fill(0);
1732
+ },
1733
+ /** Stops the animation and removes all event listeners. */
1734
+ destroy() {
1735
+ if (animationId !== null) {
1736
+ cancelAnimationFrame(animationId);
1737
+ animationId = null;
1738
+ }
1739
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
1740
+ if (morphReject !== null) {
1741
+ morphReject(new Error("[sarmal] Instance destroyed during morph"));
1742
+ morphResolve = null;
1743
+ morphReject = null;
1744
+ }
1745
+ },
1746
+ ...enginePassthroughs(engine),
1747
+ /**
1748
+ * Smoothly transitions from the current curve to `target`.
1749
+ * If a morph is already in progress, it is snapped to completion before the new one starts.
1750
+ * @returns A Promise that resolves when the transition finishes.
1751
+ */
1752
+ morphTo(target, opts) {
1753
+ if (morphResolve !== null) {
1754
+ completeMorphNow();
1755
+ }
1756
+ morphDurationMs = opts?.duration ?? DEFAULT_MORPH_DURATION_MS;
1757
+ morphProgress = 0;
1758
+ engine.startMorph(target, opts?.morphStrategy);
1759
+ return new Promise((resolve, reject) => {
1760
+ morphResolve = resolve;
1761
+ morphReject = reject;
1762
+ });
1763
+ },
1764
+ /**
1765
+ * Updates render options on a live instance without stopping the animation.
1766
+ *
1767
+ * Supported: `trailColor`, `trailStyle`, and `skeletonColor`.
1768
+ * ! Unsupported fields (`headColor`, `headRadius`, `trailWidth`) throw.
1769
+ * ! Validation fails the entire call if any field is invalid, leaving options unchanged.
1770
+ */
1771
+ setRenderOptions(partial) {
1772
+ validateBaseRenderOptions(partial);
1773
+ let needsRebuildBg = false;
1774
+ if (partial.trailColor !== void 0) {
1775
+ trailColor = partial.trailColor;
1776
+ applyColor(trailColor);
1777
+ needsRebuildBg = true;
1778
+ }
1779
+ if (partial.skeletonColor !== void 0) {
1780
+ applySkeletonColor(partial.skeletonColor);
1781
+ }
1782
+ if (partial.trailStyle !== void 0) {
1783
+ trailStyle = partial.trailStyle;
1784
+ if (trailStyle === "default") {
1785
+ gradientAnimTime = 0;
1786
+ }
1787
+ }
1788
+ if (needsRebuildBg) {
1789
+ buildBgImageData();
1790
+ }
1791
+ if (partial.trailColor !== void 0 || partial.trailStyle !== void 0) {
1792
+ warnIfTrailColorMismatch(trailColor, trailStyle);
1793
+ }
1794
+ }
1795
+ };
1796
+ function handleVisibilityChange() {
1797
+ if (document.hidden) {
1798
+ if (animationId !== null) {
1799
+ instance.pause();
1800
+ pausedByVisibility = true;
1801
+ }
1802
+ } else {
1803
+ if (pausedByVisibility) {
1804
+ pausedByVisibility = false;
1805
+ instance.play();
1806
+ }
1807
+ }
1808
+ }
1809
+ if (pauseOnHiddenOpt) {
1810
+ document.addEventListener("visibilitychange", handleVisibilityChange);
1811
+ }
1812
+ const shouldAutoStart = autoStart !== false;
1813
+ const actuallyAutoStart = shouldAutoStart && !(pauseOnHiddenOpt && document.hidden);
1814
+ if (actuallyAutoStart) {
1815
+ instance.play();
1816
+ } else if (shouldAutoStart) {
1817
+ pausedByVisibility = true;
1818
+ }
1819
+ return instance;
1820
+ }
1821
+
1394
1822
  // src/catmull-rom.ts
1395
1823
  var PERIOD = 2 * Math.PI;
1396
1824
  function catmullRom1D(p0, p1, p2, p3, u) {
@@ -1731,7 +2159,15 @@ function parseTrailColor(value) {
1731
2159
  }
1732
2160
  return value;
1733
2161
  }
2162
+ function parseBoolAttr(value) {
2163
+ if (value === void 0) {
2164
+ return void 0;
2165
+ }
2166
+ return value !== "false";
2167
+ }
1734
2168
  function buildOptions(el2) {
2169
+ const autoStart = parseBoolAttr(el2.dataset.autoStart);
2170
+ const pauseOnHidden = parseBoolAttr(el2.dataset.pauseOnHidden);
1735
2171
  return {
1736
2172
  ...el2.dataset.trailColor && {
1737
2173
  trailColor: parseTrailColor(el2.dataset.trailColor)
@@ -1742,9 +2178,38 @@ function buildOptions(el2) {
1742
2178
  ...el2.dataset.trailLength && { trailLength: parseInt(el2.dataset.trailLength, 10) },
1743
2179
  ...el2.dataset.trailStyle && {
1744
2180
  trailStyle: el2.dataset.trailStyle
2181
+ },
2182
+ ...el2.dataset.trailWidth && { trailWidth: parseFloat(el2.dataset.trailWidth) },
2183
+ ...autoStart !== void 0 && { autoStart },
2184
+ ...pauseOnHidden !== void 0 && { pauseOnHidden },
2185
+ // `!==` undefined so that data-initial-phase="0" is correctly passed as 0
2186
+ ...el2.dataset.initialPhase !== void 0 && {
2187
+ initialPhase: parseFloat(el2.dataset.initialPhase)
1745
2188
  }
1746
2189
  };
1747
2190
  }
2191
+ function buildDotMatrixOptions(el2) {
2192
+ const autoStart = parseBoolAttr(el2.dataset.autoStart);
2193
+ const pauseOnHidden = parseBoolAttr(el2.dataset.pauseOnHidden);
2194
+ return {
2195
+ ...el2.dataset.trailColor && {
2196
+ trailColor: parseTrailColor(el2.dataset.trailColor)
2197
+ },
2198
+ ...el2.dataset.skeletonColor && { skeletonColor: el2.dataset.skeletonColor },
2199
+ ...el2.dataset.trailStyle && {
2200
+ trailStyle: el2.dataset.trailStyle
2201
+ },
2202
+ ...el2.dataset.trailLength && { trailLength: parseInt(el2.dataset.trailLength, 10) },
2203
+ ...autoStart !== void 0 && { autoStart },
2204
+ ...pauseOnHidden !== void 0 && { pauseOnHidden },
2205
+ ...el2.dataset.initialPhase !== void 0 && {
2206
+ initialPhase: parseFloat(el2.dataset.initialPhase)
2207
+ },
2208
+ ...el2.dataset.cols && { cols: parseInt(el2.dataset.cols, 10) },
2209
+ ...el2.dataset.rows && { rows: parseInt(el2.dataset.rows, 10) },
2210
+ ...el2.dataset.roundness && { roundness: parseFloat(el2.dataset.roundness) }
2211
+ };
2212
+ }
1748
2213
  function init() {
1749
2214
  const elements = document.querySelectorAll("canvas[data-sarmal], svg[data-sarmal]");
1750
2215
  elements.forEach((el2) => {
@@ -1756,8 +2221,13 @@ function init() {
1756
2221
  if (!curveDef) {
1757
2222
  return console.error(`[sarmal] "${curveName}" is not a valid curve name`);
1758
2223
  }
1759
- const options = buildOptions(el2);
1760
- const instance = el2 instanceof HTMLCanvasElement ? createSarmal(el2, curveDef, options) : createSarmalSVG(el2, curveDef, options);
2224
+ const renderer = el2.dataset.renderer;
2225
+ if (el2 instanceof HTMLCanvasElement && renderer !== void 0 && renderer !== "canvas" && renderer !== "dot-matrix") {
2226
+ return console.error(
2227
+ `[sarmal] Unknown data-renderer value: "${renderer}". Expected "canvas" or "dot-matrix".`
2228
+ );
2229
+ }
2230
+ const instance = el2 instanceof HTMLCanvasElement && renderer === "dot-matrix" ? createSarmalDotMatrix(el2, curveDef, buildDotMatrixOptions(el2)) : el2 instanceof HTMLCanvasElement ? createSarmal(el2, curveDef, buildOptions(el2)) : createSarmalSVG(el2, curveDef, buildOptions(el2));
1761
2231
  if (el2.dataset.speed) {
1762
2232
  instance.setSpeed(parseFloat(el2.dataset.speed));
1763
2233
  }