hyperframes 0.7.71 → 0.7.72
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/cli.js +4564 -2048
- package/dist/commands/layout-audit.browser.js +203 -0
- package/dist/hyperframe-runtime.js +34 -32
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +34 -32
- package/dist/hyperframes-player.global.js +1 -1
- package/dist/skills/hyperframes/SKILL.md +10 -0
- package/dist/skills/hyperframes/references/capability-menu.md +17 -17
- package/dist/skills/hyperframes-cli/references/compare-and-batch.md +1 -1
- package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +1 -7
- package/dist/studio/assets/{hyperframes-player-C1X90psg.js → hyperframes-player-Bjf2HPzR.js} +1 -1
- package/dist/studio/assets/index-BgZMle9I.css +1 -0
- package/dist/studio/assets/{index-nY4lLBqM.js → index-Ch1hbJ3e.js} +1 -1
- package/dist/studio/assets/{index-DzxDHPR1.js → index-Ct_pxETK.js} +1 -1
- package/dist/studio/assets/index-DCyWLHnx.js +428 -0
- package/dist/studio/index.d.ts +8 -0
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +5383 -3804
- package/dist/studio/index.js.map +1 -1
- package/dist/templates/_shared/AGENTS.md +2 -0
- package/dist/templates/_shared/CLAUDE.md +2 -0
- package/package.json +1 -1
- package/dist/studio/assets/index-CSCh2Vrp.js +0 -428
- package/dist/studio/assets/index-Ceyz8Qt2.css +0 -1
|
@@ -1425,6 +1425,16 @@
|
|
|
1425
1425
|
return issues;
|
|
1426
1426
|
};
|
|
1427
1427
|
|
|
1428
|
+
// Reruns only the overlap detector (same threshold, no new surface) on a fine grid for the dense motion re-sampling pass.
|
|
1429
|
+
window.__hyperframesOverlapAudit = function auditOverlap(options) {
|
|
1430
|
+
const time = options && typeof options.time === "number" ? options.time : 0;
|
|
1431
|
+
const root =
|
|
1432
|
+
document.querySelector("[data-composition-id][data-width][data-height]") ||
|
|
1433
|
+
document.querySelector("[data-composition-id]") ||
|
|
1434
|
+
document.body;
|
|
1435
|
+
return contentOverlapIssues(root, time);
|
|
1436
|
+
};
|
|
1437
|
+
|
|
1428
1438
|
// Frozen-sweep guard (#U10, checkPipeline.ts): a compact per-sample
|
|
1429
1439
|
// fingerprint of every visible element's box + opacity, in DOM order. Node
|
|
1430
1440
|
// calls this once per seeked grid point and compares the strings across the
|
|
@@ -1531,4 +1541,197 @@
|
|
|
1531
1541
|
}
|
|
1532
1542
|
return samples;
|
|
1533
1543
|
};
|
|
1544
|
+
|
|
1545
|
+
// Needle-pivot sampling (off_pivot_rotation). A gauge/clock/radar pointer
|
|
1546
|
+
// whose center-of-rotation sits far from the dial hub. bbox-intrinsic measures
|
|
1547
|
+
// can't tell a correct sweep from a broken one (a base-pivoted needle's bbox
|
|
1548
|
+
// center orbits either way), so this records two MATERIAL points on each
|
|
1549
|
+
// elongated rotating SVG figure — mapped through getScreenCTM so the actual
|
|
1550
|
+
// rendered transform is honored regardless of svgOrigin/transform-origin — and
|
|
1551
|
+
// the dial's static hub (the point shared by the most non-rotating circles).
|
|
1552
|
+
// The pipeline fits a rotation to the material-point trajectories to recover
|
|
1553
|
+
// the real center-of-rotation and flags it when it drifts off that hub.
|
|
1554
|
+
function ctmRotationDeg(ctm) {
|
|
1555
|
+
if (!ctm) return null;
|
|
1556
|
+
return (Math.atan2(ctm.b, ctm.a) * 180) / Math.PI;
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
function ctmScale(ctm) {
|
|
1560
|
+
return Math.hypot(ctm.a, ctm.b);
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function mapPoint(svg, ctm, x, y) {
|
|
1564
|
+
const point = svg.createSVGPoint();
|
|
1565
|
+
point.x = x;
|
|
1566
|
+
point.y = y;
|
|
1567
|
+
const mapped = point.matrixTransform(ctm);
|
|
1568
|
+
return { x: mapped.x, y: mapped.y };
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// Walks up to (and including) the composition root, NOT just the owner <svg>:
|
|
1572
|
+
// an element spun by a div ancestor above its svg must not be mistaken for a
|
|
1573
|
+
// static hub anchor (else a lone rotating arc becomes its own dial center).
|
|
1574
|
+
function hasRotatedAncestor(element, root) {
|
|
1575
|
+
let node = element;
|
|
1576
|
+
while (node) {
|
|
1577
|
+
const angle = rotationAngleDeg(getComputedStyle(node).transform);
|
|
1578
|
+
if (angle !== null && Math.abs(angle) > 1) return true;
|
|
1579
|
+
if (node === root) break;
|
|
1580
|
+
node = node.parentElement;
|
|
1581
|
+
}
|
|
1582
|
+
return false;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// KEEP IN SYNC with `fitCircle` in packages/cli/src/utils/checkPipeline.ts —
|
|
1586
|
+
// this browser copy resolves arc-drawn dial hubs and is injected as a raw
|
|
1587
|
+
// string (no import across the puppeteer boundary), so the Kåsa math is
|
|
1588
|
+
// intentionally duplicated per-language. Any change must land in both copies.
|
|
1589
|
+
function fitCirclePoints(points) {
|
|
1590
|
+
const count = points.length;
|
|
1591
|
+
if (count < 3) return null;
|
|
1592
|
+
const meanX = points.reduce((sum, p) => sum + p.x, 0) / count;
|
|
1593
|
+
const meanY = points.reduce((sum, p) => sum + p.y, 0) / count;
|
|
1594
|
+
let suu = 0,
|
|
1595
|
+
svv = 0,
|
|
1596
|
+
suv = 0,
|
|
1597
|
+
suuu = 0,
|
|
1598
|
+
svvv = 0,
|
|
1599
|
+
suvv = 0,
|
|
1600
|
+
svuu = 0;
|
|
1601
|
+
for (const point of points) {
|
|
1602
|
+
const u = point.x - meanX;
|
|
1603
|
+
const v = point.y - meanY;
|
|
1604
|
+
suu += u * u;
|
|
1605
|
+
svv += v * v;
|
|
1606
|
+
suv += u * v;
|
|
1607
|
+
suuu += u * u * u;
|
|
1608
|
+
svvv += v * v * v;
|
|
1609
|
+
suvv += u * v * v;
|
|
1610
|
+
svuu += v * u * u;
|
|
1611
|
+
}
|
|
1612
|
+
const det = suu * svv - suv * suv;
|
|
1613
|
+
if (Math.abs(det) < 1e-6) return null;
|
|
1614
|
+
const uc = (((suuu + suvv) / 2) * svv - ((svvv + svuu) / 2) * suv) / det;
|
|
1615
|
+
const vc = (((svvv + svuu) / 2) * suu - ((suuu + suvv) / 2) * suv) / det;
|
|
1616
|
+
const cx = uc + meanX;
|
|
1617
|
+
const cy = vc + meanY;
|
|
1618
|
+
const radius = Math.sqrt(uc * uc + vc * vc + (suu + svv) / count);
|
|
1619
|
+
let squaredError = 0;
|
|
1620
|
+
for (const point of points) {
|
|
1621
|
+
const delta = Math.hypot(point.x - cx, point.y - cy) - radius;
|
|
1622
|
+
squaredError += delta * delta;
|
|
1623
|
+
}
|
|
1624
|
+
return { cx, cy, radius, residual: Math.sqrt(squaredError / count) };
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// Fallback for dials drawn as arc <path> rather than <circle> rings: sample
|
|
1628
|
+
// the largest static, near-circular path and recover its arc center.
|
|
1629
|
+
function arcHubForSvg(svg, root) {
|
|
1630
|
+
let best = null;
|
|
1631
|
+
for (const path of Array.from(svg.querySelectorAll("path"))) {
|
|
1632
|
+
if (hasRotatedAncestor(path, root)) continue;
|
|
1633
|
+
if (typeof path.getTotalLength !== "function") continue;
|
|
1634
|
+
const total = path.getTotalLength();
|
|
1635
|
+
if (total < 200) continue;
|
|
1636
|
+
const ctm = path.getScreenCTM();
|
|
1637
|
+
if (!ctm) continue;
|
|
1638
|
+
const points = [];
|
|
1639
|
+
for (let i = 0; i <= 16; i++) {
|
|
1640
|
+
const local = path.getPointAtLength((total * i) / 16);
|
|
1641
|
+
points.push(mapPoint(svg, ctm, local.x, local.y));
|
|
1642
|
+
}
|
|
1643
|
+
const fit = fitCirclePoints(points);
|
|
1644
|
+
if (!fit || fit.radius < 40) continue;
|
|
1645
|
+
if (fit.residual > 0.05 * fit.radius) continue;
|
|
1646
|
+
if (!best || fit.radius > best.radius) best = fit;
|
|
1647
|
+
}
|
|
1648
|
+
return best ? { hx: best.cx, hy: best.cy, hr: best.radius, count: 2 } : null;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
function dialHubForSvg(svg, root) {
|
|
1652
|
+
const centers = [];
|
|
1653
|
+
for (const circle of Array.from(svg.querySelectorAll("circle"))) {
|
|
1654
|
+
if (hasRotatedAncestor(circle, root)) continue;
|
|
1655
|
+
const ctm = circle.getScreenCTM();
|
|
1656
|
+
if (!ctm) continue;
|
|
1657
|
+
const cx = Number.parseFloat(circle.getAttribute("cx") || "0");
|
|
1658
|
+
const cy = Number.parseFloat(circle.getAttribute("cy") || "0");
|
|
1659
|
+
const center = mapPoint(svg, ctm, cx, cy);
|
|
1660
|
+
const radius = Number.parseFloat(circle.getAttribute("r") || "0") * ctmScale(ctm);
|
|
1661
|
+
centers.push({ x: center.x, y: center.y, radius });
|
|
1662
|
+
}
|
|
1663
|
+
let best = null;
|
|
1664
|
+
for (const anchor of centers) {
|
|
1665
|
+
const cluster = centers.filter(
|
|
1666
|
+
(other) => Math.hypot(other.x - anchor.x, other.y - anchor.y) <= 8,
|
|
1667
|
+
);
|
|
1668
|
+
if (!best || cluster.length > best.cluster.length) best = { anchor, cluster };
|
|
1669
|
+
}
|
|
1670
|
+
if (best && best.cluster.length >= 2) {
|
|
1671
|
+
const count = best.cluster.length;
|
|
1672
|
+
const hx = best.cluster.reduce((sum, item) => sum + item.x, 0) / count;
|
|
1673
|
+
const hy = best.cluster.reduce((sum, item) => sum + item.y, 0) / count;
|
|
1674
|
+
const hr = best.cluster.reduce((max, item) => Math.max(max, item.radius), 0);
|
|
1675
|
+
return { hx, hy, hr, count };
|
|
1676
|
+
}
|
|
1677
|
+
return arcHubForSvg(svg, root);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
window.__hyperframesOffPivotRotationSample = function collectOffPivotRotationSample() {
|
|
1681
|
+
const root =
|
|
1682
|
+
document.querySelector("[data-composition-id][data-width][data-height]") ||
|
|
1683
|
+
document.querySelector("[data-composition-id]") ||
|
|
1684
|
+
document.body;
|
|
1685
|
+
const samples = [];
|
|
1686
|
+
const hubCache = new Map();
|
|
1687
|
+
const CANDIDATE_CAP = 60;
|
|
1688
|
+
for (const element of Array.from(
|
|
1689
|
+
root.querySelectorAll("path, polygon, line, rect, polyline, g"),
|
|
1690
|
+
)) {
|
|
1691
|
+
if (samples.length >= CANDIDATE_CAP) break;
|
|
1692
|
+
const svg = element.ownerSVGElement;
|
|
1693
|
+
if (!svg || typeof element.getBBox !== "function") continue;
|
|
1694
|
+
if (element.closest("[data-layout-allow-orbit]")) continue;
|
|
1695
|
+
if (!isVisibleElement(element, 0.05)) continue;
|
|
1696
|
+
const ctm = element.getScreenCTM();
|
|
1697
|
+
const angle = ctmRotationDeg(ctm);
|
|
1698
|
+
if (ctm === null || angle === null) continue;
|
|
1699
|
+
let bbox;
|
|
1700
|
+
try {
|
|
1701
|
+
bbox = element.getBBox();
|
|
1702
|
+
} catch {
|
|
1703
|
+
continue;
|
|
1704
|
+
}
|
|
1705
|
+
const long = Math.max(bbox.width, bbox.height);
|
|
1706
|
+
const short = Math.min(bbox.width, bbox.height);
|
|
1707
|
+
if (short <= 0 || long / short < 3 || long < 40) continue;
|
|
1708
|
+
const vertical = bbox.height >= bbox.width;
|
|
1709
|
+
const midMajor = vertical ? bbox.x + bbox.width / 2 : bbox.y + bbox.height / 2;
|
|
1710
|
+
const a = vertical
|
|
1711
|
+
? mapPoint(svg, ctm, midMajor, bbox.y)
|
|
1712
|
+
: mapPoint(svg, ctm, bbox.x, midMajor);
|
|
1713
|
+
const b = vertical
|
|
1714
|
+
? mapPoint(svg, ctm, midMajor, bbox.y + bbox.height)
|
|
1715
|
+
: mapPoint(svg, ctm, bbox.x + bbox.width, midMajor);
|
|
1716
|
+
let hub = hubCache.get(svg);
|
|
1717
|
+
if (hub === undefined) {
|
|
1718
|
+
hub = dialHubForSvg(svg, root);
|
|
1719
|
+
hubCache.set(svg, hub);
|
|
1720
|
+
}
|
|
1721
|
+
samples.push({
|
|
1722
|
+
selector: selectorFor(element),
|
|
1723
|
+
ax: round(a.x),
|
|
1724
|
+
ay: round(a.y),
|
|
1725
|
+
bx: round(b.x),
|
|
1726
|
+
by: round(b.y),
|
|
1727
|
+
len: round(Math.hypot(b.x - a.x, b.y - a.y)),
|
|
1728
|
+
angle: round(angle),
|
|
1729
|
+
hx: hub ? round(hub.hx) : null,
|
|
1730
|
+
hy: hub ? round(hub.hy) : null,
|
|
1731
|
+
hr: hub ? round(hub.hr) : null,
|
|
1732
|
+
hubCount: hub ? hub.count : 0,
|
|
1733
|
+
});
|
|
1734
|
+
}
|
|
1735
|
+
return samples;
|
|
1736
|
+
};
|
|
1534
1737
|
})();
|