mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-16 08:06:24 +03:00
Four new layout detections: - nested-cards: jsdom DOM walk finds card-like elements (shadow + rounded + bg) nested inside other card-like elements. Excludes dropdowns (absolute/fixed), form inputs, code blocks, badges (<20 chars), and known component classes. - identical-card-grid: detects grid/flex parents with 3+ children sharing the same structural fingerprint (icon + heading + paragraph template pattern). - monotonous-spacing: regex on raw HTML collects padding/margin/gap values (px, rem, Tailwind classes), rounds to nearest 4px, flags when >60% use the same value with <=3 distinct values. - everything-centered: regex counts text-align:center and Tailwind text-center on text elements, flags when >70% of 5+ text elements are centered. Also narrowed pure-black-white to only flag #000 as background color — text-black, text-white, bg-white, and #fff are no longer flagged (too common, per user feedback). Extensive should-pass fixture covers: shadcn card sub-components, cards with form inputs/dropdowns/code blocks/badges/accordions/tabs/images, pricing cards, varied spacing, mixed centered/left-aligned layouts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
243 lines
12 KiB
HTML
243 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Layout — Clean Patterns (Should NOT Flag)</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<style>
|
|
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; color: #111827; }
|
|
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
h2 { font-size: 1.25rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
|
|
p.intro { color: #6b7280; margin-bottom: 2rem; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Layout — Should Pass</h1>
|
|
<p class="intro">All patterns here are legitimate. None should be flagged.</p>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- shadcn-style Card sub-components (NOT nested cards) -->
|
|
<!-- ============================================================ -->
|
|
<h2>shadcn Card Sub-Components</h2>
|
|
|
|
<!-- Card with CardHeader / CardContent / CardFooter structure -->
|
|
<div class="rounded-lg border bg-white shadow-sm max-w-md mb-4">
|
|
<div class="flex flex-col space-y-1.5 p-6">
|
|
<h3 class="text-lg font-semibold">Card Title</h3>
|
|
<p class="text-sm text-gray-500">Card description goes here.</p>
|
|
</div>
|
|
<div class="p-6 pt-0">
|
|
<p class="text-sm text-gray-600">This is CardContent — a sub-section, not a nested card. No shadow, no border-radius of its own.</p>
|
|
</div>
|
|
<div class="flex items-center p-6 pt-0">
|
|
<button class="px-4 py-2 bg-gray-900 text-white text-sm rounded-md">Action</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with form inputs (inputs have shadow/rounded) -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Form Inputs</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
|
|
<h3 class="font-semibold mb-4">Settings</h3>
|
|
<div class="space-y-3">
|
|
<input type="text" placeholder="Name" class="w-full px-3 py-2 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-2">
|
|
<input type="email" placeholder="Email" class="w-full px-3 py-2 border rounded-md shadow-sm text-sm focus:outline-none focus:ring-2">
|
|
<select class="w-full px-3 py-2 border rounded-md shadow-sm text-sm bg-white">
|
|
<option>Select option</option>
|
|
</select>
|
|
<textarea placeholder="Bio" class="w-full px-3 py-2 border rounded-md shadow-sm text-sm h-20 resize-none"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with dropdown/popover (has shadow + rounded) -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Dropdown</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border relative">
|
|
<h3 class="font-semibold mb-2">Select Plan</h3>
|
|
<p class="text-sm text-gray-600 mb-3">Choose your subscription tier.</p>
|
|
<!-- Simulated dropdown menu -->
|
|
<div class="absolute top-full left-6 mt-1 w-48 bg-white rounded-md shadow-lg border py-1 z-10">
|
|
<div class="px-3 py-2 text-sm hover:bg-gray-50 cursor-pointer">Free</div>
|
|
<div class="px-3 py-2 text-sm hover:bg-gray-50 cursor-pointer">Pro</div>
|
|
<div class="px-3 py-2 text-sm hover:bg-gray-50 cursor-pointer">Enterprise</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="height: 80px;"></div> <!-- spacer for dropdown -->
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with code block inside -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Code Block</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
|
|
<h3 class="font-semibold mb-3">Installation</h3>
|
|
<pre class="bg-gray-900 text-gray-100 rounded-md p-4 text-sm overflow-x-auto"><code>npm install @acme/sdk</code></pre>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with badge/chip inside -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Badges</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
|
|
<h3 class="font-semibold mb-3">Tags</h3>
|
|
<div class="flex flex-wrap gap-2">
|
|
<span class="px-2 py-1 bg-blue-100 text-blue-700 rounded-full text-xs font-medium">React</span>
|
|
<span class="px-2 py-1 bg-green-100 text-green-700 rounded-full text-xs font-medium">TypeScript</span>
|
|
<span class="px-2 py-1 bg-purple-100 text-purple-700 rounded-full text-xs font-medium">Tailwind</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with accordion/collapsible inside -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Accordion</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm max-w-md mb-4 border">
|
|
<div class="p-4 border-b cursor-pointer flex justify-between items-center">
|
|
<span class="font-medium text-sm">How does billing work?</span>
|
|
<span class="text-gray-400">+</span>
|
|
</div>
|
|
<div class="p-4 border-b cursor-pointer flex justify-between items-center">
|
|
<span class="font-medium text-sm">Can I cancel anytime?</span>
|
|
<span class="text-gray-400">+</span>
|
|
</div>
|
|
<div class="p-4 cursor-pointer flex justify-between items-center">
|
|
<span class="font-medium text-sm">Do you offer refunds?</span>
|
|
<span class="text-gray-400">+</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with image (rounded + shadow on image) -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Styled Image</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm p-6 max-w-md mb-4 border">
|
|
<div class="w-full h-40 bg-gradient-to-br from-blue-400 to-purple-500 rounded-lg shadow-inner mb-4"></div>
|
|
<h3 class="font-semibold">Project Preview</h3>
|
|
<p class="text-sm text-gray-600 mt-1">Image placeholder with rounded corners and shadow.</p>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Card with tab panels inside -->
|
|
<!-- ============================================================ -->
|
|
<h2>Card with Tabs</h2>
|
|
|
|
<div class="bg-white rounded-lg shadow-sm max-w-md mb-4 border">
|
|
<div class="flex border-b">
|
|
<button class="px-4 py-3 text-sm font-medium text-blue-600 border-b-2 border-blue-600">Overview</button>
|
|
<button class="px-4 py-3 text-sm text-gray-500">Analytics</button>
|
|
<button class="px-4 py-3 text-sm text-gray-500">Settings</button>
|
|
</div>
|
|
<div class="p-6">
|
|
<p class="text-sm text-gray-600">Tab content area — structured content inside a card, not a nested card.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Pricing cards (similar but intentionally comparative) -->
|
|
<!-- ============================================================ -->
|
|
<h2>Pricing Cards (Intentionally Similar)</h2>
|
|
|
|
<div class="grid grid-cols-3 gap-4 max-w-3xl mb-4">
|
|
<div class="bg-white rounded-lg shadow-sm p-6 border">
|
|
<h3 class="font-semibold mb-1">Free</h3>
|
|
<div class="text-2xl font-bold mb-4">$0</div>
|
|
<ul class="space-y-2 text-sm text-gray-600">
|
|
<li>5 projects</li>
|
|
<li>1 GB storage</li>
|
|
<li>Community support</li>
|
|
</ul>
|
|
</div>
|
|
<div class="bg-white rounded-lg shadow-sm p-6 border-2 border-blue-500">
|
|
<h3 class="font-semibold mb-1">Pro</h3>
|
|
<div class="text-2xl font-bold mb-4">$29</div>
|
|
<ul class="space-y-2 text-sm text-gray-600">
|
|
<li>Unlimited projects</li>
|
|
<li>50 GB storage</li>
|
|
<li>Priority support</li>
|
|
</ul>
|
|
</div>
|
|
<div class="bg-white rounded-lg shadow-sm p-6 border">
|
|
<h3 class="font-semibold mb-1">Enterprise</h3>
|
|
<div class="text-2xl font-bold mb-4">Custom</div>
|
|
<ul class="space-y-2 text-sm text-gray-600">
|
|
<li>Everything in Pro</li>
|
|
<li>Unlimited storage</li>
|
|
<li>Dedicated support</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Good spacing variety -->
|
|
<!-- ============================================================ -->
|
|
<h2>Varied Spacing (Good Rhythm)</h2>
|
|
|
|
<div style="max-width: 28rem; margin-bottom: 1rem;">
|
|
<div style="background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); margin-bottom: 2rem;">
|
|
<h3 style="font-weight: 600; margin-bottom: 0.5rem; font-size: 1.25rem;">Heading with tight spacing</h3>
|
|
<p style="font-size: 0.875rem; color: #6b7280; margin-bottom: 1.5rem;">Body text with medium spacing below.</p>
|
|
<div style="padding: 1rem; background: #f3f4f6; border-radius: 6px;">
|
|
<p style="font-size: 0.8125rem; color: #6b7280;">Nested content with different padding.</p>
|
|
</div>
|
|
</div>
|
|
<div style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
|
|
<h3 style="font-weight: 600; margin-bottom: 0.75rem;">Another section</h3>
|
|
<p style="font-size: 0.875rem; color: #6b7280;">Different padding than above — intentional rhythm.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Centered hero section (legitimately centered) -->
|
|
<!-- ============================================================ -->
|
|
<h2>Centered Hero (Legitimate)</h2>
|
|
|
|
<div style="text-align: center; max-width: 32rem; margin: 0 auto 1rem; padding: 2rem 0;">
|
|
<h3 style="font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem;">Hero Heading</h3>
|
|
<p style="color: #6b7280; margin-bottom: 1.5rem;">A centered hero section is fine — it's the rest of the page that shouldn't all be centered too.</p>
|
|
<button style="padding: 0.75rem 1.5rem; background: #111827; color: white; border: none; border-radius: 6px; font-weight: 500;">Get Started</button>
|
|
</div>
|
|
|
|
<!-- Left-aligned content following the hero -->
|
|
<div style="max-width: 32rem; margin-bottom: 1rem;">
|
|
<h3 style="font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem;">Features</h3>
|
|
<p style="color: #6b7280; margin-bottom: 1rem;">This content is left-aligned, creating contrast with the centered hero above.</p>
|
|
<ul style="color: #4b5563; font-size: 0.875rem; padding-left: 1.25rem; list-style: disc;">
|
|
<li style="margin-bottom: 0.5rem;">Left-aligned list items</li>
|
|
<li style="margin-bottom: 0.5rem;">Natural reading direction</li>
|
|
<li>Intentional layout variety</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<!-- ============================================================ -->
|
|
<!-- Non-card grid (varied structure) -->
|
|
<!-- ============================================================ -->
|
|
<h2>Grid with Varied Card Structures</h2>
|
|
|
|
<div class="grid grid-cols-2 gap-4 max-w-2xl mb-4">
|
|
<div class="bg-white rounded-lg shadow-sm p-6 border col-span-2">
|
|
<h3 class="font-semibold text-lg mb-2">Featured Item</h3>
|
|
<p class="text-sm text-gray-600">This card spans the full width — different from the others.</p>
|
|
</div>
|
|
<div class="bg-white rounded-lg shadow-sm p-4 border">
|
|
<h3 class="font-semibold text-sm">Compact Card</h3>
|
|
<p class="text-xs text-gray-500 mt-1">Smaller, less padding.</p>
|
|
</div>
|
|
<div class="bg-blue-50 rounded-lg p-4 border border-blue-200">
|
|
<h3 class="font-semibold text-sm text-blue-900">Highlighted</h3>
|
|
<p class="text-xs text-blue-700 mt-1">Different bg, no shadow — visual variety.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/js/detect-antipatterns-browser.js"></script>
|
|
</body>
|
|
</html>
|