@monoes/monodesign 1.2.17 → 1.2.20

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.
@@ -0,0 +1,465 @@
1
+ import {
2
+ GENERIC_FONTS,
3
+ OVERUSED_FONTS,
4
+ SAFE_TAGS,
5
+ isBrandFontOnOwnDomain,
6
+ } from '../shared/constants.mjs';
7
+ import { extractGoogleFontFamilies } from '../shared/fonts.mjs';
8
+ import { isCardLikeFromProps } from './checks-pure.mjs';
9
+ import { readOwnBackgroundColor, resolveBorderRadiusPx } from './checks-background.mjs';
10
+ import { parseAnyColor } from './checks-css-values.mjs';
11
+ import { resolveFontSizePx, cssColorIsTransparent } from './checks-quality.mjs';
12
+
13
+ // ─── Section 6: Page-Level Checks ───────────────────────────────────────────
14
+
15
+ // Browser page-level checks — use document/getComputedStyle globals
16
+
17
+ function checkTypography() {
18
+ const findings = [];
19
+
20
+ // Walk actual text-bearing elements and tally font usage by *computed style*.
21
+ // This is much more accurate than scanning CSS rules — it ignores rules that
22
+ // exist in the stylesheet but apply to nothing (e.g. demo classes showing
23
+ // anti-patterns), and counts what the user actually sees.
24
+ const fontUsage = new Map(); // primary font name → count of elements
25
+ let totalTextElements = 0;
26
+ for (const el of document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, li, td, th, dd, blockquote, figcaption, a, button, label, span')) {
27
+ // Skip monodesign's own elements
28
+ if (el.closest?.('.monodesign-overlay, .monodesign-label, .monodesign-banner, .monodesign-tooltip')) continue;
29
+ // Only count elements that actually have visible direct text
30
+ const hasText = [...el.childNodes].some(n => n.nodeType === 3 && n.textContent.trim().length > 0);
31
+ if (!hasText) continue;
32
+ const style = getComputedStyle(el);
33
+ const ff = style.fontFamily;
34
+ if (!ff) continue;
35
+ const stack = ff.split(',').map(f => f.trim().replace(/^['"]|['"]$/g, '').toLowerCase());
36
+ const primary = stack.find(f => f && !GENERIC_FONTS.has(f));
37
+ if (!primary) continue;
38
+ fontUsage.set(primary, (fontUsage.get(primary) || 0) + 1);
39
+ totalTextElements++;
40
+ }
41
+
42
+ if (totalTextElements >= 20) {
43
+ // A font is "primary" if it's used by at least 15% of text elements
44
+ const PRIMARY_THRESHOLD = 0.15;
45
+ for (const [font, count] of fontUsage) {
46
+ const share = count / totalTextElements;
47
+ if (share < PRIMARY_THRESHOLD) continue;
48
+ if (!OVERUSED_FONTS.has(font)) continue;
49
+ if (isBrandFontOnOwnDomain(font)) continue;
50
+ findings.push({ type: 'overused-font', detail: `Primary font: ${font} (${Math.round(share * 100)}% of text)` });
51
+ }
52
+
53
+ // Single-font check: only one distinct primary font across all text
54
+ if (fontUsage.size === 1) {
55
+ const only = [...fontUsage.keys()][0];
56
+ findings.push({ type: 'single-font', detail: `only font used is ${only}` });
57
+ }
58
+ }
59
+
60
+ const sizes = new Set();
61
+ for (const el of document.querySelectorAll('h1,h2,h3,h4,h5,h6,p,span,a,li,td,th,label,button,div')) {
62
+ const fs = parseFloat(getComputedStyle(el).fontSize);
63
+ if (fs > 0 && fs < 200) sizes.add(Math.round(fs * 10) / 10);
64
+ }
65
+ if (sizes.size >= 3) {
66
+ const sorted = [...sizes].sort((a, b) => a - b);
67
+ const ratio = sorted[sorted.length - 1] / sorted[0];
68
+ if (ratio < 2.0) {
69
+ findings.push({ type: 'flat-type-hierarchy', detail: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
70
+ }
71
+ }
72
+
73
+ return findings;
74
+ }
75
+
76
+ function isCardLikeDOM(el) {
77
+ const tag = el.tagName.toLowerCase();
78
+ if (SAFE_TAGS.has(tag) || ['input','select','textarea','img','video','canvas','picture'].includes(tag)) return false;
79
+ const style = getComputedStyle(el);
80
+ const cls = el.getAttribute('class') || '';
81
+ const hasShadow = (style.boxShadow && style.boxShadow !== 'none') || /\bshadow(?:-sm|-md|-lg|-xl|-2xl)?\b/.test(cls);
82
+ const hasBorder = /\bborder\b/.test(cls);
83
+ const hasRadius = parseFloat(style.borderRadius) > 0 || /\brounded(?:-sm|-md|-lg|-xl|-2xl|-full)?\b/.test(cls);
84
+ const hasBg = (style.backgroundColor && style.backgroundColor !== 'rgba(0, 0, 0, 0)') || /\bbg-(?:white|gray-\d+|slate-\d+)\b/.test(cls);
85
+ return isCardLikeFromProps(hasShadow, hasBorder, hasRadius, hasBg);
86
+ }
87
+
88
+ function checkLayout() {
89
+ const findings = [];
90
+ const flaggedEls = new Set();
91
+
92
+ for (const el of document.querySelectorAll('*')) {
93
+ if (!isCardLikeDOM(el) || flaggedEls.has(el)) continue;
94
+ const cls = el.getAttribute('class') || '';
95
+ const style = getComputedStyle(el);
96
+ if (style.position === 'absolute' || style.position === 'fixed') continue;
97
+ if (/\b(?:dropdown|popover|tooltip|menu|modal|dialog)\b/i.test(cls)) continue;
98
+ if ((el.textContent?.trim().length || 0) < 10) continue;
99
+ const rect = el.getBoundingClientRect();
100
+ if (rect.width < 50 || rect.height < 30) continue;
101
+
102
+ let parent = el.parentElement;
103
+ while (parent) {
104
+ if (isCardLikeDOM(parent)) { flaggedEls.add(el); break; }
105
+ parent = parent.parentElement;
106
+ }
107
+ }
108
+
109
+ for (const el of flaggedEls) {
110
+ let isAncestor = false;
111
+ for (const other of flaggedEls) {
112
+ if (other !== el && el.contains(other)) { isAncestor = true; break; }
113
+ }
114
+ if (!isAncestor) findings.push({ type: 'nested-cards', detail: 'Card inside card', el });
115
+ }
116
+
117
+ return findings;
118
+ }
119
+
120
+ // Node page-level checks — take document/window as parameters
121
+
122
+ function checkPageTypography(doc, win) {
123
+ const findings = [];
124
+
125
+ const fonts = new Set();
126
+ const overusedFound = new Set();
127
+
128
+ for (const sheet of doc.styleSheets) {
129
+ let rules;
130
+ try { rules = sheet.cssRules || sheet.rules; } catch { continue; }
131
+ if (!rules) continue;
132
+ for (const rule of rules) {
133
+ if (rule.type !== 1) continue;
134
+ const ff = rule.style?.fontFamily;
135
+ if (!ff) continue;
136
+ const stack = ff.split(',').map(f => f.trim().replace(/^['"]|['"]$/g, '').toLowerCase());
137
+ const primary = stack.find(f => f && !GENERIC_FONTS.has(f));
138
+ if (primary) {
139
+ fonts.add(primary);
140
+ if (OVERUSED_FONTS.has(primary)) overusedFound.add(primary);
141
+ }
142
+ }
143
+ }
144
+
145
+ // Check Google Fonts links in HTML
146
+ const html = doc.documentElement?.outerHTML || '';
147
+ for (const f of extractGoogleFontFamilies(html)) {
148
+ fonts.add(f);
149
+ if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
150
+ }
151
+
152
+ // Also parse raw HTML/style content for font-family (jsdom may not expose all via CSSOM)
153
+ const ffRe = /font-family\s*:\s*([^;}]+)/gi;
154
+ let fm;
155
+ while ((fm = ffRe.exec(html)) !== null) {
156
+ for (const f of fm[1].split(',').map(f => f.trim().replace(/^['"]|['"]$/g, '').toLowerCase())) {
157
+ if (f && !GENERIC_FONTS.has(f)) {
158
+ fonts.add(f);
159
+ if (OVERUSED_FONTS.has(f)) overusedFound.add(f);
160
+ }
161
+ }
162
+ }
163
+
164
+ for (const font of overusedFound) {
165
+ findings.push({ id: 'overused-font', snippet: `Primary font: ${font}` });
166
+ }
167
+
168
+ // Single font
169
+ if (fonts.size === 1) {
170
+ const els = doc.querySelectorAll('*');
171
+ if (els.length >= 20) {
172
+ findings.push({ id: 'single-font', snippet: `only font used is ${[...fonts][0]}` });
173
+ }
174
+ }
175
+
176
+ // Flat type hierarchy
177
+ const sizes = new Set();
178
+ const textEls = doc.querySelectorAll('h1, h2, h3, h4, h5, h6, p, span, a, li, td, th, label, button, div');
179
+ for (const el of textEls) {
180
+ const fontSize = parseFloat(win.getComputedStyle(el).fontSize);
181
+ // Filter out sub-8px values (jsdom doesn't resolve relative units properly)
182
+ if (fontSize >= 8 && fontSize < 200) sizes.add(Math.round(fontSize * 10) / 10);
183
+ }
184
+ if (sizes.size >= 3) {
185
+ const sorted = [...sizes].sort((a, b) => a - b);
186
+ const ratio = sorted[sorted.length - 1] / sorted[0];
187
+ if (ratio < 2.0) {
188
+ findings.push({ id: 'flat-type-hierarchy', snippet: `Sizes: ${sorted.map(s => `${s}px`).join(', ')} (ratio ${ratio.toFixed(1)}:1)` });
189
+ }
190
+ }
191
+
192
+ return findings;
193
+ }
194
+
195
+ function isCardLike(el, win) {
196
+ const tag = el.tagName.toLowerCase();
197
+ if (SAFE_TAGS.has(tag) || ['input', 'select', 'textarea', 'img', 'video', 'canvas', 'picture'].includes(tag)) return false;
198
+
199
+ const style = win.getComputedStyle(el);
200
+ const rawStyle = el.getAttribute?.('style') || '';
201
+ const cls = el.getAttribute?.('class') || '';
202
+
203
+ const hasShadow = (style.boxShadow && style.boxShadow !== 'none') ||
204
+ /\bshadow(?:-sm|-md|-lg|-xl|-2xl)?\b/.test(cls) || /box-shadow/i.test(rawStyle);
205
+ const hasBorder = /\bborder\b/.test(cls);
206
+ const widthPx = parseFloat(style.width) || 0;
207
+ const hasRadius = resolveBorderRadiusPx(el, style, widthPx, win) > 0 ||
208
+ /\brounded(?:-sm|-md|-lg|-xl|-2xl|-full)?\b/.test(cls) || /border-radius/i.test(rawStyle);
209
+ const hasBg = /\bbg-(?:white|gray-\d+|slate-\d+)\b/.test(cls) ||
210
+ /background(?:-color)?\s*:\s*(?!transparent)/i.test(rawStyle);
211
+
212
+ return isCardLikeFromProps(hasShadow, hasBorder, hasRadius, hasBg);
213
+ }
214
+
215
+ function checkPageLayout(doc, win) {
216
+ const findings = [];
217
+
218
+ // Nested cards
219
+ const allEls = doc.querySelectorAll('*');
220
+ const flaggedEls = new Set();
221
+ for (const el of allEls) {
222
+ if (!isCardLike(el, win)) continue;
223
+ if (flaggedEls.has(el)) continue;
224
+
225
+ const tag = el.tagName.toLowerCase();
226
+ const cls = el.getAttribute?.('class') || '';
227
+ const rawStyle = el.getAttribute?.('style') || '';
228
+
229
+ if (['pre', 'code'].includes(tag)) continue;
230
+ if (/\b(?:absolute|fixed)\b/.test(cls) || /position\s*:\s*(?:absolute|fixed)/i.test(rawStyle)) continue;
231
+ if ((el.textContent?.trim().length || 0) < 10) continue;
232
+ if (/\b(?:dropdown|popover|tooltip|menu|modal|dialog)\b/i.test(cls)) continue;
233
+
234
+ // Walk up to find card-like ancestor
235
+ let parent = el.parentElement;
236
+ while (parent) {
237
+ if (isCardLike(parent, win)) {
238
+ flaggedEls.add(el);
239
+ break;
240
+ }
241
+ parent = parent.parentElement;
242
+ }
243
+ }
244
+
245
+ // Only report innermost nested cards
246
+ for (const el of flaggedEls) {
247
+ let isAncestorOfFlagged = false;
248
+ for (const other of flaggedEls) {
249
+ if (other !== el && el.contains(other)) {
250
+ isAncestorOfFlagged = true;
251
+ break;
252
+ }
253
+ }
254
+ if (!isAncestorOfFlagged) {
255
+ findings.push({ id: 'nested-cards', snippet: `Card inside card (${el.tagName.toLowerCase()})` });
256
+ }
257
+ }
258
+
259
+ return findings;
260
+ }
261
+
262
+ // ─── Cream / beige palette (the default "tasteful" AI surface) ────────────────
263
+ // A warm, lightly-tinted off-white page background — light, with R≥G≥B and a
264
+ // small warm tint (not white, not a strong color). The current reflex surface.
265
+ function isCreamColor(rgb) {
266
+ if (!rgb) return false;
267
+ const { r, g, b } = rgb;
268
+ if (Math.min(r, g, b) < 209) return false; // must be light
269
+ if (!(r >= g && g >= b)) return false; // warm ordering
270
+ const warmth = r - b;
271
+ return warmth >= 6 && warmth <= 48; // tinted, not white, not strong
272
+ }
273
+
274
+ // Tailwind background utilities that render as a warm off-white surface. The
275
+ // static engine doesn't fetch Tailwind's CSS, so a `bg-amber-50` on <body>
276
+ // resolves to nothing in computed style — catch it from the class list
277
+ // instead. Candidate tokens map to their actual Tailwind hex and are still
278
+ // filtered through isCreamColor, so neutral grays (stone) and over-saturated
279
+ // shades drop out on their own.
280
+ const TAILWIND_BG_HEX = {
281
+ 'bg-amber-50': '#fffbeb', 'bg-amber-100': '#fef3c7',
282
+ 'bg-orange-50': '#fff7ed', 'bg-orange-100': '#ffedd5',
283
+ 'bg-yellow-50': '#fefce8',
284
+ 'bg-stone-50': '#fafaf9', 'bg-stone-100': '#f5f5f4', 'bg-stone-200': '#e7e5e4',
285
+ };
286
+
287
+ function creamFromClassList(cls) {
288
+ if (!cls) return null;
289
+ // Arbitrary value: bg-[#f5f0e6] / bg-[rgb(245_240_230)] (underscores = spaces).
290
+ const arb = cls.match(/\bbg-\[([^\]]+)\]/);
291
+ if (arb && isCreamColor(parseAnyColor(arb[1].replace(/_/g, ' ')))) return `bg-[${arb[1]}]`;
292
+ // Named warm-light utilities.
293
+ for (const [tok, hex] of Object.entries(TAILWIND_BG_HEX)) {
294
+ if (new RegExp(`(^|\\s)${tok}($|\\s)`).test(cls) && isCreamColor(parseAnyColor(hex))) return tok;
295
+ }
296
+ return null;
297
+ }
298
+
299
+ function checkCreamPalette(doc, win) {
300
+ const findings = [];
301
+ const body = doc.body || (doc.querySelector ? doc.querySelector('body') : null);
302
+ if (!body) return findings;
303
+ const html = doc.documentElement;
304
+ const getCS = (el) => (win ? win.getComputedStyle(el) : getComputedStyle(el));
305
+
306
+ // 1. Computed background — covers inline / <style> / linked CSS, and Tailwind
307
+ // once it's actually rendered (browser path).
308
+ let bg = readOwnBackgroundColor(body, getCS(body));
309
+ if (!bg || bg.a === 0) {
310
+ if (html) bg = readOwnBackgroundColor(html, getCS(html));
311
+ }
312
+ if (isCreamColor(bg)) {
313
+ findings.push({ id: 'cream-palette', snippet: `cream/beige page background rgb(${bg.r}, ${bg.g}, ${bg.b})` });
314
+ return findings;
315
+ }
316
+
317
+ // 2. Tailwind class fallback — for the static path, where utility classes
318
+ // never resolve to computed CSS.
319
+ for (const el of [body, html]) {
320
+ const tok = creamFromClassList(el?.getAttribute ? el.getAttribute('class') : '');
321
+ if (tok) {
322
+ findings.push({ id: 'cream-palette', snippet: `cream/beige page background (Tailwind ${tok})` });
323
+ break;
324
+ }
325
+ }
326
+ return findings;
327
+ }
328
+
329
+ // ─── Oversized hero headline ────────────────────────────────────────────────
330
+ // Fires when a *long* headline is set at display size and actually dominates
331
+ // the viewport. A punchy one- or two-word headline at the same size is a
332
+ // legitimate stylistic choice, and a large-but-contained two-line hero should
333
+ // pass too — length and viewport share together are the tell.
334
+ const OVERSIZED_H1_FONT_PX = 72;
335
+ const OVERSIZED_H1_MIN_CHARS = 40;
336
+ const OVERSIZED_H1_MIN_VIEWPORT_HEIGHT_RATIO = 0.28;
337
+ const OVERSIZED_H1_MIN_VIEWPORT_AREA_RATIO = 0.25;
338
+ function checkOversizedH1({ tag, fontSize, headingText, rect = null, viewportWidth = 0, viewportHeight = 0 }) {
339
+ if (tag !== 'h1') return [];
340
+ const textLen = headingText.length;
341
+ if (fontSize >= OVERSIZED_H1_FONT_PX && textLen >= OVERSIZED_H1_MIN_CHARS) {
342
+ let viewportDetail = '';
343
+ if (rect && viewportWidth > 0 && viewportHeight > 0) {
344
+ const heightRatio = rect.height / viewportHeight;
345
+ const areaRatio = (rect.width * rect.height) / (viewportWidth * viewportHeight);
346
+ const dominatesViewport = heightRatio >= OVERSIZED_H1_MIN_VIEWPORT_HEIGHT_RATIO
347
+ || areaRatio >= OVERSIZED_H1_MIN_VIEWPORT_AREA_RATIO;
348
+ if (!dominatesViewport) return [];
349
+ viewportDetail = `, ${Math.round(heightRatio * 100)}vh`;
350
+ }
351
+ return [{ id: 'oversized-h1', snippet: `${Math.round(fontSize)}px h1, ${textLen} chars${viewportDetail} "${headingText.slice(0, 60)}"` }];
352
+ }
353
+ return [];
354
+ }
355
+
356
+ function checkElementOversizedH1(el, _style, tag, window) {
357
+ if (tag !== 'h1') return [];
358
+ const fontSize = resolveFontSizePx(el, window);
359
+ const headingText = (el.textContent || '').trim().replace(/\s+/g, ' ');
360
+ return checkOversizedH1({ tag, fontSize, headingText });
361
+ }
362
+
363
+ function checkElementOversizedH1DOM(el) {
364
+ const tag = el.tagName.toLowerCase();
365
+ if (tag !== 'h1') return [];
366
+ const style = getComputedStyle(el);
367
+ const fontSize = parseFloat(style.fontSize) || 0;
368
+ const headingText = (el.textContent || '').trim().replace(/\s+/g, ' ');
369
+ const rect = el.getBoundingClientRect();
370
+ const viewportWidth = (typeof window !== 'undefined' ? window.innerWidth : 0) || 0;
371
+ const viewportHeight = (typeof window !== 'undefined' ? window.innerHeight : 0) || 0;
372
+ return checkOversizedH1({ tag, fontSize, headingText, rect, viewportWidth, viewportHeight });
373
+ }
374
+
375
+ // ─── GPT tell: hairline border + wide diffuse shadow (gated --gpt) ────────────
376
+ const CSS_COLOR_TOKEN_RE = /(?:rgba?|hsla?|oklch|oklab|lab|lch|color)\([^)]*\)|#[0-9a-fA-F]{3,8}\b|\b(?:black|white|transparent|currentcolor)\b/gi;
377
+
378
+ function shadowLayerAlpha(layer) {
379
+ CSS_COLOR_TOKEN_RE.lastIndex = 0;
380
+ const match = CSS_COLOR_TOKEN_RE.exec(layer);
381
+ if (!match) return 1;
382
+ if (match[0].toLowerCase() === 'transparent') return 0;
383
+ const parsed = parseAnyColor(match[0]);
384
+ return parsed ? (parsed.a ?? 1) : 1;
385
+ }
386
+
387
+ function shadowMaxBlurPx(boxShadow, { minAlpha = 0 } = {}) {
388
+ if (!boxShadow || boxShadow === 'none') return 0;
389
+ let maxBlur = 0;
390
+ // Split into layers on commas not inside parentheses (rgba(...) etc.).
391
+ for (const layer of boxShadow.split(/,(?![^()]*\))/)) {
392
+ if (shadowLayerAlpha(layer) < minAlpha) continue;
393
+ // Strip colors and keywords (rgba()/hsl()/hex/named/inset/px), leaving the
394
+ // ordered length tokens: offsetX offsetY blur [spread]. Static jsdom keeps
395
+ // unitless zeros ("0 0 24px"); browsers normalize to px ("0px 0px 24px") —
396
+ // both reduce to the same numbers here.
397
+ const cleaned = layer.replace(CSS_COLOR_TOKEN_RE, ' ').replace(/\b[a-z]+\b/gi, ' ');
398
+ const nums = [...cleaned.matchAll(/-?\d*\.?\d+/g)].map(m => parseFloat(m[0]));
399
+ if (nums.length >= 3) maxBlur = Math.max(maxBlur, nums[2]);
400
+ }
401
+ return maxBlur;
402
+ }
403
+
404
+ function cssColorAlpha(value) {
405
+ if (cssColorIsTransparent(value)) return 0;
406
+ const parsed = parseAnyColor(value);
407
+ return parsed ? (parsed.a ?? 1) : 1;
408
+ }
409
+
410
+ function checkGptThinBorderWideShadow({ borderWidths, borderColors, boxShadow }) {
411
+ const visibleThinBorders = borderWidths
412
+ .map((width, index) => ({ width, alpha: cssColorAlpha(borderColors?.[index] || '') }))
413
+ .filter(({ width, alpha }) => width > 0 && width <= 1.5 && alpha >= 0.28);
414
+ const maxBorder = Math.max(0, ...visibleThinBorders.map(({ width }) => width));
415
+ const blur = shadowMaxBlurPx(boxShadow, { minAlpha: 0.12 });
416
+ if (visibleThinBorders.length >= 2 && blur >= 16) {
417
+ return [{ id: 'gpt-thin-border-wide-shadow', snippet: `${maxBorder}px border + ${Math.round(blur)}px shadow blur` }];
418
+ }
419
+ return [];
420
+ }
421
+
422
+ function borderWidthsFromStyle(style) {
423
+ return [
424
+ parseFloat(style.borderTopWidth) || 0,
425
+ parseFloat(style.borderRightWidth) || 0,
426
+ parseFloat(style.borderBottomWidth) || 0,
427
+ parseFloat(style.borderLeftWidth) || 0,
428
+ ];
429
+ }
430
+
431
+ function borderColorsFromStyle(style) {
432
+ return [
433
+ style.borderTopColor || '',
434
+ style.borderRightColor || '',
435
+ style.borderBottomColor || '',
436
+ style.borderLeftColor || '',
437
+ ];
438
+ }
439
+
440
+ function checkElementGptBorderShadow(_el, style) {
441
+ return checkGptThinBorderWideShadow({ borderWidths: borderWidthsFromStyle(style), borderColors: borderColorsFromStyle(style), boxShadow: style.boxShadow || '' });
442
+ }
443
+
444
+ function checkElementGptBorderShadowDOM(el) {
445
+ const style = getComputedStyle(el);
446
+ return checkGptThinBorderWideShadow({ borderWidths: borderWidthsFromStyle(style), borderColors: borderColorsFromStyle(style), boxShadow: style.boxShadow || '' });
447
+ }
448
+
449
+ export {
450
+ checkTypography,
451
+ isCardLikeDOM,
452
+ checkLayout,
453
+ checkPageTypography,
454
+ isCardLike,
455
+ checkPageLayout,
456
+ isCreamColor,
457
+ checkCreamPalette,
458
+ checkOversizedH1,
459
+ checkElementOversizedH1,
460
+ checkElementOversizedH1DOM,
461
+ shadowMaxBlurPx,
462
+ checkGptThinBorderWideShadow,
463
+ checkElementGptBorderShadow,
464
+ checkElementGptBorderShadowDOM,
465
+ };