[codex] Improve detector false positive handling (#232)

* Improve detector false positive handling

* Register docs integrity test

* Fix clipped overflow decorative skip
This commit is contained in:
Paul Bakaus
2026-06-09 10:56:32 -07:00
committed by GitHub
parent c169b84f20
commit e3e22007a9
70 changed files with 10042 additions and 1221 deletions
+51 -2
View File
@@ -93,16 +93,57 @@ describe('detectUrl — browser-only fixtures', () => {
// 6. 24px heading / 8px all sides — H fail (improvement over old 8px floor)
// 7. 32px hero / 6px V / 16px H — V fail
// 8. 14px <pre> / 2px all sides — both fail
// Pass column has 12 cases (small pills, standard cards, code blocks,
// Pass column has 13 cases (small pills, inline code, standard cards, code blocks,
// buttons, inputs, big text with proportional padding) — none should fire.
assert.equal(cramped.length, 8, `expected 8 cramped-padding findings, got ${cramped.length}`);
});
it('cramped-padding wrapper: skips same-surface wrappers, full-bleed marquees, and inset inner text surfaces', async () => {
const f = await detectUrl(`${baseUrl}/fixtures/antipatterns/flush-against-border.html`);
const cramped = f.filter(r => r.antipattern === 'cramped-padding');
const snippets = cramped.map(r => r.snippet || '').join('\n');
for (const cls of ['flag-frameworks', 'flag-card-borders', 'flag-bg-only', 'flag-outline-only', 'flag-asym-leftflush']) {
assert.match(snippets, new RegExp(`"${cls}"`), `expected ".${cls}" to be flagged`);
}
for (const cls of ['pass-same-bg-child', 'pass-marquee-shell', 'pass-inner-text-surface']) {
assert.doesNotMatch(snippets, new RegExp(`"${cls}"`), `".${cls}" should not be flagged`);
}
});
it('line-length: flag column triggers, pass column adds none', async () => {
const f = await detectUrl(`${baseUrl}/fixtures/antipatterns/quality.html`);
assert.equal(f.filter(r => r.antipattern === 'line-length').length, 1);
});
it('clipped-overflow-container: utility-named popovers still flag when clipped', async () => {
const f = await detectUrl(`${baseUrl}/fixtures/antipatterns/clipped-overflow-container.html`);
const snippets = f
.filter(r => r.antipattern === 'clipped-overflow-container')
.map(r => r.snippet || '')
.join('\n');
assert.match(snippets, /flag-shadow-utility/, 'shadow-lg utility surfaces must not be skipped as decorative');
assert.match(snippets, /flag-overlay-surface/, 'overlay-named content surfaces must not be skipped as decorative');
assert.doesNotMatch(snippets, /pass-contained-overlay/, 'aria-hidden decorative overlays should remain skipped');
});
it('oversized-h1: requires the headline to dominate the viewport, not just be large', async () => {
const f = await detectUrl(`${baseUrl}/fixtures/antipatterns/oversized-h1-browser.html`);
const hits = f.filter(r => r.antipattern === 'oversized-h1');
assert.equal(
hits.length,
1,
`expected exactly one oversized-h1 finding, got ${hits.length}: ${hits.map(r => r.snippet).join('; ')}`,
);
assert.match(hits[0].snippet, /sprawls across the whole/i);
assert.equal(
hits.some(r => /missing design vocabulary/i.test(r.snippet || '')),
false,
'a large two-line homepage-style h1 must not flag unless it dominates the viewport',
);
});
it('typography side-by-side: element-level flag cases get regular overlays', async () => {
const puppeteer = await import('puppeteer');
const browser = await puppeteer.default.launch({
@@ -174,6 +215,7 @@ describe('detectUrl — browser-only fixtures', () => {
'pass-sr-only-legacy',
'pass-sr-only-tiny-hidden',
'pass-sr-only-clipped-wide',
'pass-hidden-slide-overflow',
]) {
assert.ok(!flagged.has(cls), `".${cls}" should NOT be flagged as text-overflow`);
}
@@ -235,6 +277,11 @@ describe('detectUrl — browser-only fixtures', () => {
false,
'light image with dark text should keep enough contrast',
);
assert.equal(
f.some(r => r.antipattern === 'low-contrast' && /Hidden mockup text/i.test(r.snippet || '')),
false,
'aria-hidden decorative mockups should not produce visual contrast findings',
);
assert.equal(
f.some(r => r.antipattern === 'low-contrast' && /Should (?:flag|pass) after pixel sampling/i.test(r.snippet || '')),
false,
@@ -288,7 +335,9 @@ describe('detectUrl — browser-only fixtures', () => {
});
try {
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
// Keep three failing visual-contrast cards in the no-scroll viewport;
// the offscreen cases are covered by the scrollOffscreen test above.
await page.setViewport({ width: 1280, height: 1000 });
await page.goto(`${baseUrl}/fixtures/antipatterns/visual-contrast.html`, { waitUntil: 'load' });
const browserScript = fs.readFileSync(path.join(ROOT, 'cli/engine/detect-antipatterns-browser.js'), 'utf-8');
await page.evaluate(() => { window.__IMPECCABLE_CONFIG__ = { autoScan: false }; });
+36 -2
View File
@@ -407,6 +407,18 @@ describe('detectHtml — repeated-section-kickers', () => {
'Figure Caption Label',
'Normal Case Kicker',
'Intentional Brand Label',
'Garden Suite',
'Sea Loft',
'Cliff Suite',
'/impeccabletypeset',
'/impeccablelayout',
'/impeccablecolorize',
'/impeccablecraft',
'/impeccableaudit',
'/impeccablepolish',
'Mockup Hero Variant One',
'Mockup Hero Variant Two',
'Mockup Hero Variant Three',
];
it('repeated-section-kickers: flags only repeated section scaffolding', async () => {
@@ -471,6 +483,10 @@ describe('detectHtml — cramped-padding (wrapper variant)', () => {
'pass-bg-padded',
'pass-outline-padded',
'pass-image-only',
'pass-margin-inset',
'pass-inner-shell',
'pass-same-bg-child',
'pass-inner-text-surface',
];
it('cramped-padding (wrapper): flags only the should-flag column', async () => {
@@ -541,8 +557,26 @@ describe('detectHtml — clipped-overflow-container', () => {
// (overflow hidden/clip) with an absolutely-positioned descendant clips
// tooltips/menus that need to escape. Real scroll regions (auto/scroll),
// visible overflow, and clipping containers without positioned children pass.
const SHOULD_FLAG = ['flag-overflow-hidden', 'flag-overflow-clip'];
const SHOULD_PASS = ['pass-hidden-no-abs', 'pass-visible-abs', 'pass-scroll-abs'];
const SHOULD_FLAG = [
'flag-overflow-hidden',
'flag-overflow-clip',
'flag-overflow-negative',
'flag-overflow-right',
'flag-shadow-utility',
'flag-overlay-surface',
];
const SHOULD_PASS = [
'pass-hidden-no-abs',
'pass-visible-abs',
'pass-scroll-abs',
'pass-contained-abs',
'pass-button-shine',
'pass-crop-photo',
'pass-contained-overlay',
'pass-carousel-viewport',
'pass-fisheye-list',
'pass-split-container',
];
it('clipped-overflow-container: flags only clipping ancestors with positioned children', async () => {
const f = await detectHtml(path.join(FIXTURES, 'clipped-overflow-container.html'));
+61 -1
View File
@@ -9,6 +9,26 @@
.col { padding: 16px; }
.box { position: relative; width: 200px; height: 120px; margin: 0 0 24px; padding: 16px; border: 1px solid #ddd; }
.pop { position: absolute; top: 100%; left: 0; width: 220px; background: #222; color: #fff; padding: 8px; }
.tip-negative { position: absolute; top: -34px; left: 16px; width: 160px; background: #222; color: #fff; padding: 8px; }
.flyout { position: absolute; top: 28px; left: 100%; width: 180px; background: #222; color: #fff; padding: 8px; }
.contained-badge { position: absolute; top: 10px; right: 10px; width: 36px; height: 24px; background: #e5f0ff; }
.button-shine { position: absolute; inset: 0; background: linear-gradient(90deg, transparent, rgba(255,255,255,.6), transparent); }
.crop-photo { position: absolute; inset: -18px; background: linear-gradient(135deg, #b7d7ff, #486aa3); }
.inside-overlay { position: absolute; left: 12px; right: 12px; bottom: 12px; min-height: 28px; background: rgba(255,255,255,.86); }
.pass-hidden-button { position: relative; overflow: hidden; width: 160px; height: 52px; border: 1px solid #ddd; background: #111; color: #fff; }
.pass-carousel-viewport,
.pass-fisheye-list,
.pass-split-container {
position: relative;
overflow: hidden;
width: 200px;
height: 120px;
margin: 0 0 24px;
border: 1px solid #ddd;
}
.carousel-slide-next { position: absolute; top: 100%; left: 0; width: 100%; height: 100%; padding: 16px; background: #f5f5f5; }
.fisheye-next-item { position: absolute; top: 100%; left: 0; width: 100%; padding: 8px; background: none; border: 0; text-align: left; }
.split-after-panel { position: absolute; top: 0; left: 50%; width: 100%; height: 100%; background: #f5f5f5; }
</style>
</head>
<body>
@@ -23,9 +43,25 @@
<button>Tooltip host</button>
<span class="pop">Tooltip clipped by overflow clip</span>
</div>
<div class="box flag-overflow-negative" style="overflow: hidden">
<button>Help</button>
<span class="tip-negative">Tooltip clipped above the host</span>
</div>
<div class="box flag-overflow-right" style="overflow: hidden">
<button>Actions</button>
<div class="flyout">Flyout menu clipped to the right of the host</div>
</div>
<div class="box flag-shadow-utility" style="overflow: hidden">
<button>More</button>
<div class="pop bg-white shadow-lg">Dropdown with utility classes still gets clipped.</div>
</div>
<div class="box flag-overlay-surface" style="overflow: clip">
<button>Dialog</button>
<div class="pop modal-overlay">Overlay-style dialog content still needs to escape.</div>
</div>
</section>
<!-- PASS column: no clipping, no positioned child, or a real scroll region -->
<!-- PASS column: no clipping, no positioned child, real scroll region, or contained decorative positioning -->
<section class="col pass">
<div class="box pass-hidden-no-abs" style="overflow: hidden">
<p style="position: static">Hidden container with only static content.</p>
@@ -38,6 +74,30 @@
<button>Scroll host</button>
<span class="pop">A genuine scroll region is allowed to contain positioned children.</span>
</div>
<div class="box pass-contained-abs" style="overflow: hidden">
<p>Positioned decoration remains inside the clipping box.</p>
<span class="contained-badge" aria-hidden="true"></span>
</div>
<button type="button" class="pass-hidden-button pass-button-shine">
Save
<span class="button-shine" aria-hidden="true"></span>
</button>
<div class="box pass-crop-photo" style="overflow: hidden">
<span class="crop-photo" aria-hidden="true"></span>
</div>
<div class="box pass-contained-overlay" style="overflow: clip">
<p>Mockup frame with a contained absolute overlay.</p>
<span class="inside-overlay" aria-hidden="true"></span>
</div>
<div class="pass-carousel-viewport" aria-roledescription="carousel">
<div class="carousel-slide-next">A hidden next slide is clipped intentionally by the viewport.</div>
</div>
<div class="pass-fisheye-list">
<button type="button" class="fisheye-next-item">Next command</button>
</div>
<div class="pass-split-container">
<div class="split-after-panel">Before/after comparison panel clipped by the split frame.</div>
</div>
</section>
</div>
</body>
+14
View File
@@ -66,6 +66,16 @@
border-radius: 4px;
font-weight: 500;
}
.pass-inline-code-chip {
display: inline-block;
font-family: ui-monospace, SFMono-Regular, monospace;
font-size: 12px;
line-height: 2;
padding: 0.3em 0.5em;
background: #eef2f7;
color: #0f172a;
border-radius: 4px;
}
/* ── PASS: cards at current standards ── */
.pass-card-min { padding: 8px; background: #f1f5f9; border-radius: 6px; font-size: 16px; line-height: 1.6; }
@@ -157,6 +167,10 @@
<span class="case-label">tag chip: 12px font, 4px / 10px padding</span>
<span class="pass-chip">design system tag chip</span>
</div>
<div class="case">
<span class="case-label">long inline code chip: compact inline code should not be treated like a padded card</span>
<code class="pass-inline-code-chip">npx impeccable skills check</code>
</div>
<h3>Cards at current standards</h3>
<div class="case">
+130
View File
@@ -218,6 +218,83 @@
height: 80px;
background: linear-gradient(135deg, #cbd5e1, #94a3b8);
}
/* ── PASS: parent has zero padding, but child margins create the visible inset ── */
.pass-margin-inset {
padding: 0;
background: white;
border: 1px solid #cbd5e1;
border-radius: 8px;
}
.pass-margin-inset h4 { margin: 16px 18px 4px; font-size: 14px; color: #0f172a; }
.pass-margin-inset p { margin: 0 18px 16px; font-size: 13px; color: #475569; }
/* ── PASS: an inner shell, not the parent, owns the inset spacing ── */
.pass-inner-shell {
padding: 0;
background: white;
border: 1px solid #cbd5e1;
border-radius: 8px;
}
.pass-inner-shell .inner-shell {
margin: 18px;
}
.pass-inner-shell h4 { margin: 0 0 4px; font-size: 14px; color: #0f172a; }
.pass-inner-shell p { margin: 0; font-size: 13px; color: #475569; }
/* ── PASS: same background as parent, so the child draws no visible boundary ── */
.pass-same-bg-parent {
padding: 16px;
background: #eef2f7;
border-radius: 8px;
}
.pass-same-bg-child {
padding: 0;
background: #eef2f7;
border: 0;
border-radius: 8px;
}
.pass-same-bg-child h4 { margin: 0; font-size: 14px; color: #0f172a; }
.pass-same-bg-child p { margin: 4px 0 0; font-size: 13px; color: #475569; }
/* ── PASS: full-bleed marquee/surface, not a text card missing padding ── */
.pass-marquee-shell {
padding: 0;
background: #172033;
color: white;
overflow: hidden;
border: 0;
}
.pass-marquee-track {
display: flex;
gap: 12px;
width: max-content;
}
.pass-marquee-card {
width: 220px;
padding: 14px;
background: #23304a;
border: 1px solid rgba(255, 255, 255, 0.16);
}
.pass-marquee-card p { margin: 0; font-size: 13px; }
/* ── PASS: outer scene owns background; inner text surface owns padding ── */
.pass-inner-text-surface {
padding: 0;
background: #0f172a;
color: white;
overflow: hidden;
border: 0;
}
.pass-inner-text-surface-window {
display: grid;
gap: 6px;
min-height: 92px;
padding: 14px;
background: #111827;
border: 1px solid rgba(255, 255, 255, 0.16);
}
.pass-inner-text-surface-window p { margin: 0; font-size: 13px; }
</style>
</head>
<body>
@@ -346,6 +423,59 @@
<div class="placeholder-img"></div>
</div>
</div>
<h3>Child margins provide the inset</h3>
<div class="case">
<span class="case-label">card: parent padding 0, but text children have margins that create visible inset.</span>
<div class="pass-margin-inset">
<h4>Title inset by child margin</h4>
<p>Body copy is not visually flush even though the parent has zero padding.</p>
</div>
</div>
<h3>Inner shell provides the inset</h3>
<div class="case">
<span class="case-label">card: parent padding 0, but an inner shell is offset from every edge.</span>
<div class="pass-inner-shell">
<div class="inner-shell">
<h4>Title inset by inner shell</h4>
<p>The direct child creates the breathing room.</p>
</div>
</div>
</div>
<h3>Same background as parent</h3>
<div class="case">
<span class="case-label">child padding 0, but it has the same background as the parent, so no child boundary is visible.</span>
<div class="pass-same-bg-parent">
<div class="pass-same-bg-child">
<h4>Same-surface visual shell</h4>
<p>The text belongs to the parent surface, not to a distinct zero-padding card.</p>
</div>
</div>
</div>
<h3>Full-bleed marquee shell</h3>
<div class="case">
<span class="case-label">marquee shell clips animated cards intentionally; the cards, not the shell, own text padding.</span>
<section class="pass-marquee-shell">
<div class="pass-marquee-track">
<article class="pass-marquee-card"><p>Quote card with its own comfortable padding.</p></article>
<article class="pass-marquee-card"><p>Another moving card, also padded inside.</p></article>
</div>
</section>
</div>
<h3>Inner text surface</h3>
<div class="case">
<span class="case-label">outer shell has a scene background; the inner window owns the actual text padding.</span>
<div class="pass-inner-text-surface">
<div class="pass-inner-text-surface-window">
<p>Build step running</p>
<p>Detector output belongs to the mock window.</p>
</div>
</div>
</div>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
+6
View File
@@ -27,6 +27,12 @@
<div class="card pass-tight-shadow" style="border: 1px solid #e5e7eb; box-shadow: 0 1px 4px rgba(0,0,0,0.08)">
Hairline border with a tight, purposeful shadow.
</div>
<div class="card pass-soft-border-wide-shadow" style="border: 1px solid rgba(148, 163, 184, 0.16); box-shadow: 0 18px 48px -16px rgba(15, 23, 42, 0.18)">
A quiet low-contrast hairline with a broad but restrained ambient shadow.
</div>
<div class="card pass-oklch-soft-shadow" style="border: 1px solid oklch(78% 0 0 / 0.16); box-shadow: 0 16px 36px -12px oklch(2% 0.004 95 / 0.18)">
OKLCH shadow hue values should not be mistaken for shadow blur.
</div>
<div class="stripes pass-plain-gradient" style="background: linear-gradient(180deg, #f3f4f6, #ffffff)"></div>
<p class="pass-no-theater">We measured real outcomes for the people who use this every day.</p>
</section>
+27
View File
@@ -0,0 +1,27 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Oversized H1 browser fixture</title>
<style>
body { font-family: Georgia, serif; margin: 0; color: #1a1a1a; background: #fff; }
main { display: grid; gap: 48px; padding: 32px; }
h1 { margin: 0; line-height: 1.04; }
.flag-dominating-h1 {
max-width: 1040px;
font-size: 104px;
}
.pass-contained-large-h1 {
max-width: 1120px;
font-size: 83px;
}
</style>
</head>
<body>
<main>
<h1 class="flag-dominating-h1">A sweeping product headline that sprawls across the whole viewport and keeps going</h1>
<h1 class="pass-contained-large-h1">The missing design vocabulary for agents.</h1>
</main>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
+19
View File
@@ -116,6 +116,17 @@
font-weight: 600;
color: #475569;
}
.mock-terminal-title,
.mock-terminal-meta,
.mock-diff-line {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
color: #475569;
line-height: 1.5;
}
.mock-terminal-title { font-size: 11px; }
.mock-terminal-meta { font-size: 10.8px; }
.mock-diff-line { font-size: 11.84px; }
</style>
</head>
<body>
@@ -220,6 +231,14 @@
<h2 style="font-size: 18px; margin: 0 0 4px;">Second level</h2>
<h3 style="font-size: 16px; margin: 0;">Third level</h3>
</div>
<h3>Small mockup metadata</h3>
<div class="case">
<span class="case-label">terminal/diff metadata at tiny sizes</span>
<span class="mock-terminal-title">/impeccable polish : scanning codebase</span>
<div class="mock-terminal-meta">12 files inspected · 4 visual issues queued</div>
<div class="mock-diff-line">+ Replace generic button label with action-specific copy</div>
</div>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
@@ -69,6 +69,54 @@
.brand-system {
min-height: 120px;
}
.card-grid,
.command-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
margin: 0 0 24px;
}
.suite-card,
.command-card {
display: block;
min-height: 120px;
padding: 16px;
border: 1px solid #d8cec0;
background: #fff;
text-align: left;
}
.taxonomy-label {
display: block;
margin: 0 0 8px;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.11em;
text-transform: uppercase;
color: #5b5046;
}
.suite-card h3,
.command-card h3 {
font-size: 24px;
line-height: 30px;
}
.command-carousel,
.hidden-mockup {
display: grid;
gap: 12px;
margin: 0 0 24px;
}
.command-spread,
.mockup-hero {
min-height: 120px;
padding: 16px;
border: 1px solid #d8cec0;
background: #fff;
}
.command-spread h3,
.mockup-hero h2 {
font-size: 24px;
line-height: 30px;
}
</style>
</head>
<body>
@@ -140,6 +188,75 @@
<h2>"Intentional Brand Label"</h2>
<p>Deliberate brand systems can opt out with an explicit marker.</p>
</section>
<div class="card-grid" aria-label="Room cards">
<article class="suite-card">
<span class="taxonomy-label">Suite</span>
<h3>"Garden Suite"</h3>
<p>Repeated category labels inside cards are structured metadata, not page-section scaffolding.</p>
</article>
<article class="suite-card">
<span class="taxonomy-label">Suite</span>
<h3>"Sea Loft"</h3>
<p>The label is useful because the user is scanning sibling cards.</p>
</article>
<article class="suite-card">
<span class="taxonomy-label">Suite</span>
<h3>"Cliff Suite"</h3>
<p>Repeating it here does not create the generic section-kicker smell.</p>
</article>
</div>
<div class="command-grid" aria-label="Command cards">
<button type="button" class="command-card">
<span class="taxonomy-label">Refine</span>
<h3>"/impeccabletypeset"</h3>
<p>Command category labels can repeat across many action cards.</p>
</button>
<button type="button" class="command-card">
<span class="taxonomy-label">Refine</span>
<h3>"/impeccablelayout"</h3>
<p>The repeated taxonomy helps compare tools inside the same group.</p>
</button>
<button type="button" class="command-card">
<span class="taxonomy-label">Refine</span>
<h3>"/impeccablecolorize"</h3>
<p>This is a card-grid label, not a page-level eyebrow before a section.</p>
</button>
</div>
<div class="command-carousel" aria-label="Command carousel">
<div class="command-spread" data-category="create">
<span class="taxonomy-label">Create</span>
<h3>"/impeccablecraft"</h3>
<p>Carousel slides can repeat category labels as navigational taxonomy.</p>
</div>
<div class="command-spread" data-category="evaluate">
<span class="taxonomy-label">Evaluate</span>
<h3>"/impeccableaudit"</h3>
<p>The slash command heading identifies an item, not a page section.</p>
</div>
<div class="command-spread" data-category="harden">
<span class="taxonomy-label">Harden</span>
<h3>"/impeccablepolish"</h3>
<p>The label is a category chip for a selectable command slide.</p>
</div>
</div>
<div class="hidden-mockup" aria-hidden="true">
<div class="mockup-hero">
<span class="taxonomy-label">Amalfi Coast</span>
<h2>"Mockup Hero Variant One"</h2>
</div>
<div class="mockup-hero">
<span class="taxonomy-label">Amalfi Coast</span>
<h2>"Mockup Hero Variant Two"</h2>
</div>
<div class="mockup-hero">
<span class="taxonomy-label">Amalfi Coast</span>
<h2>"Mockup Hero Variant Three"</h2>
</div>
</div>
</section>
</main>
</body>
+12
View File
@@ -29,6 +29,15 @@
white-space: nowrap;
clip-path: inset(50%);
}
.hidden-slide {
opacity: 0;
pointer-events: none;
}
.hidden-slide .pass-hidden-slide-overflow {
display: block;
width: 120px;
white-space: nowrap;
}
</style>
</head>
<body>
@@ -49,6 +58,9 @@
<span class="pass-sr-only-legacy sr-only sr-only-legacy-clip">A legacy clipped screen-reader-only label with long text that should not be flagged.</span>
<span class="pass-sr-only-tiny-hidden sr-only sr-only-tiny-hidden">A tiny overflow-hidden screen-reader-only label without a clip declaration should not be flagged.</span>
<span class="pass-sr-only-clipped-wide sr-only-clipped-wide">A fully clipped label with a normal-sized box and overflowing text should not be flagged.</span>
<div class="hidden-slide">
<span class="pass-hidden-slide-overflow">An inactive carousel slide can contain long text that overflows while hidden.</span>
</div>
</section>
</div>
</body>
+4
View File
@@ -150,6 +150,10 @@
</svg>
<p style="color: rgb(35, 31, 27);">Dark text over a light SVG underlay should pass.</p>
</article>
<article class="image-card light-image" aria-hidden="true">
<p style="color: rgb(255, 255, 255);">Hidden mockup text on a light image should not be sampled.</p>
</article>
</section>
</main>
</body>