Files
pbakaus_impeccable/tests/fixtures/antipatterns/icon-tile-stack.html
T
Paul BakausandClaude Opus 4.6 06ef4f3c14 Handle emoji-only text in contrast and icon-tile detection
Emojis render as multicolor glyphs regardless of CSS \`color\`, so the
text color is irrelevant for contrast calculations. The detector was
flagging emoji icons as low-contrast whenever the surrounding bg/text
colors were close (e.g. an emoji card with text-color set to match
the bg). Adds an isEmojiOnlyText() helper that returns true when the
direct text consists entirely of emoji characters (and zero-width
joiners, variation selectors, skin-tone modifiers, regional
indicators), and skips both gray-on-color and low-contrast checks
when it's true.

Same insight fixes a missed icon-tile-stack detection: many AI-
generated cards use \`<div class="card-icon"></div>\` where the
tile contains the emoji directly as text, not an <svg>/<i> child.
The detector now also recognizes these "inline emoji icon" tiles.

Both fixes are TDD'd: new test cases in color.html (two emoji cards
with matching text/bg colors) and icon-tile-stack.html (the inline
emoji tile pattern). The test suite went from green → red → green
across both rules.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 01:38:11 -07:00

248 lines
9.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Icon-Tile-Stack — Should Flag vs Should Pass</title>
<style>
/* ─── Layout convention: two-column fixture ─────────────────────────
Left column = patterns the rule SHOULD flag.
Right column = legitimate patterns the rule should NOT flag.
Each test case is annotated with a unique <h3> text so the test
can match snippets back to expectations.
──────────────────────────────────────────────────────────────── */
body { font-family: system-ui, sans-serif; margin: 0; padding: 24px; }
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; max-width: 960px; margin: 0 auto; }
.col h2 { font-size: 14px; text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 16px; color: #475569; }
.case { margin-bottom: 24px; padding: 16px; border: 1px solid #e2e8f0; border-radius: 12px; background: #fff; }
.case h3 { font-size: 18px; margin: 0 0 4px; color: #0f172a; }
.case p { font-size: 14px; margin: 0; color: #64748b; }
/* ── FLAG cases ── */
/* 1: pastel-gradient icon-tile (the canonical massive-icons.html shape) */
.icon-pastel {
width: 80px; height: 80px;
border-radius: 20px;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
/* 2: solid-color icon-tile, white svg inside (no gradient) */
.icon-solid {
width: 64px; height: 64px;
border-radius: 16px;
background: #3b82f6;
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
/* 3: white-bg outlined icon-tile (no chromatic background at all) */
.icon-white {
width: 72px; height: 72px;
border-radius: 18px;
background: #ffffff;
border: 1px solid #e2e8f0;
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
/* 4: larger square (still inside the 32128 range) */
.icon-large {
width: 96px; height: 96px;
border-radius: 24px;
background: #f1f5f9;
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
/* 5: inline emoji icon — tile contains an emoji/symbol character directly,
not an <svg> or <i> child. This is the "card-icon" pattern from many
generated demos. */
.icon-emoji {
width: 48px; height: 48px;
border-radius: 12px;
background: linear-gradient(135deg, #8b5cf6, #a855f7);
display: flex; align-items: center; justify-content: center;
font-size: 24px;
margin-bottom: 16px;
}
/* ── PASS cases ── */
/* a: round avatar above name (border-radius: 50% — circle, not rounded square) */
.avatar-round {
width: 64px; height: 64px;
border-radius: 50%;
background: #f59e0b;
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
/* b: wide thumbnail above article title (16:9, much wider than tall) */
.thumb-wide {
width: 200px; height: 112px;
border-radius: 8px;
background: #cbd5e1;
margin-bottom: 16px;
}
/* c: inline icon next to heading (icon and h3 on the same row, NOT stacked) */
.inline-row { display: flex; align-items: center; gap: 12px; }
.inline-row .icon-inline {
width: 80px; height: 80px;
border-radius: 20px;
background: #ddd6fe;
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
/* d: nothing above the heading at all */
.no-icon h3 { margin-top: 0; }
/* e: icon-tile that's too small (under 32px) — out of size range */
.icon-tiny {
width: 24px; height: 24px;
border-radius: 6px;
background: #93c5fd;
display: flex; align-items: center; justify-content: center;
margin-bottom: 16px;
}
/* f: oversized image card / hero image (over 128px) — out of size range */
.img-huge {
width: 200px; height: 200px;
border-radius: 16px;
background: #cbd5e1;
margin-bottom: 16px;
}
</style>
</head>
<body>
<div class="grid">
<!-- ════════════════════════════════════════════════════════════════
LEFT COLUMN: should flag
═══════════════════════════════════════════════════════════════ -->
<div class="col" data-col="flag">
<h2>Should flag</h2>
<div class="case">
<div class="icon-pastel">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" stroke-width="1.5">
<polyline points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
</svg>
</div>
<h3>Lightning Fast</h3>
<p>Built for speed and performance from the ground up.</p>
</div>
<div class="case">
<div class="icon-solid">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="1.5">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
</svg>
</div>
<h3>Secure Storage</h3>
<p>Enterprise-grade security for your peace of mind.</p>
</div>
<div class="case">
<div class="icon-white">
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#0f172a" stroke-width="1.5">
<circle cx="12" cy="12" r="10"/>
<path d="M12 6v6l4 2"/>
</svg>
</div>
<h3>Easy Setup</h3>
<p>Get started in minutes with zero configuration.</p>
</div>
<div class="case">
<div class="icon-large">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#0f172a" stroke-width="1.5">
<line x1="18" y1="20" x2="18" y2="10"/>
<line x1="12" y1="20" x2="12" y2="4"/>
<line x1="6" y1="20" x2="6" y2="14"/>
</svg>
</div>
<h3>Powerful Analytics</h3>
<p>Insights that drive smarter business decisions.</p>
</div>
<div class="case">
<div class="icon-emoji">&#9889;</div>
<h3>Emoji Inline Icon</h3>
<p>The tile contains an emoji directly, no SVG child.</p>
</div>
</div>
<!-- ════════════════════════════════════════════════════════════════
RIGHT COLUMN: should pass
═══════════════════════════════════════════════════════════════ -->
<div class="col" data-col="pass">
<h2>Should pass</h2>
<!-- a: round avatar (circle, not rounded square) -->
<div class="case">
<div class="avatar-round">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2">
<circle cx="12" cy="8" r="4"/>
<path d="M4 22v-2a8 8 0 0116 0v2"/>
</svg>
</div>
<h3>Sarah Chen</h3>
<p>Product designer based in Berlin.</p>
</div>
<!-- b: wide 16:9 thumbnail (aspect ratio out of range) -->
<div class="case">
<div class="thumb-wide"></div>
<h3>Article Headline</h3>
<p>A summary of an interesting article.</p>
</div>
<!-- c: side-by-side icon and heading (not stacked vertically) -->
<div class="case">
<div class="inline-row">
<div class="icon-inline">
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#7c3aed" stroke-width="1.5">
<circle cx="12" cy="12" r="10"/>
</svg>
</div>
<div>
<h3>Inline Side By Side</h3>
<p>Icon sits next to the heading, not above it.</p>
</div>
</div>
</div>
<!-- d: no icon above the heading at all -->
<div class="case no-icon">
<h3>Plain Heading No Icon</h3>
<p>Just a heading with body text, nothing above.</p>
</div>
<!-- e: icon-tile too small (24px — under 32px floor) -->
<div class="case">
<div class="icon-tiny">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#1e40af" stroke-width="2">
<circle cx="12" cy="12" r="10"/>
</svg>
</div>
<h3>Tiny Icon Above Me</h3>
<p>The icon container is smaller than 32px.</p>
</div>
<!-- f: huge image / hero (200px — over 128px ceiling) -->
<div class="case">
<div class="img-huge"></div>
<h3>Huge Hero Image</h3>
<p>The container is larger than the size cap.</p>
</div>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>