hyperframes 0.7.53 → 0.7.54
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 +316 -38
- package/dist/commands/layout-audit.browser.js +82 -7
- package/dist/studio/assets/{index-Bo8sRL2U.js → index-CMHYjEZ5.js} +1 -1
- package/dist/studio/assets/{index-CJpl5RTK.js → index-pRhCpGPz.js} +3 -3
- package/dist/studio/assets/{index-DHrXh-VF.js → index-uBY329wb.js} +1 -1
- package/dist/studio/index.html +1 -1
- package/package.json +1 -1
|
@@ -796,6 +796,51 @@
|
|
|
796
796
|
};
|
|
797
797
|
}
|
|
798
798
|
|
|
799
|
+
// Text whose glyphs paint with an effectively transparent fill renders
|
|
800
|
+
// invisibly even though the element, its box, opacity and color all read as
|
|
801
|
+
// present — so geometry/occlusion/contrast audits miss it (contrast reads
|
|
802
|
+
// `color`, not the fill that actually paints). `-webkit-text-fill-color`
|
|
803
|
+
// overrides `color` for the glyph fill AND inherits, so a parent's
|
|
804
|
+
// `transparent` fill silently blanks descendant text that has its own opaque
|
|
805
|
+
// `color`. Its computed value already resolves to `color` when unset, so it
|
|
806
|
+
// is the effective fill directly. Clipped text (`background-clip: text`)
|
|
807
|
+
// legitimately uses a transparent fill — BUT only when a background actually
|
|
808
|
+
// paints the glyphs; a `background-clip: text` with no gradient/image and no
|
|
809
|
+
// opaque background-color paints nothing, so it stays reportable.
|
|
810
|
+
function invisibleTextIssue(element, time) {
|
|
811
|
+
const textRect = textRectFor(element);
|
|
812
|
+
if (!textRect) return null;
|
|
813
|
+
const text = textContentFor(element);
|
|
814
|
+
if (!text) return null;
|
|
815
|
+
const cs = getComputedStyle(element);
|
|
816
|
+
// Vendor computed-style props are read by property (camelCase), matching
|
|
817
|
+
// the rest of this script; `webkitTextFillColor` computes to `color` when
|
|
818
|
+
// unset, so it is the effective fill directly.
|
|
819
|
+
const fill = cs.webkitTextFillColor || cs.color;
|
|
820
|
+
if (colorAlpha(fill) > 0.05) return null;
|
|
821
|
+
const clip = cs.webkitBackgroundClip || cs.backgroundClip || "";
|
|
822
|
+
if (/text/i.test(clip)) {
|
|
823
|
+
const bgImage = cs.backgroundImage || "none";
|
|
824
|
+
const paintsGlyphs =
|
|
825
|
+
bgImage !== "none" || colorAlpha(cs.backgroundColor || "rgba(0, 0, 0, 0)") > 0.05;
|
|
826
|
+
// A usable clipped background fills the glyphs — legitimate gradient/solid
|
|
827
|
+
// clipped text. If nothing paints, fall through and report it.
|
|
828
|
+
if (paintsGlyphs) return null;
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
code: "text_not_painted",
|
|
832
|
+
severity: "error",
|
|
833
|
+
time,
|
|
834
|
+
selector: selectorFor(element),
|
|
835
|
+
text,
|
|
836
|
+
message:
|
|
837
|
+
"Text paints with an effectively transparent fill (-webkit-text-fill-color / color), so its glyphs are invisible.",
|
|
838
|
+
rect: textRect,
|
|
839
|
+
fixHint:
|
|
840
|
+
"Set an explicit, opaque `color` on the text — and an explicit `-webkit-text-fill-color` if an ancestor makes the fill transparent. If the transparency is intentional gradient text, add `background-clip: text`.",
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
799
844
|
function candidateAnchor(element) {
|
|
800
845
|
const dataAttributes = {};
|
|
801
846
|
for (const attribute of Array.from(element.attributes)) {
|
|
@@ -881,6 +926,8 @@
|
|
|
881
926
|
issues.push(...textOverflowIssues(element, root, rootRect, time, tolerance));
|
|
882
927
|
const occluded = occludedTextIssue(element, time);
|
|
883
928
|
if (occluded) issues.push(occluded);
|
|
929
|
+
const invisible = invisibleTextIssue(element, time);
|
|
930
|
+
if (invisible) issues.push(invisible);
|
|
884
931
|
}
|
|
885
932
|
|
|
886
933
|
issues.push(...containerOverflowIssues(root, time, tolerance));
|
|
@@ -895,6 +942,31 @@
|
|
|
895
942
|
// actually moved anything and the whole audit run is unreliable. Deliberately
|
|
896
943
|
// a single opaque string (not a structured array) since Node only ever needs
|
|
897
944
|
// equality, not per-element diffing.
|
|
945
|
+
// Pixel-only canvas motion (a 2D/WebGL canvas repainting without any element
|
|
946
|
+
// moving) is invisible to a geometry+opacity fingerprint and false-positived
|
|
947
|
+
// sweep_static (wild report: 4K WebGL comp needed a transparent moving DOM
|
|
948
|
+
// sentinel to pass). Downsample each visible canvas to 8x8 and fold its
|
|
949
|
+
// pixels into the fingerprint. Tainted/zero-sized/unreadable canvases hash
|
|
950
|
+
// to a constant — no worse than today, never a new false NEGATIVE for
|
|
951
|
+
// DOM-motion comps.
|
|
952
|
+
function canvasPixelHash(canvas) {
|
|
953
|
+
try {
|
|
954
|
+
if (!canvas.width || !canvas.height) return "x";
|
|
955
|
+
const off = document.createElement("canvas");
|
|
956
|
+
off.width = 8;
|
|
957
|
+
off.height = 8;
|
|
958
|
+
const ctx = off.getContext("2d");
|
|
959
|
+
if (!ctx) return "x";
|
|
960
|
+
ctx.drawImage(canvas, 0, 0, 8, 8);
|
|
961
|
+
const data = ctx.getImageData(0, 0, 8, 8).data;
|
|
962
|
+
let hash = 0;
|
|
963
|
+
for (let i = 0; i < data.length; i++) hash = (hash * 31 + data[i]) >>> 0;
|
|
964
|
+
return String(hash);
|
|
965
|
+
} catch {
|
|
966
|
+
return "x";
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
898
970
|
window.__hyperframesLayoutGeometry = function collectLayoutGeometry() {
|
|
899
971
|
const root =
|
|
900
972
|
document.querySelector("[data-composition-id][data-width][data-height]") ||
|
|
@@ -903,12 +975,15 @@
|
|
|
903
975
|
const elements = Array.from(root.querySelectorAll("*")).filter((element) =>
|
|
904
976
|
isVisibleElement(element),
|
|
905
977
|
);
|
|
906
|
-
|
|
907
|
-
.
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
978
|
+
const parts = elements.map((element) => {
|
|
979
|
+
const rect = toRect(element.getBoundingClientRect());
|
|
980
|
+
const opacity = round(opacityChain(element));
|
|
981
|
+
return `${rect.left},${rect.top},${rect.width},${rect.height},${opacity}`;
|
|
982
|
+
});
|
|
983
|
+
for (const canvas of root.querySelectorAll("canvas")) {
|
|
984
|
+
if (!isVisibleElement(canvas)) continue;
|
|
985
|
+
parts.push(`c:${canvasPixelHash(canvas)}`);
|
|
986
|
+
}
|
|
987
|
+
return parts.join("|");
|
|
913
988
|
};
|
|
914
989
|
})();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{g as P}from"./index-
|
|
1
|
+
import{g as P}from"./index-pRhCpGPz.js";function j(c,d){for(var s=0;s<d.length;s++){const a=d[s];if(typeof a!="string"&&!Array.isArray(a)){for(const i in a)if(i!=="default"&&!(i in c)){const l=Object.getOwnPropertyDescriptor(a,i);l&&Object.defineProperty(c,i,l.get?l:{enumerable:!0,get:()=>a[i]})}}}return Object.freeze(Object.defineProperty(c,Symbol.toStringTag,{value:"Module"}))}var v={},w;function k(){if(w)return v;w=1,Object.defineProperty(v,"__esModule",{value:!0}),v.default=d;var c=window.OfflineAudioContext||window.webkitOfflineAudioContext;function d(e){var r=a(e);return r.start(0),[i,y,O(e.sampleRate),s].reduce(function(t,o){return o(t)},r.buffer.getChannelData(0))}function s(e){return e.sort(function(r,t){return t.count-r.count}).splice(0,5)[0].tempo}function a(e){var r=e.length,t=e.numberOfChannels,o=e.sampleRate,n=new c(t,r,o),u=n.createBufferSource();u.buffer=e;var f=n.createBiquadFilter();return f.type="lowpass",u.connect(f),f.connect(n.destination),u}function i(e){for(var r=[],t=.9,o=.3,n=15;r.length<n&&t>=o;)r=l(e,t),t-=.05;if(r.length<n)throw new Error("Could not find enough samples for a reliable detection.");return r}function l(e,r){for(var t=[],o=0,n=e.length;o<n;o+=1)e[o]>r&&(t.push(o),o+=1e4);return t}function y(e){var r=[];return e.forEach(function(t,o){for(var n=function(x){var g=e[o+x]-t,_=r.some(function(h){if(h.interval===g)return h.count+=1});_||r.push({interval:g,count:1})},u=0;u<10;u+=1)n(u)}),r}function O(e){return function(r){var t=[];return r.forEach(function(o){if(o.interval!==0){for(var n=60/(o.interval/e);n<90;)n*=2;for(;n>180;)n/=2;n=Math.round(n);var u=t.some(function(f){if(f.tempo===n)return f.count+=o.count});u||t.push({tempo:n,count:o.count})}}),t}}return v}var p,b;function q(){return b||(b=1,p=k().default),p}var m=q();const A=P(m),D=j({__proto__:null,default:A},[m]);export{D as i};
|