hyperframes 0.7.46 → 0.7.48
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 +370 -165
- package/dist/commands/contrast-audit.browser.js +250 -102
- package/dist/hyperframe-runtime.js +25 -25
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +25 -25
- package/dist/studio/assets/{index-DO2MAFSG.js → index-CWHON2kh.js} +1 -1
- package/dist/studio/assets/index-DX02Q1Yl.js +423 -0
- package/dist/studio/assets/index-Dq7FEg0K.css +1 -0
- package/dist/studio/assets/{index-BGVoOXdg.js → index-RTqYPUN7.js} +1 -1
- package/dist/studio/index.d.ts +96 -29
- package/dist/studio/index.html +2 -2
- package/dist/studio/index.js +8443 -4305
- package/dist/studio/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/studio/assets/index-BsLQ2qGn.js +0 -420
- package/dist/studio/assets/index-DoVLXke0.css +0 -1
|
@@ -2,25 +2,59 @@
|
|
|
2
2
|
// Loaded as a raw string and injected via page.addScriptTag to avoid
|
|
3
3
|
// esbuild mangling (page.evaluate serializes functions; __name helpers break).
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Two-phase API — see packages/cli/src/commands/validate.ts's
|
|
6
|
+
// runContrastAudit() for the calling contract:
|
|
7
|
+
//
|
|
8
|
+
// 1. window.__contrastAuditPrepare() walks the DOM for text-bearing
|
|
9
|
+
// elements, computes each one's foreground paint (CSS `color`, or SVG
|
|
10
|
+
// `fill` for SVG text — fill and color are independent CSS properties
|
|
11
|
+
// in SVG), and HIDES that element's own text paint (color/fill set to
|
|
12
|
+
// transparent). Hiding is layout-neutral — it doesn't reflow anything —
|
|
13
|
+
// so the caller can screenshot the frame right after with the glyphs
|
|
14
|
+
// invisible but everything else unchanged. Returns the candidate list
|
|
15
|
+
// (selector, text, fg, bbox, font metrics).
|
|
16
|
+
// 2. Caller takes ONE screenshot (page.screenshot()) — same number of
|
|
17
|
+
// screenshots as before, just moved to after prepare() instead of
|
|
18
|
+
// before it.
|
|
19
|
+
// 3. window.__contrastAuditFinish(imgBase64, time, candidates) restores
|
|
20
|
+
// the original paint FIRST (so a slow/failed decode can't leave the
|
|
21
|
+
// page's text stuck invisible), then decodes the screenshot and, for
|
|
22
|
+
// each candidate, samples the real composited pixels directly INSIDE
|
|
23
|
+
// its own bounding box for the true background.
|
|
24
|
+
//
|
|
25
|
+
// Why sample inside the box instead of the 4px ring just outside it (the
|
|
26
|
+
// previous approach): the ring is a proximity heuristic that's wrong
|
|
27
|
+
// whenever what's immediately outside the text differs from what's actually
|
|
28
|
+
// behind it — a neighboring panel/component just past the text's edge, a
|
|
29
|
+
// rounded pill/button whose corner falls inside the ring, a
|
|
30
|
+
// backdrop-filter-blurred glass panel sized only a couple pixels larger
|
|
31
|
+
// than the text, or a translucent decoration that only partially overlaps
|
|
32
|
+
// the ring (or sits entirely inside the bbox, never touching the ring at
|
|
33
|
+
// all). Hiding the glyphs and sampling their own box side-steps all of
|
|
34
|
+
// that: it reads the exact pixels that were behind them.
|
|
35
|
+
//
|
|
36
|
+
// window.__contrastAuditRestoreIfPending() is a safety net: if the caller's
|
|
37
|
+
// screenshot or finish() call throws between prepare() and finish(), calling
|
|
38
|
+
// this restores any still-hidden paint so the next sample in the loop
|
|
39
|
+
// doesn't audit a page with stale invisible text. It's a no-op after a
|
|
40
|
+
// normal finish() call.
|
|
41
|
+
//
|
|
42
|
+
// NOTE: this logic (DOM-walk, foreground/paint-hide, background sampling)
|
|
43
|
+
// plus the pure WCAG math (relLum, wcagRatio, median) is duplicated in
|
|
44
|
+
// skills/hyperframes-creative/scripts/contrast-report.mjs — keep in sync.
|
|
45
|
+
// The pure "which rect to sample" decision is also mirrored in
|
|
46
|
+
// contrast-sample.ts (unit-tested there since this file can't import).
|
|
7
47
|
|
|
8
48
|
/* eslint-disable */
|
|
9
|
-
window.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function wcagRatio(r1, g1, b1, r2, g2, b2) {
|
|
19
|
-
var l1 = relLum(r1, g1, b1),
|
|
20
|
-
l2 = relLum(r2, g2, b2);
|
|
21
|
-
var hi = l1 > l2 ? l1 : l2,
|
|
22
|
-
lo = l1 > l2 ? l2 : l1;
|
|
23
|
-
return (hi + 0.05) / (lo + 0.05);
|
|
49
|
+
window.__contrastAuditPrepare = function () {
|
|
50
|
+
// SVG text (<text>, <tspan>, <textPath>) is painted via the `fill`
|
|
51
|
+
// property, not `color` — the two are independent CSS properties in SVG.
|
|
52
|
+
// A page can set `fill` (inline style, `fill` attribute, or a CSS rule)
|
|
53
|
+
// without ever touching `color`, in which case getComputedStyle(el).color
|
|
54
|
+
// resolves to the inherited/initial value (often black) and does not
|
|
55
|
+
// reflect what's actually rendered on screen.
|
|
56
|
+
function isSvgTextElement(el) {
|
|
57
|
+
return !!el.ownerSVGElement;
|
|
24
58
|
}
|
|
25
59
|
|
|
26
60
|
function parseColor(c) {
|
|
@@ -32,19 +66,32 @@ window.__contrastAudit = async function (imgBase64, time) {
|
|
|
32
66
|
return [p[0], p[1], p[2], p[3] != null ? p[3] : 1];
|
|
33
67
|
}
|
|
34
68
|
|
|
69
|
+
// Like parseColor, but returns null instead of defaulting to black when the
|
|
70
|
+
// value isn't a solid rgb()/rgba() color — e.g. SVG paint keywords such as
|
|
71
|
+
// "none"/"context-fill", or a gradient/pattern reference like
|
|
72
|
+
// 'url("#grad")'. Callers should fall back to another source of truth
|
|
73
|
+
// rather than trust a fabricated black.
|
|
74
|
+
function tryParseSolidColor(c) {
|
|
75
|
+
var m = c.match(/rgba?\(([^)]+)\)/);
|
|
76
|
+
if (!m) return null;
|
|
77
|
+
var p = m[1].split(",").map(function (s) {
|
|
78
|
+
return parseFloat(s.trim());
|
|
79
|
+
});
|
|
80
|
+
if (
|
|
81
|
+
p.some(function (v) {
|
|
82
|
+
return isNaN(v);
|
|
83
|
+
})
|
|
84
|
+
)
|
|
85
|
+
return null;
|
|
86
|
+
return [p[0], p[1], p[2], p[3] != null ? p[3] : 1];
|
|
87
|
+
}
|
|
88
|
+
|
|
35
89
|
function selectorOf(el) {
|
|
36
90
|
if (el.id) return "#" + el.id;
|
|
37
91
|
var cls = Array.from(el.classList).slice(0, 2).join(".");
|
|
38
92
|
return cls ? el.tagName.toLowerCase() + "." + cls : el.tagName.toLowerCase();
|
|
39
93
|
}
|
|
40
94
|
|
|
41
|
-
function median(arr) {
|
|
42
|
-
var s = arr.slice().sort(function (a, b) {
|
|
43
|
-
return a - b;
|
|
44
|
-
});
|
|
45
|
-
return s[Math.floor(s.length / 2)];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
95
|
function hasClipPath(el) {
|
|
49
96
|
for (var ce = el; ce; ce = ce.parentElement) {
|
|
50
97
|
var cp = getComputedStyle(ce).clipPath;
|
|
@@ -84,28 +131,15 @@ window.__contrastAudit = async function (imgBase64, time) {
|
|
|
84
131
|
return !paintsAnyProbePoint(el, rect);
|
|
85
132
|
}
|
|
86
133
|
|
|
87
|
-
// Decode screenshot into canvas pixel data
|
|
88
|
-
var img = new Image();
|
|
89
|
-
await new Promise(function (resolve) {
|
|
90
|
-
img.onload = resolve;
|
|
91
|
-
img.onerror = function () {
|
|
92
|
-
resolve();
|
|
93
|
-
};
|
|
94
|
-
img.src = "data:image/png;base64," + imgBase64;
|
|
95
|
-
});
|
|
96
|
-
if (!img.naturalWidth) return [];
|
|
97
|
-
var canvas = document.createElement("canvas");
|
|
98
|
-
canvas.width = img.naturalWidth || 1920;
|
|
99
|
-
canvas.height = img.naturalHeight || 1080;
|
|
100
|
-
var ctx = canvas.getContext("2d");
|
|
101
|
-
if (!ctx) return [];
|
|
102
|
-
ctx.drawImage(img, 0, 0);
|
|
103
|
-
var px = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
104
|
-
var w = canvas.width;
|
|
105
|
-
var h = canvas.height;
|
|
106
|
-
|
|
107
|
-
// Walk DOM for text elements
|
|
108
134
|
var out = [];
|
|
135
|
+
var restores = [];
|
|
136
|
+
// Registered BEFORE the walk starts (not after it finishes) and pushed to
|
|
137
|
+
// incrementally as each element is hidden: if getComputedStyle/
|
|
138
|
+
// getBoundingClientRect/etc. throws partway through the walk (e.g. on a
|
|
139
|
+
// detached or otherwise pathological element), everything hidden before
|
|
140
|
+
// the throw is still reachable for restore instead of leaking hidden
|
|
141
|
+
// indefinitely.
|
|
142
|
+
window.__contrastAuditRestores = restores;
|
|
109
143
|
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT);
|
|
110
144
|
var node;
|
|
111
145
|
while ((node = walker.nextNode())) {
|
|
@@ -148,85 +182,199 @@ window.__contrastAudit = async function (imgBase64, time) {
|
|
|
148
182
|
if (ancHidden) continue;
|
|
149
183
|
var rect = el.getBoundingClientRect();
|
|
150
184
|
if (rect.width < 8 || rect.height < 8) continue;
|
|
151
|
-
if (rect.right <= 0 || rect.bottom <= 0
|
|
185
|
+
if (rect.right <= 0 || rect.bottom <= 0) continue;
|
|
152
186
|
if (isClippedAway(el, rect)) continue;
|
|
153
187
|
|
|
154
|
-
|
|
188
|
+
// For SVG text, `fill` is the paint that's actually rendered; `color` is
|
|
189
|
+
// frequently just the inherited/initial value and unrelated to what's on
|
|
190
|
+
// screen. Only trust `fill` when it resolves to a solid color — "none",
|
|
191
|
+
// "context-fill", and gradient/pattern refs (url(#...)) fall back to
|
|
192
|
+
// `color` rather than crashing parseColor or reporting a fabricated
|
|
193
|
+
// black.
|
|
194
|
+
var isSvgText = isSvgTextElement(el);
|
|
195
|
+
var fg = isSvgText ? tryParseSolidColor(cs.fill) || parseColor(cs.color) : parseColor(cs.color);
|
|
155
196
|
if (fg[3] <= 0.01) continue;
|
|
156
197
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
198
|
+
var fontSize = parseFloat(cs.fontSize);
|
|
199
|
+
var fontWeight = Number(cs.fontWeight) || 400;
|
|
200
|
+
var large = fontSize >= 24 || (fontSize >= 19 && fontWeight >= 700);
|
|
201
|
+
|
|
202
|
+
// Hide this element's OWN text paint so the caller's next screenshot
|
|
203
|
+
// reveals the true pixels behind the glyphs. Layout-neutral: color/fill
|
|
204
|
+
// never affect box geometry. `!important` beats any non-!important rule
|
|
205
|
+
// that might otherwise win on specificity; we restore the exact prior
|
|
206
|
+
// inline value (or remove the property entirely) afterward.
|
|
207
|
+
//
|
|
208
|
+
// A `transition` on color/fill would otherwise animate this hide instead
|
|
209
|
+
// of applying it instantly — the caller's screenshot lands in the same
|
|
210
|
+
// task-queue gap as the transition's own frames, so it can catch a
|
|
211
|
+
// partially-transparent (still partly the original color) glyph instead
|
|
212
|
+
// of a fully hidden one, contaminating the background sample. Force
|
|
213
|
+
// `transition: none` alongside color/fill so the hide is atomic, and
|
|
214
|
+
// restore it alongside them.
|
|
215
|
+
var origTransition = el.style.getPropertyValue("transition");
|
|
216
|
+
var origTransitionPriority = el.style.getPropertyPriority("transition");
|
|
217
|
+
el.style.setProperty("transition", "none", "important");
|
|
218
|
+
var origColor = el.style.getPropertyValue("color");
|
|
219
|
+
var origColorPriority = el.style.getPropertyPriority("color");
|
|
220
|
+
el.style.setProperty("color", "transparent", "important");
|
|
221
|
+
var origFill = null,
|
|
222
|
+
origFillPriority = null;
|
|
223
|
+
if (isSvgText) {
|
|
224
|
+
origFill = el.style.getPropertyValue("fill");
|
|
225
|
+
origFillPriority = el.style.getPropertyPriority("fill");
|
|
226
|
+
el.style.setProperty("fill", "transparent", "important");
|
|
174
227
|
}
|
|
228
|
+
restores.push({
|
|
229
|
+
el: el,
|
|
230
|
+
origTransition: origTransition,
|
|
231
|
+
origTransitionPriority: origTransitionPriority,
|
|
232
|
+
origColor: origColor,
|
|
233
|
+
origColorPriority: origColorPriority,
|
|
234
|
+
origFill: origFill,
|
|
235
|
+
origFillPriority: origFillPriority,
|
|
236
|
+
isSvgText: isSvgText,
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
out.push({
|
|
240
|
+
selector: selectorOf(el),
|
|
241
|
+
text: (el.textContent || "").trim().slice(0, 50),
|
|
242
|
+
fg: fg,
|
|
243
|
+
fontSize: fontSize,
|
|
244
|
+
fontWeight: fontWeight,
|
|
245
|
+
large: large,
|
|
246
|
+
bbox: { x: rect.x, y: rect.y, w: rect.width, h: rect.height },
|
|
247
|
+
});
|
|
248
|
+
}
|
|
175
249
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
250
|
+
return out;
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
function __contrastAuditRestoreAll() {
|
|
254
|
+
var restores = window.__contrastAuditRestores;
|
|
255
|
+
if (!restores) return;
|
|
256
|
+
for (var i = 0; i < restores.length; i++) {
|
|
257
|
+
var r = restores[i];
|
|
258
|
+
if (r.origColor) r.el.style.setProperty("color", r.origColor, r.origColorPriority);
|
|
259
|
+
else r.el.style.removeProperty("color");
|
|
260
|
+
if (r.isSvgText) {
|
|
261
|
+
if (r.origFill) r.el.style.setProperty("fill", r.origFill, r.origFillPriority);
|
|
262
|
+
else r.el.style.removeProperty("fill");
|
|
263
|
+
}
|
|
264
|
+
if (r.origTransition)
|
|
265
|
+
r.el.style.setProperty("transition", r.origTransition, r.origTransitionPriority);
|
|
266
|
+
else r.el.style.removeProperty("transition");
|
|
267
|
+
}
|
|
268
|
+
window.__contrastAuditRestores = null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Safety net for the caller: if the screenshot or finish() call throws
|
|
272
|
+
// between prepare() and finish(), call this to restore any still-hidden
|
|
273
|
+
// paint so the next sample in the loop isn't auditing a page with stale
|
|
274
|
+
// invisible text. No-op if finish() already ran normally.
|
|
275
|
+
window.__contrastAuditRestoreIfPending = function () {
|
|
276
|
+
__contrastAuditRestoreAll();
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
window.__contrastAuditFinish = async function (imgBase64, time, candidates) {
|
|
280
|
+
function relLum(r, g, b) {
|
|
281
|
+
function ch(v) {
|
|
282
|
+
var s = v / 255;
|
|
283
|
+
return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4);
|
|
284
|
+
}
|
|
285
|
+
return 0.2126 * ch(r) + 0.7152 * ch(g) + 0.0722 * ch(b);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function wcagRatio(r1, g1, b1, r2, g2, b2) {
|
|
289
|
+
var l1 = relLum(r1, g1, b1),
|
|
290
|
+
l2 = relLum(r2, g2, b2);
|
|
291
|
+
var hi = l1 > l2 ? l1 : l2,
|
|
292
|
+
lo = l1 > l2 ? l2 : l1;
|
|
293
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function median(arr) {
|
|
297
|
+
var s = arr.slice().sort(function (a, b) {
|
|
298
|
+
return a - b;
|
|
299
|
+
});
|
|
300
|
+
return s[Math.floor(s.length / 2)];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Restore original paint first — we already have the screenshot, and we
|
|
304
|
+
// never want a decode failure below to leave the page's text invisible.
|
|
305
|
+
__contrastAuditRestoreAll();
|
|
306
|
+
|
|
307
|
+
var img = new Image();
|
|
308
|
+
await new Promise(function (resolve) {
|
|
309
|
+
img.onload = resolve;
|
|
310
|
+
img.onerror = function () {
|
|
311
|
+
resolve();
|
|
312
|
+
};
|
|
313
|
+
img.src = "data:image/png;base64," + imgBase64;
|
|
314
|
+
});
|
|
315
|
+
if (!img.naturalWidth) return [];
|
|
316
|
+
var canvas = document.createElement("canvas");
|
|
317
|
+
canvas.width = img.naturalWidth || 1920;
|
|
318
|
+
canvas.height = img.naturalHeight || 1080;
|
|
319
|
+
var ctx = canvas.getContext("2d");
|
|
320
|
+
if (!ctx) return [];
|
|
321
|
+
ctx.drawImage(img, 0, 0);
|
|
322
|
+
var px = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
|
|
323
|
+
var w = canvas.width;
|
|
324
|
+
var h = canvas.height;
|
|
325
|
+
|
|
326
|
+
var out = [];
|
|
327
|
+
for (var ci = 0; ci < candidates.length; ci++) {
|
|
328
|
+
var c = candidates[ci];
|
|
329
|
+
var bbox = c.bbox;
|
|
330
|
+
|
|
331
|
+
// Sample the element's OWN box (glyphs are hidden in this screenshot),
|
|
332
|
+
// inset 1px on each side to dodge anti-aliased edge pixels, clamped to
|
|
333
|
+
// the canvas. Mirrors contrast-sample.ts's computeSampleRect.
|
|
334
|
+
var x0 = Math.max(0, Math.round(bbox.x) + 1);
|
|
335
|
+
var x1 = Math.min(w - 1, Math.round(bbox.x + bbox.w) - 1);
|
|
336
|
+
var y0 = Math.max(0, Math.round(bbox.y) + 1);
|
|
337
|
+
var y1 = Math.min(h - 1, Math.round(bbox.y + bbox.h) - 1);
|
|
338
|
+
if (x1 <= x0 || y1 <= y0) continue;
|
|
339
|
+
|
|
340
|
+
// Bounded grid, not a full scan — dense enough to catch a
|
|
341
|
+
// partially-overlapping decoration without turning a wide caption bar
|
|
342
|
+
// into thousands of samples. Mirrors contrast-sample.ts's
|
|
343
|
+
// sampleGridPoints.
|
|
344
|
+
var stepX = Math.max(1, Math.floor((x1 - x0) / 12));
|
|
345
|
+
var stepY = Math.max(1, Math.floor((y1 - y0) / 6));
|
|
346
|
+
var rr = [],
|
|
347
|
+
gg = [],
|
|
348
|
+
bb = [];
|
|
349
|
+
for (var y = y0; y <= y1; y += stepY) {
|
|
350
|
+
for (var x = x0; x <= x1; x += stepX) {
|
|
351
|
+
var idx = (y * w + x) * 4;
|
|
193
352
|
rr.push(px[idx]);
|
|
194
353
|
gg.push(px[idx + 1]);
|
|
195
354
|
bb.push(px[idx + 2]);
|
|
196
|
-
};
|
|
197
|
-
for (var x = x0; x <= x1; x++) {
|
|
198
|
-
sample(x, y0);
|
|
199
|
-
sample(x, y1);
|
|
200
355
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
sample(x1, y);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (rr.length === 0) continue;
|
|
356
|
+
}
|
|
357
|
+
if (rr.length === 0) continue;
|
|
207
358
|
|
|
208
|
-
|
|
209
|
-
bgG = median(gg)
|
|
359
|
+
var bgR = median(rr),
|
|
360
|
+
bgG = median(gg),
|
|
210
361
|
bgB = median(bb);
|
|
211
|
-
}
|
|
212
362
|
|
|
213
|
-
// Composite foreground alpha over measured background
|
|
363
|
+
// Composite foreground alpha over the measured background
|
|
364
|
+
var fg = c.fg;
|
|
214
365
|
var compR = Math.round(fg[0] * fg[3] + bgR * (1 - fg[3]));
|
|
215
366
|
var compG = Math.round(fg[1] * fg[3] + bgG * (1 - fg[3]));
|
|
216
367
|
var compB = Math.round(fg[2] * fg[3] + bgB * (1 - fg[3]));
|
|
217
368
|
|
|
218
369
|
var ratio = +wcagRatio(compR, compG, compB, bgR, bgG, bgB).toFixed(2);
|
|
219
|
-
var fontSize = parseFloat(cs.fontSize);
|
|
220
|
-
var fontWeight = Number(cs.fontWeight) || 400;
|
|
221
|
-
var large = fontSize >= 24 || (fontSize >= 19 && fontWeight >= 700);
|
|
222
370
|
|
|
223
371
|
out.push({
|
|
224
372
|
time: time,
|
|
225
|
-
selector:
|
|
226
|
-
text:
|
|
373
|
+
selector: c.selector,
|
|
374
|
+
text: c.text,
|
|
227
375
|
ratio: ratio,
|
|
228
|
-
wcagAA: large ? ratio >= 3 : ratio >= 4.5,
|
|
229
|
-
large: large,
|
|
376
|
+
wcagAA: c.large ? ratio >= 3 : ratio >= 4.5,
|
|
377
|
+
large: c.large,
|
|
230
378
|
fg: "rgb(" + compR + "," + compG + "," + compB + ")",
|
|
231
379
|
bg: "rgb(" + bgR + "," + bgG + "," + bgB + ")",
|
|
232
380
|
});
|