hyperframes 0.7.21 → 0.7.23
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 +1384 -510
- package/dist/commands/contrast-audit.browser.js +40 -0
- package/dist/commands/layout-audit.browser.js +91 -3
- package/dist/hyperframe-runtime.js +25 -25
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +25 -25
- package/dist/hyperframes-player.global.js +2 -2
- package/dist/skills/hyperframes/SKILL.md +17 -17
- package/dist/skills/hyperframes-cli/references/preview-render.md +2 -0
- package/dist/studio/assets/hyperframes-player-BGuumSiM.js +459 -0
- package/dist/studio/assets/{index-jbPe1Dih.js → index-B8XMgbWF.js} +157 -157
- package/dist/studio/assets/{index-pAaVqALC.js → index-BRZTrsSs.js} +1 -1
- package/dist/studio/assets/{index-gFA786gK.js → index-DPO5Gw_v.js} +1 -1
- package/dist/studio/{chunk-SBGXX7WY.js → chunk-AN2EWWK3.js} +59 -108
- package/dist/studio/chunk-AN2EWWK3.js.map +1 -0
- package/dist/studio/{domEditingLayers-VZMLL4AP.js → domEditingLayers-EK7R7R4G.js} +4 -2
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +163 -99
- package/dist/studio/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/studio/assets/hyperframes-player-DNLS_l47.js +0 -459
- package/dist/studio/chunk-SBGXX7WY.js.map +0 -1
- /package/dist/studio/{domEditingLayers-VZMLL4AP.js.map → domEditingLayers-EK7R7R4G.js.map} +0 -0
|
@@ -45,6 +45,45 @@ window.__contrastAudit = async function (imgBase64, time) {
|
|
|
45
45
|
return s[Math.floor(s.length / 2)];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
function hasClipPath(el) {
|
|
49
|
+
for (var ce = el; ce; ce = ce.parentElement) {
|
|
50
|
+
var cp = getComputedStyle(ce).clipPath;
|
|
51
|
+
if (cp && cp !== "none") return true;
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
var CLIP_PROBE_COLS = [0.05, 0.25, 0.5, 0.75, 0.95];
|
|
57
|
+
var CLIP_PROBE_ROWS = [0.25, 0.5, 0.75];
|
|
58
|
+
|
|
59
|
+
function paintsAnyProbePoint(el, rect) {
|
|
60
|
+
// Keep probe resolution aligned with layout-audit.browser.js. Edge strips
|
|
61
|
+
// narrower than the nearest probe point are treated as clipped away to
|
|
62
|
+
// avoid noisy typewriter pre-reveal contrast reports.
|
|
63
|
+
for (var ci = 0; ci < CLIP_PROBE_COLS.length; ci++) {
|
|
64
|
+
for (var ri = 0; ri < CLIP_PROBE_ROWS.length; ri++) {
|
|
65
|
+
var x = rect.left + rect.width * CLIP_PROBE_COLS[ci];
|
|
66
|
+
var y = rect.top + rect.height * CLIP_PROBE_ROWS[ri];
|
|
67
|
+
var hit = document.elementFromPoint(x, y);
|
|
68
|
+
if (hit === el || el.contains(hit)) return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// A clip-path can shrink an element's painted region to nothing (a typewriter
|
|
75
|
+
// span pre-reveal at `inset(0 100% 0 0)`, or `circle(0px)`) while its box and
|
|
76
|
+
// colours read normally; it then paints zero pixels and measures a meaningless
|
|
77
|
+
// background-on-background ratio. clip-path drives hit-testing, so a fully
|
|
78
|
+
// clipped element is unreachable by elementFromPoint across its box. Probe only
|
|
79
|
+
// when a clip-path is in effect (self or ancestor) so genuinely-occluded but
|
|
80
|
+
// unclipped text is not skipped.
|
|
81
|
+
function isClippedAway(el, rect) {
|
|
82
|
+
if (typeof document.elementFromPoint !== "function") return false;
|
|
83
|
+
if (!hasClipPath(el)) return false;
|
|
84
|
+
return !paintsAnyProbePoint(el, rect);
|
|
85
|
+
}
|
|
86
|
+
|
|
48
87
|
// Decode screenshot into canvas pixel data
|
|
49
88
|
var img = new Image();
|
|
50
89
|
await new Promise(function (resolve) {
|
|
@@ -110,6 +149,7 @@ window.__contrastAudit = async function (imgBase64, time) {
|
|
|
110
149
|
var rect = el.getBoundingClientRect();
|
|
111
150
|
if (rect.width < 8 || rect.height < 8) continue;
|
|
112
151
|
if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= w || rect.top >= h) continue;
|
|
152
|
+
if (isClippedAway(el, rect)) continue;
|
|
113
153
|
|
|
114
154
|
var fg = parseColor(cs.color);
|
|
115
155
|
if (fg[3] <= 0.01) continue;
|
|
@@ -98,6 +98,50 @@
|
|
|
98
98
|
return opacity;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// A clip-path can shrink an element's painted region to nothing (e.g. a
|
|
102
|
+
// typewriter span pre-reveal at `inset(0 100% 0 0)`, or `circle(0px)`) while
|
|
103
|
+
// its layout box, opacity, visibility and display all still read as present.
|
|
104
|
+
// Such an element paints zero pixels, so flagging it for overlap/occlusion is
|
|
105
|
+
// a false positive. clip-path also drives hit-testing, so an element clipped
|
|
106
|
+
// to nothing is unreachable by elementFromPoint anywhere in its box; only run
|
|
107
|
+
// the probe when a clip-path is actually in effect (self or ancestor) to avoid
|
|
108
|
+
// mistaking a genuinely-occluded element for a clipped one.
|
|
109
|
+
function hasClipPath(element) {
|
|
110
|
+
for (let current = element; current; current = current.parentElement) {
|
|
111
|
+
const clip = getComputedStyle(current).clipPath;
|
|
112
|
+
if (clip && clip !== "none") return true;
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const CLIP_PROBE_COLS = [0.05, 0.25, 0.5, 0.75, 0.95];
|
|
118
|
+
const CLIP_PROBE_ROWS = [0.25, 0.5, 0.75];
|
|
119
|
+
|
|
120
|
+
function paintsAnyProbePoint(element, rect) {
|
|
121
|
+
// Probe resolution intentionally treats edge strips narrower than the
|
|
122
|
+
// nearest probe point as clipped away. That avoids noisy reports for
|
|
123
|
+
// typewriter pre-reveal states; if a real visible-strip bug appears, add
|
|
124
|
+
// edge probes here before widening the audit surface.
|
|
125
|
+
for (const fx of CLIP_PROBE_COLS) {
|
|
126
|
+
for (const fy of CLIP_PROBE_ROWS) {
|
|
127
|
+
const hit = document.elementFromPoint(
|
|
128
|
+
rect.left + rect.width * fx,
|
|
129
|
+
rect.top + rect.height * fy,
|
|
130
|
+
);
|
|
131
|
+
if (hit === element || element.contains(hit)) return true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function isClippedAway(element) {
|
|
138
|
+
if (typeof document.elementFromPoint !== "function") return false;
|
|
139
|
+
if (!hasClipPath(element)) return false;
|
|
140
|
+
const rect = element.getBoundingClientRect();
|
|
141
|
+
if (rect.width <= 0.5 || rect.height <= 0.5) return false;
|
|
142
|
+
return !paintsAnyProbePoint(element, rect);
|
|
143
|
+
}
|
|
144
|
+
|
|
101
145
|
function isVisibleElement(element) {
|
|
102
146
|
if (IGNORE_TAGS.has(element.tagName)) return false;
|
|
103
147
|
if (hasIgnoreFlag(element)) return false;
|
|
@@ -111,7 +155,8 @@
|
|
|
111
155
|
}
|
|
112
156
|
if (opacityChain(element) < 0.2) return false;
|
|
113
157
|
const rect = element.getBoundingClientRect();
|
|
114
|
-
|
|
158
|
+
if (rect.width <= 0.5 || rect.height <= 0.5) return false;
|
|
159
|
+
return !isClippedAway(element);
|
|
115
160
|
}
|
|
116
161
|
|
|
117
162
|
function textContentFor(element) {
|
|
@@ -473,11 +518,37 @@
|
|
|
473
518
|
return a.contains(b) || b.contains(a);
|
|
474
519
|
}
|
|
475
520
|
|
|
521
|
+
function isInFlow(element) {
|
|
522
|
+
const position = getComputedStyle(element).position;
|
|
523
|
+
return position === "static" || position === "relative" || position === "sticky";
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function nearestFlexGridAncestor(element) {
|
|
527
|
+
for (let parent = element.parentElement; parent; parent = parent.parentElement) {
|
|
528
|
+
const display = getComputedStyle(parent).display;
|
|
529
|
+
if (display.includes("flex") || display.includes("grid")) return parent;
|
|
530
|
+
}
|
|
531
|
+
return null;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Two in-flow text blocks governed by the same flex/grid container are placed
|
|
535
|
+
// by the layout engine, which reserves space for each — they cannot visually
|
|
536
|
+
// collide. Any measured text-rect overlap between them is line-box / leading
|
|
537
|
+
// slop (tight stacks, number lockups, super/subscript units), not a collision.
|
|
538
|
+
// A real overlap bug needs free positioning (absolute/fixed), which keeps a
|
|
539
|
+
// different formatting context and is still flagged.
|
|
540
|
+
function isManagedFlowOverlap(a, b) {
|
|
541
|
+
if (!isInFlow(a) || !isInFlow(b)) return false;
|
|
542
|
+
const container = nearestFlexGridAncestor(a);
|
|
543
|
+
return !!container && container === nearestFlexGridAncestor(b);
|
|
544
|
+
}
|
|
545
|
+
|
|
476
546
|
// Two solid text blocks whose boxes overlap by more than a fifth of the
|
|
477
547
|
// smaller block read as a collision — unreadable, and invisible to the
|
|
478
548
|
// overflow checks, which only compare an element against its container.
|
|
479
549
|
function overlapIssue(a, b, time) {
|
|
480
550
|
if (isNested(a.element, b.element)) return null;
|
|
551
|
+
if (isManagedFlowOverlap(a.element, b.element)) return null;
|
|
481
552
|
const area = intersectionArea(a.rect, b.rect);
|
|
482
553
|
if (area <= Math.min(rectArea(a.rect), rectArea(b.rect)) * 0.2) return null;
|
|
483
554
|
return {
|
|
@@ -537,13 +608,30 @@
|
|
|
537
608
|
return !!hit && hit !== element && !element.contains(hit) && !hit.contains(element);
|
|
538
609
|
}
|
|
539
610
|
|
|
611
|
+
// During a scene-to-scene crossfade the incoming scene paints over the
|
|
612
|
+
// outgoing scene's still-visible text at >= 0.6 opacity — and `--at-transitions`
|
|
613
|
+
// samples exactly that midpoint. That overlap is the transition doing its job,
|
|
614
|
+
// not an occlusion bug. Detect it: the occluder lives in a DIFFERENT composition
|
|
615
|
+
// mount ([data-composition-id]) than the text, and at least one of the two scenes
|
|
616
|
+
// is mid-fade (effective opacity < 1). Two fully-settled scenes overlapping
|
|
617
|
+
// (both opacity 1) is NOT suppressed — that is a real layering bug.
|
|
618
|
+
function isCrossSceneTransitionOverlap(textEl, occluder) {
|
|
619
|
+
const textScene = textEl.closest("[data-composition-id]");
|
|
620
|
+
const occluderScene = occluder.closest("[data-composition-id]");
|
|
621
|
+
if (!textScene || !occluderScene || textScene === occluderScene) return false;
|
|
622
|
+
return Math.min(opacityChain(textScene), opacityChain(occluderScene)) < 0.999;
|
|
623
|
+
}
|
|
624
|
+
|
|
540
625
|
// The opaque element painted over (x, y), or null when the topmost element
|
|
541
|
-
// there is related to the text
|
|
626
|
+
// there is related to the text, non-opaque, or a transient crossfade overlap.
|
|
627
|
+
// fallow-ignore-next-line complexity
|
|
542
628
|
function occluderAt(element, x, y) {
|
|
543
629
|
if (typeof document.elementFromPoint !== "function") return null;
|
|
544
630
|
const hit = document.elementFromPoint(x, y);
|
|
545
631
|
if (!isForeignElement(element, hit)) return null;
|
|
546
|
-
|
|
632
|
+
if (!isOpaqueOccluder(hit)) return null;
|
|
633
|
+
if (isCrossSceneTransitionOverlap(element, hit)) return null;
|
|
634
|
+
return hit;
|
|
547
635
|
}
|
|
548
636
|
|
|
549
637
|
// Sweep a grid across the text box (three rows, not just the mid-line, so
|