', () => {
- const vue = `
- card
- `;
- const f = detectText(vue, 'Card.vue');
- expect(f.some(r => r.antipattern === 'side-tab')).toBe(true);
- });
-});
-
-describe('detectText -- Svelte', () => {
- test('detects side-tab in `;
- const f = detectText(svelte, 'Sidebar.svelte');
- expect(f.some(r => r.antipattern === 'side-tab')).toBe(true);
- });
-
- test('detects overused font in `;
- const f = detectText(svelte, 'App.svelte');
- expect(f.some(r => r.antipattern === 'overused-font')).toBe(true);
- });
-
- test('detects layout transition in `;
- const f = detectText(svelte, 'Panel.svelte');
- expect(f.some(r => r.antipattern === 'layout-transition')).toBe(true);
- });
-});
-
-// ---------------------------------------------------------------------------
-// Tier 1: detectText on CSS-in-JS files
-// ---------------------------------------------------------------------------
-
-describe('detectText -- CSS-in-JS', () => {
- test('detects side-tab in styled-components', () => {
- const tsx = "const Card = styled.div`\n border-left: 4px solid #3b82f6;\n border-radius: 12px;\n`;";
- const f = detectText(tsx, 'Card.tsx');
- expect(f.some(r => r.antipattern === 'side-tab')).toBe(true);
- });
-
- test('detects bounce in emotion css', () => {
- const tsx = "const style = css`\n animation: bounce 1s infinite;\n`;";
- const f = detectText(tsx, 'anim.ts');
- expect(f.some(r => r.antipattern === 'bounce-easing')).toBe(true);
- });
-
- test('detects overused font in styled-components', () => {
- const tsx = "const Wrapper = styled.main`\n font-family: 'Inter', sans-serif;\n`;";
- const f = detectText(tsx, 'Layout.tsx');
- expect(f.some(r => r.antipattern === 'overused-font')).toBe(true);
- });
-
- test('detects gradient-text in styled-components', () => {
- const tsx = "const Title = styled.h1`\n background: linear-gradient(to right, purple, cyan);\n -webkit-background-clip: text;\n background-clip: text;\n`;";
- const f = detectText(tsx, 'Hero.tsx');
- expect(f.some(r => r.antipattern === 'gradient-text')).toBe(true);
- });
-
- test('detects pure-black-white in styled-components', () => {
- const tsx = "const Dark = styled.section`\n background-color: #000000;\n`;";
- const f = detectText(tsx, 'Dark.tsx');
- expect(f.some(r => r.antipattern === 'pure-black-white')).toBe(true);
- });
-
- test('does not false-positive on clean CSS-in-JS', () => {
- const tsx = "const Card = styled.div`\n border-radius: 12px;\n padding: 24px;\n`;";
- const f = detectText(tsx, 'Card.tsx');
- expect(f.filter(r => r.antipattern === 'side-tab')).toHaveLength(0);
- });
-});
-
-// ---------------------------------------------------------------------------
-// Tier 1: Fixture file integration tests (CLI)
-// ---------------------------------------------------------------------------
-
-describe('CLI -- framework fixtures', () => {
- function run(...args) {
- const result = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf-8', timeout: 15000 });
- return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
- }
-
- test('jsx-should-flag catches anti-patterns', () => {
- const { code, stderr } = run(path.join(FIXTURES, 'jsx-should-flag.jsx'));
- expect(code).toBe(2);
- expect(stderr).toContain('side-tab');
- });
-
- test('jsx-should-pass is clean', () => {
- const { code } = run(path.join(FIXTURES, 'jsx-should-pass.jsx'));
- expect(code).toBe(0);
- });
-
- test('vue-should-flag catches anti-patterns', () => {
- const { code, stderr } = run(path.join(FIXTURES, 'vue-should-flag.vue'));
- expect(code).toBe(2);
- expect(stderr).toContain('side-tab');
- });
-
- test('vue-should-pass is clean', () => {
- const { code } = run(path.join(FIXTURES, 'vue-should-pass.vue'));
- expect(code).toBe(0);
- });
-
- test('svelte-should-flag catches anti-patterns', () => {
- const { code, stderr } = run(path.join(FIXTURES, 'svelte-should-flag.svelte'));
- expect(code).toBe(2);
- expect(stderr).toContain('side-tab');
- });
-
- test('svelte-should-pass is clean', () => {
- const { code } = run(path.join(FIXTURES, 'svelte-should-pass.svelte'));
- expect(code).toBe(0);
- });
-
- test('cssinjs-should-flag catches anti-patterns', () => {
- const { code, stderr } = run(path.join(FIXTURES, 'cssinjs-should-flag.tsx'));
- expect(code).toBe(2);
- expect(stderr).toContain('side-tab');
- });
-
- test('cssinjs-should-pass is clean', () => {
- const { code } = run(path.join(FIXTURES, 'cssinjs-should-pass.tsx'));
- expect(code).toBe(0);
- });
-});
-
-// ---------------------------------------------------------------------------
-// Realistic Next.js project fixtures
-// ---------------------------------------------------------------------------
-
-describe('CLI -- Next.js + Tailwind project', () => {
- const dir = path.join(FIXTURES, 'framework-next-tailwind');
- let stderr;
-
- function run(...args) {
- const result = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf-8', timeout: 15000 });
- return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
- }
-
- test('finds all expected anti-pattern types', () => {
- const result = run(dir);
- stderr = result.stderr;
- expect(result.code).toBe(2);
- for (const ap of ['side-tab', 'gradient-text', 'ai-color-palette', 'overused-font', 'bounce-easing', 'pure-black-white']) {
- expect(stderr).toContain(ap);
- }
- });
-
- test('FeatureCard: side-tab + ai-color-palette + bounce-easing', () => {
- const { stderr } = run(path.join(dir, 'components', 'FeatureCard.tsx'));
- expect(stderr).toContain('side-tab');
- expect(stderr).toContain('border-l-4');
- expect(stderr).toContain('ai-color-palette');
- expect(stderr).toContain('text-purple-600');
- expect(stderr).toContain('bounce-easing');
- expect(stderr).toContain('animate-bounce');
- });
-
- test('PricingCard: pure-black-white + gradient-text + ai-color-palette', () => {
- const { stderr } = run(path.join(dir, 'components', 'PricingCard.tsx'));
- expect(stderr).toContain('pure-black-white');
- expect(stderr).toContain('bg-black');
- expect(stderr).toContain('gradient-text');
- expect(stderr).toContain('bg-clip-text');
- expect(stderr).toContain('ai-color-palette');
- });
-
- test('globals.css: overused Inter font', () => {
- const { stderr } = run(path.join(dir, 'app', 'globals.css'));
- expect(stderr).toContain('overused-font');
- expect(stderr).toContain('Inter');
- });
-
- test('page.tsx: gradient-text + ai-color-palette', () => {
- const { stderr } = run(path.join(dir, 'app', 'page.tsx'));
- expect(stderr).toContain('gradient-text');
- expect(stderr).toContain('ai-color-palette');
- });
-
- test('directory scan shows import context for components', () => {
- const { stderr } = run(dir);
- expect(stderr).toContain('imported by page.tsx');
- });
-
- test('--json produces clean JSON without framework message', () => {
- const { stderr, code } = run('--json', dir);
- expect(code).toBe(2);
- const parsed = JSON.parse(stderr.trim());
- expect(parsed).toBeArray();
- expect(parsed.length).toBeGreaterThanOrEqual(6);
- });
-});
-
-describe('CLI -- Next.js + CSS Modules project', () => {
- function run(...args) {
- const result = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf-8', timeout: 15000 });
- return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
- }
-
- const dir = path.join(FIXTURES, 'framework-next-modules');
-
- test('finds all expected anti-pattern types', () => {
- const { code, stderr } = run(dir);
- expect(code).toBe(2);
- for (const ap of ['side-tab', 'overused-font', 'pure-black-white', 'layout-transition', 'gradient-text']) {
- expect(stderr).toContain(ap);
- }
- });
-
- test('StatsCard.module.css: side-tab + overused-font + layout-transition', () => {
- const { stderr } = run(path.join(dir, 'components', 'StatsCard.module.css'));
- expect(stderr).toContain('side-tab');
- expect(stderr).toContain('border-left: 4px solid #6366f1');
- expect(stderr).toContain('overused-font');
- expect(stderr).toContain('Inter');
- expect(stderr).toContain('layout-transition');
- expect(stderr).toContain('transition: width');
- });
-
- test('Sidebar.module.css: side-tab border accent', () => {
- const { stderr } = run(path.join(dir, 'components', 'Sidebar.module.css'));
- expect(stderr).toContain('side-tab');
- expect(stderr).toContain('border-right: 3px solid');
- });
-
- test('globals.css: overused Roboto + pure-black-white', () => {
- const { stderr } = run(path.join(dir, 'app', 'globals.css'));
- expect(stderr).toContain('overused-font');
- expect(stderr).toContain('Roboto');
- expect(stderr).toContain('pure-black-white');
- expect(stderr).toContain('#000000');
- });
-
- test('page.module.css: gradient-text across lines', () => {
- const { stderr } = run(path.join(dir, 'app', 'page.module.css'));
- expect(stderr).toContain('gradient-text');
- expect(stderr).toContain('background-clip: text');
- });
-
- test('directory scan shows import context for CSS modules', () => {
- const { stderr } = run(dir);
- expect(stderr).toContain('imported by StatsCard.tsx');
- expect(stderr).toContain('imported by Sidebar.tsx');
- expect(stderr).toContain('imported by layout.tsx');
- });
-});
-
-describe('CLI -- Next.js + CSS-in-JS (styled-components) project', () => {
- function run(...args) {
- const result = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf-8', timeout: 15000 });
- return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
- }
-
- const dir = path.join(FIXTURES, 'framework-next-cssinjs');
-
- test('finds all expected anti-pattern types', () => {
- const { code, stderr } = run(dir);
- expect(code).toBe(2);
- for (const ap of ['side-tab', 'gradient-text', 'overused-font', 'bounce-easing', 'pure-black-white', 'layout-transition']) {
- expect(stderr).toContain(ap);
- }
- });
-
- test('FeatureGrid.tsx: side-tab + bounce-easing + layout-transition', () => {
- const { stderr } = run(path.join(dir, 'components', 'FeatureGrid.tsx'));
- expect(stderr).toContain('side-tab');
- expect(stderr).toContain('border-left: 4px solid');
- expect(stderr).toContain('bounce-easing');
- expect(stderr).toContain('animation: bounce');
- expect(stderr).toContain('layout-transition');
- expect(stderr).toContain('transition: width');
- });
-
- test('Hero.tsx: gradient-text + overused Montserrat font', () => {
- const { stderr } = run(path.join(dir, 'components', 'Hero.tsx'));
- expect(stderr).toContain('gradient-text');
- expect(stderr).toContain('background-clip: text');
- expect(stderr).toContain('overused-font');
- expect(stderr).toContain('Montserrat');
- });
-
- test('GlobalStyle.tsx: overused Inter + pure-black-white', () => {
- const { stderr } = run(path.join(dir, 'components', 'GlobalStyle.tsx'));
- expect(stderr).toContain('overused-font');
- expect(stderr).toContain('Inter');
- expect(stderr).toContain('pure-black-white');
- expect(stderr).toContain('#000000');
- });
-
- test('Testimonials.tsx: side-tab + gradient-text in styled blockquote', () => {
- const { stderr } = run(path.join(dir, 'components', 'Testimonials.tsx'));
- expect(stderr).toContain('side-tab');
- expect(stderr).toContain('border-left: 4px solid');
- expect(stderr).toContain('gradient-text');
- });
-
- test('directory scan shows import context for components', () => {
- const { stderr } = run(dir);
- expect(stderr).toContain('imported by index.tsx');
- expect(stderr).toContain('imported by _app.tsx');
- });
-
- test('--json produces clean JSON without framework message', () => {
- const { stderr, code } = run('--json', dir);
- expect(code).toBe(2);
- const parsed = JSON.parse(stderr.trim());
- expect(parsed).toBeArray();
- expect(parsed.length).toBeGreaterThanOrEqual(6);
- // Verify importedBy is present in JSON
- const featureGridFindings = parsed.filter(f => f.file?.includes('FeatureGrid'));
- expect(featureGridFindings.length).toBeGreaterThan(0);
- expect(featureGridFindings[0].importedBy).toContain('index.tsx');
- });
-});
-
-// ---------------------------------------------------------------------------
-// Tier 2: Import graph
-// ---------------------------------------------------------------------------
-
-describe('buildImportGraph', () => {
- const MF = path.join(FIXTURES, 'multifile');
-
- test('resolves ES import from tsx to tsx', () => {
- const graph = buildImportGraph([
- path.join(MF, 'App.tsx'),
- path.join(MF, 'Card.tsx'),
- path.join(MF, 'styles.css'),
- ]);
- const appImports = graph.get(path.join(MF, 'App.tsx'));
- expect(appImports).toBeDefined();
- expect(appImports.has(path.join(MF, 'Card.tsx'))).toBe(true);
- expect(appImports.has(path.join(MF, 'styles.css'))).toBe(true);
- });
-
- test('resolves extensionless imports', () => {
- const graph = buildImportGraph([
- path.join(MF, 'App.tsx'),
- path.join(MF, 'Card.tsx'),
- ]);
- const appImports = graph.get(path.join(MF, 'App.tsx'));
- expect(appImports.has(path.join(MF, 'Card.tsx'))).toBe(true);
- });
-
- test('resolves CSS @import', () => {
- const graph = buildImportGraph([
- path.join(MF, 'theme.scss'),
- path.join(MF, 'variables.scss'),
- ]);
- const themeImports = graph.get(path.join(MF, 'theme.scss'));
- expect(themeImports).toBeDefined();
- expect(themeImports.has(path.join(MF, 'variables.scss'))).toBe(true);
- });
-
- test('ignores bare/node_modules imports', () => {
- const graph = buildImportGraph([
- path.join(MF, 'App.tsx'),
- ]);
- const appImports = graph.get(path.join(MF, 'App.tsx'));
- // Should not contain 'react' or 'styled-components'
- for (const imp of appImports) {
- expect(imp).toContain(MF);
- }
- });
-});
-
-describe('resolveImport', () => {
- const MF = path.join(FIXTURES, 'multifile');
-
- test('resolves relative path with extension', () => {
- const fileSet = new Set([path.join(MF, 'Card.tsx')]);
- const result = resolveImport('./Card.tsx', MF, fileSet);
- expect(result).toBe(path.join(MF, 'Card.tsx'));
- });
-
- test('resolves extensionless import by trying extensions', () => {
- const fileSet = new Set([path.join(MF, 'Card.tsx')]);
- const result = resolveImport('./Card', MF, fileSet);
- expect(result).toBe(path.join(MF, 'Card.tsx'));
- });
-
- test('returns null for bare specifiers', () => {
- const fileSet = new Set([path.join(MF, 'Card.tsx')]);
- expect(resolveImport('react', MF, fileSet)).toBeNull();
- expect(resolveImport('styled-components', MF, fileSet)).toBeNull();
- });
-
- test('returns null for unresolvable imports', () => {
- const fileSet = new Set([path.join(MF, 'Card.tsx')]);
- expect(resolveImport('./Unknown', MF, fileSet)).toBeNull();
- });
-});
-
-// ---------------------------------------------------------------------------
-// Tier 2: Multi-file directory scan
-// ---------------------------------------------------------------------------
-
-describe('CLI -- multi-file scan', () => {
- function run(...args) {
- const result = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf-8', timeout: 15000 });
- return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
- }
-
- test('scanning multifile/ directory finds findings across files', () => {
- const { code, stderr } = run(path.join(FIXTURES, 'multifile'));
- expect(code).toBe(2);
- expect(stderr).toContain('side-tab');
- });
-
- test('--json multi-file scan includes import context', () => {
- const { stderr, code } = run('--json', path.join(FIXTURES, 'multifile'));
- expect(code).toBe(2);
- const parsed = JSON.parse(stderr.trim());
- expect(parsed.length).toBeGreaterThan(0);
- // Findings from Card.tsx should mention being imported by App.tsx
- const cardFindings = parsed.filter(f => f.file?.includes('Card.tsx'));
- expect(cardFindings.length).toBeGreaterThan(0);
- expect(cardFindings.some(f => f.importedBy?.includes('App.tsx'))).toBe(true);
- });
-});
-
-// ---------------------------------------------------------------------------
-// Tier 3: Framework config detection
-// ---------------------------------------------------------------------------
-
-describe('detectFrameworkConfig', () => {
- test('detects next.config.mjs and returns Next.js with default port', () => {
- const result = detectFrameworkConfig(path.join(FIXTURES, 'framework-next-tailwind'));
- expect(result).not.toBeNull();
- expect(result.name).toBe('Next.js');
- expect(result.port).toBe(3000);
- });
-
- test('detects next.config.js (pages router)', () => {
- const result = detectFrameworkConfig(path.join(FIXTURES, 'framework-next-cssinjs'));
- expect(result).not.toBeNull();
- expect(result.name).toBe('Next.js');
- });
-
- test('parses custom port from vite.config.ts', () => {
- const result = detectFrameworkConfig(path.join(FIXTURES, 'framework-vite'));
- expect(result).not.toBeNull();
- expect(result.name).toBe('Vite');
- expect(result.port).toBe(8080);
- });
-
- test('returns null for directory without framework config', () => {
- const result = detectFrameworkConfig(path.join(FIXTURES, 'multifile'));
- expect(result).toBeNull();
- });
-
- test('returns null for nonexistent directory', () => {
- const result = detectFrameworkConfig('/nonexistent/path/12345');
- expect(result).toBeNull();
- });
-});
-
-describe('isPortListening', () => {
- test('returns { listening: false } for unlikely port', async () => {
- const result = await isPortListening(59999);
- expect(result.listening).toBe(false);
- });
-});
-
-describe('FRAMEWORK_CONFIGS', () => {
- test('covers major frameworks', () => {
- const names = FRAMEWORK_CONFIGS.map(c => c.name);
- expect(names).toContain('Next.js');
- expect(names).toContain('Vite');
- expect(names).toContain('SvelteKit');
- expect(names).toContain('Nuxt');
- expect(names).toContain('Astro');
- });
-
- test('each config has required fields', () => {
- for (const cfg of FRAMEWORK_CONFIGS) {
- expect(cfg.name).toBeTypeOf('string');
- expect(cfg.defaultPort).toBeTypeOf('number');
- expect(cfg.files).toBeArray();
- expect(cfg.files.length).toBeGreaterThan(0);
- }
- });
-});
-
-describe('CLI -- dev server suggestion', () => {
- function run(...args) {
- const result = spawnSync('node', [SCRIPT, ...args], { encoding: 'utf-8', timeout: 15000 });
- return { stdout: result.stdout || '', stderr: result.stderr || '', code: result.status };
- }
-
- test('suggests URL scan when Next.js config found', () => {
- const { stderr } = run(path.join(FIXTURES, 'framework-next-tailwind'));
- expect(stderr).toContain('Next.js');
- expect(stderr).toContain('3000');
- });
-
- test('suggests URL scan when Vite config found', () => {
- const { stderr } = run(path.join(FIXTURES, 'framework-vite'));
- expect(stderr).toContain('Vite');
- expect(stderr).toContain('8080');
- });
-});
diff --git a/tests/fixtures/antipatterns/color-should-flag.html b/tests/fixtures/antipatterns/color-should-flag.html
deleted file mode 100644
index af0df8aa8..000000000
--- a/tests/fixtures/antipatterns/color-should-flag.html
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
- Color Anti-Patterns — Should Flag
-
-
-
- Color Anti-Patterns
-
-
- Pure Black & White
-
-
-
-
Pure #000 text on pure #fff background
-
-
-
-
- Gray on Color
-
-
-
Gray text on blue background
-
-
-
Gray text on green background
-
-
-
-
- Low Contrast
-
-
-
Light gray text on white — very low contrast
-
-
-
Dark gray text on near-black — low contrast
-
-
-
-
- Gradient Text
-
-
- Gradient Heading
-
-
-
-
- AI Color Palette
-
-
Purple heading text
-
-
- Tailwind Colors
-
-
-
bg-black — pure black bg
-
-
-
text-gray-400 on bg-blue-500 — gray on color
-
-
text-purple-500 heading
-
-
Purple-to-indigo gradient
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/color-should-pass.html b/tests/fixtures/antipatterns/color-should-pass.html
deleted file mode 100644
index 71afbce66..000000000
--- a/tests/fixtures/antipatterns/color-should-pass.html
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
- Color — Clean Patterns
-
-
-
- Clean Color Patterns
-
-
- Tinted Neutrals
-
-
-
Near-black bg, near-white text — tinted, not pure
-
-
-
Near-white bg, near-black text — good contrast, tinted
-
-
-
-
- Good Contrast
-
-
-
Near-white text on dark blue — high contrast, not pure white
-
-
-
Dark green text on green bg — same hue family
-
-
-
-
- Distinctive Accents
-
-
Red heading — not AI purple
- Amber heading — distinctive
-
-
-
-
diff --git a/tests/fixtures/antipatterns/cssinjs-should-flag.tsx b/tests/fixtures/antipatterns/cssinjs-should-flag.tsx
deleted file mode 100644
index eee8692d6..000000000
--- a/tests/fixtures/antipatterns/cssinjs-should-flag.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-// CSS-in-JS patterns with anti-patterns (styled-components + emotion)
-
-import styled from 'styled-components';
-import { css } from '@emotion/react';
-
-// styled-components: side-tab + border-accent-on-rounded
-export const Card = styled.div`
- border-left: 4px solid #3b82f6;
- border-radius: 12px;
- padding: 24px;
- font-family: 'Inter', sans-serif;
-`;
-
-// styled-components: pure black background + gradient text
-export const Hero = styled.section`
- background-color: #000000;
- padding: 80px 20px;
- text-align: center;
-
- h1 {
- background: linear-gradient(to right, #a855f7, #06b6d4);
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
- }
-`;
-
-// emotion css: bounce animation + layout transition
-export const animatedPanel = css`
- animation: bounce 1s infinite;
- transition: width 0.3s ease;
-`;
-
-// styled with parenthesized component
-export const AccentBox = styled(Box)`
- border-right: 5px solid #8b5cf6;
- border-radius: 8px;
-`;
-
-// Object style pattern
-export const inlineStyles = {
- borderLeft: '4px solid #6366f1',
- borderRadius: '12px',
-};
diff --git a/tests/fixtures/antipatterns/cssinjs-should-pass.tsx b/tests/fixtures/antipatterns/cssinjs-should-pass.tsx
deleted file mode 100644
index 161967ce8..000000000
--- a/tests/fixtures/antipatterns/cssinjs-should-pass.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-// Clean CSS-in-JS patterns -- no anti-patterns
-
-import styled from 'styled-components';
-import { css } from '@emotion/react';
-
-export const Card = styled.div`
- border-radius: 12px;
- padding: 24px;
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- font-family: 'Geist', system-ui, sans-serif;
-`;
-
-export const Hero = styled.section`
- background-color: #0f172a;
- padding: 80px 20px;
-
- h1 {
- color: #f8fafc;
- font-size: 3rem;
- }
-`;
-
-export const smoothPanel = css`
- transition: opacity 0.3s ease, transform 0.2s ease;
-`;
diff --git a/tests/fixtures/antipatterns/external-styles.css b/tests/fixtures/antipatterns/external-styles.css
deleted file mode 100644
index a432becf0..000000000
--- a/tests/fixtures/antipatterns/external-styles.css
+++ /dev/null
@@ -1,33 +0,0 @@
-/* External stylesheet that applies anti-pattern styles */
-
-/* Side-tab via external class */
-.external-side-tab {
- background: white;
- padding: 1rem;
- border-radius: 12px;
- border-left: 4px solid #3b82f6;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
-}
-
-/* Top border accent via external class */
-.external-top-accent {
- background: white;
- padding: 1rem;
- border-radius: 12px;
- border-top: 3px solid #8b5cf6;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
-}
-
-/* Overused font from external stylesheet */
-.external-inter {
- font-family: 'Inter', sans-serif;
-}
-
-/* Clean card — should NOT flag */
-.external-clean {
- background: white;
- padding: 1rem;
- border-radius: 12px;
- border: 1px solid #e5e7eb;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
-}
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/components/FeatureGrid.tsx b/tests/fixtures/antipatterns/framework-next-cssinjs/components/FeatureGrid.tsx
deleted file mode 100644
index 7a2bbe084..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/components/FeatureGrid.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import styled from "styled-components";
-
-const Grid = styled.div`
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 32px;
- max-width: 1200px;
- margin: 0 auto;
- padding: 80px 24px;
-`;
-
-const Card = styled.div`
- border-left: 4px solid #8b5cf6;
- border-radius: 16px;
- background: #1a1a2e;
- padding: 32px;
- box-shadow: 0 0 25px rgba(139, 92, 246, 0.2);
- transition: width 0.3s ease;
-`;
-
-const CardIcon = styled.div`
- font-size: 40px;
- margin-bottom: 16px;
- animation: bounce 2s infinite;
-`;
-
-const CardTitle = styled.h3`
- font-size: 20px;
- font-weight: 700;
- color: #a855f7;
- margin-bottom: 8px;
-`;
-
-const CardDescription = styled.p`
- font-size: 15px;
- color: #6b7280;
- line-height: 1.6;
-`;
-
-const features = [
- { icon: "⚡", title: "Blazing Fast", description: "Optimized for speed with edge-first architecture." },
- { icon: "🔒", title: "Secure by Default", description: "Enterprise-grade security with zero configuration." },
- { icon: "📦", title: "Modular Design", description: "Pick and choose only what you need. No bloat." },
-];
-
-export function FeatureGrid() {
- return (
-
- {features.map((feature) => (
-
- {feature.icon}
- {feature.title}
- {feature.description}
-
- ))}
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/components/GlobalStyle.tsx b/tests/fixtures/antipatterns/framework-next-cssinjs/components/GlobalStyle.tsx
deleted file mode 100644
index 1bbda6c91..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/components/GlobalStyle.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import { createGlobalStyle } from "styled-components";
-
-export const GlobalStyle = createGlobalStyle`
- *,
- *::before,
- *::after {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- }
-
- body {
- font-family: 'Inter', sans-serif;
- background-color: #000000;
- color: #ffffff;
- line-height: 1.6;
- }
-`;
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/components/Hero.tsx b/tests/fixtures/antipatterns/framework-next-cssinjs/components/Hero.tsx
deleted file mode 100644
index a11392630..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/components/Hero.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import styled from "styled-components";
-
-const Section = styled.section`
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 80px 24px;
- text-align: center;
-`;
-
-const Title = styled.h1`
- font-size: 64px;
- font-weight: 800;
- font-family: 'Montserrat', sans-serif;
- background: linear-gradient(135deg, #a855f7, #06b6d4);
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
- margin-bottom: 24px;
- line-height: 1.1;
-`;
-
-const Subtitle = styled.p`
- font-size: 20px;
- color: #6b7280;
- max-width: 600px;
- margin-bottom: 48px;
-`;
-
-const CTAButton = styled.button`
- padding: 16px 48px;
- font-size: 18px;
- font-weight: 600;
- color: white;
- background: linear-gradient(135deg, #8b5cf6, #6366f1);
- border: none;
- border-radius: 12px;
- cursor: pointer;
- box-shadow: 0 0 40px rgba(139, 92, 246, 0.4);
- transition: transform 0.2s ease, box-shadow 0.2s ease;
-
- &:hover {
- transform: translateY(-2px);
- box-shadow: 0 0 60px rgba(139, 92, 246, 0.6);
- }
-`;
-
-export function Hero() {
- return (
-
- Build the Future
-
- The most powerful platform for modern web development.
- Ship faster, scale easier.
-
- Get Started Free
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/components/Testimonials.tsx b/tests/fixtures/antipatterns/framework-next-cssinjs/components/Testimonials.tsx
deleted file mode 100644
index 60a4fd506..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/components/Testimonials.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import styled from "styled-components";
-
-const Section = styled.section`
- padding: 80px 24px;
- max-width: 800px;
- margin: 0 auto;
-`;
-
-const SectionTitle = styled.h2`
- font-size: 36px;
- font-weight: 700;
- text-align: center;
- margin-bottom: 48px;
- background: linear-gradient(to right, #a855f7, #ec4899);
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
-`;
-
-const Quote = styled.blockquote`
- border-left: 4px solid #6366f1;
- border-radius: 12px;
- background: #1a1a2e;
- padding: 24px 32px;
- margin-bottom: 24px;
- font-size: 16px;
- color: #d1d5db;
- font-style: italic;
-`;
-
-const Author = styled.p`
- font-size: 14px;
- color: #6b7280;
- margin-top: 12px;
- font-style: normal;
-`;
-
-export function Testimonials() {
- return (
-
- What People Say
-
- "This platform completely transformed our workflow. Deployment went from
- hours to minutes."
- -- Jane Smith, CTO at TechCorp
-
-
- "The developer experience is unmatched. I can't imagine going back."
- -- Alex Chen, Senior Engineer
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/next.config.js b/tests/fixtures/antipatterns/framework-next-cssinjs/next.config.js
deleted file mode 100644
index 7b09dd40f..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/next.config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {
- compiler: {
- styledComponents: true,
- },
-};
-
-module.exports = nextConfig;
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/pages/_app.tsx b/tests/fixtures/antipatterns/framework-next-cssinjs/pages/_app.tsx
deleted file mode 100644
index 2e3cb4fc1..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/pages/_app.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { AppProps } from "next/app";
-import { ThemeProvider } from "styled-components";
-import { GlobalStyle } from "../components/GlobalStyle";
-
-const theme = {
- colors: {
- primary: "#8b5cf6",
- secondary: "#06b6d4",
- background: "#000000",
- surface: "#1a1a2e",
- text: "#ffffff",
- muted: "#6b7280",
- },
- fonts: {
- body: "'Inter', sans-serif",
- heading: "'Montserrat', sans-serif",
- },
-};
-
-export default function App({ Component, pageProps }: AppProps) {
- return (
-
-
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-cssinjs/pages/index.tsx b/tests/fixtures/antipatterns/framework-next-cssinjs/pages/index.tsx
deleted file mode 100644
index ec6721b4d..000000000
--- a/tests/fixtures/antipatterns/framework-next-cssinjs/pages/index.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Hero } from "../components/Hero";
-import { FeatureGrid } from "../components/FeatureGrid";
-import { Testimonials } from "../components/Testimonials";
-
-export default function Home() {
- return (
- <>
-
-
-
- >
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/app/globals.css b/tests/fixtures/antipatterns/framework-next-modules/app/globals.css
deleted file mode 100644
index f5804bc9d..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/app/globals.css
+++ /dev/null
@@ -1,18 +0,0 @@
-*,
-*::before,
-*::after {
- box-sizing: border-box;
- padding: 0;
- margin: 0;
-}
-
-body {
- font-family: 'Roboto', sans-serif;
- background-color: #000000;
- color: #fff;
-}
-
-a {
- color: inherit;
- text-decoration: none;
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/app/layout.tsx b/tests/fixtures/antipatterns/framework-next-modules/app/layout.tsx
deleted file mode 100644
index fe7b15ec3..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/app/layout.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { Metadata } from "next";
-import "./globals.css";
-
-export const metadata: Metadata = {
- title: "Dashboard",
- description: "Analytics dashboard",
-};
-
-export default function RootLayout({
- children,
-}: {
- children: React.ReactNode;
-}) {
- return (
-
- {children}
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/app/page.module.css b/tests/fixtures/antipatterns/framework-next-modules/app/page.module.css
deleted file mode 100644
index e18b425ba..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/app/page.module.css
+++ /dev/null
@@ -1,25 +0,0 @@
-.container {
- display: flex;
- min-height: 100vh;
-}
-
-.main {
- flex: 1;
- padding: 32px;
-}
-
-.title {
- font-size: 28px;
- font-weight: 700;
- margin-bottom: 32px;
- background: linear-gradient(to right, #a855f7, #06b6d4);
- -webkit-background-clip: text;
- background-clip: text;
- color: transparent;
-}
-
-.grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 24px;
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/app/page.tsx b/tests/fixtures/antipatterns/framework-next-modules/app/page.tsx
deleted file mode 100644
index c7709ebe8..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/app/page.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { StatsCard } from "../components/StatsCard";
-import { Sidebar } from "../components/Sidebar";
-import styles from "./page.module.css";
-
-export default function Dashboard() {
- return (
-
-
-
- Dashboard
-
-
-
-
-
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/components/Sidebar.module.css b/tests/fixtures/antipatterns/framework-next-modules/components/Sidebar.module.css
deleted file mode 100644
index 4a078635b..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/components/Sidebar.module.css
+++ /dev/null
@@ -1,38 +0,0 @@
-.sidebar {
- width: 240px;
- background: #111827;
- border-right: 3px solid #4f46e5;
- padding: 24px 16px;
- display: flex;
- flex-direction: column;
- gap: 32px;
-}
-
-.logo {
- font-size: 20px;
- font-weight: 700;
- color: #fff;
- padding: 0 8px;
-}
-
-.nav {
- display: flex;
- flex-direction: column;
- gap: 4px;
-}
-
-.navItem {
- display: flex;
- align-items: center;
- gap: 12px;
- padding: 10px 12px;
- border-radius: 8px;
- color: #9ca3af;
- font-size: 14px;
- transition: background 0.2s ease;
-}
-
-.navItem:hover {
- background: rgba(255, 255, 255, 0.05);
- color: #fff;
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/components/Sidebar.tsx b/tests/fixtures/antipatterns/framework-next-modules/components/Sidebar.tsx
deleted file mode 100644
index 672ecaf04..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/components/Sidebar.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import styles from "./Sidebar.module.css";
-
-const navItems = [
- { label: "Overview", icon: "📊" },
- { label: "Analytics", icon: "📈" },
- { label: "Customers", icon: "👥" },
- { label: "Settings", icon: "⚙️" },
-];
-
-export function Sidebar() {
- return (
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/components/StatsCard.module.css b/tests/fixtures/antipatterns/framework-next-modules/components/StatsCard.module.css
deleted file mode 100644
index d1d6780c5..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/components/StatsCard.module.css
+++ /dev/null
@@ -1,34 +0,0 @@
-.card {
- border-left: 4px solid #6366f1;
- border-radius: 12px;
- background: #1e1e2e;
- padding: 24px;
- display: flex;
- flex-direction: column;
- gap: 8px;
- box-shadow: 0 0 30px rgba(99, 102, 241, 0.3);
- transition: width 0.3s ease;
-}
-
-.label {
- font-size: 13px;
- color: #9ca3af;
- text-transform: uppercase;
- letter-spacing: 0.08em;
-}
-
-.value {
- font-size: 32px;
- font-weight: 700;
- font-family: 'Inter', sans-serif;
-}
-
-.changeUp {
- color: #22c55e;
- font-size: 14px;
-}
-
-.changeDown {
- color: #ef4444;
- font-size: 14px;
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/components/StatsCard.tsx b/tests/fixtures/antipatterns/framework-next-modules/components/StatsCard.tsx
deleted file mode 100644
index 05126fdfb..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/components/StatsCard.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import styles from "./StatsCard.module.css";
-
-interface StatsCardProps {
- label: string;
- value: string;
- change: string;
-}
-
-export function StatsCard({ label, value, change }: StatsCardProps) {
- const isPositive = change.startsWith("+");
-
- return (
-
- {label}
- {value}
-
- {change}
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-modules/next.config.mjs b/tests/fixtures/antipatterns/framework-next-modules/next.config.mjs
deleted file mode 100644
index 4678774e6..000000000
--- a/tests/fixtures/antipatterns/framework-next-modules/next.config.mjs
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {};
-
-export default nextConfig;
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/app/globals.css b/tests/fixtures/antipatterns/framework-next-tailwind/app/globals.css
deleted file mode 100644
index 382b44fb8..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/app/globals.css
+++ /dev/null
@@ -1,20 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-:root {
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-}
-
-body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
- font-family: 'Inter', sans-serif;
-}
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/app/layout.tsx b/tests/fixtures/antipatterns/framework-next-tailwind/app/layout.tsx
deleted file mode 100644
index 3314e4780..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/app/layout.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { Metadata } from "next";
-import { Inter } from "next/font/google";
-import "./globals.css";
-
-const inter = Inter({ subsets: ["latin"] });
-
-export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
-};
-
-export default function RootLayout({
- children,
-}: Readonly<{
- children: React.ReactNode;
-}>) {
- return (
-
- {children}
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/app/page.tsx b/tests/fixtures/antipatterns/framework-next-tailwind/app/page.tsx
deleted file mode 100644
index cbc5df651..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/app/page.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { FeatureCard } from "../components/FeatureCard";
-import { PricingCard } from "../components/PricingCard";
-
-export default function Home() {
- return (
-
-
-
- Get started by editing
- app/page.tsx
-
-
-
-
-
- Welcome to Our Platform
-
-
- The next generation of web development
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/components/FeatureCard.tsx b/tests/fixtures/antipatterns/framework-next-tailwind/components/FeatureCard.tsx
deleted file mode 100644
index 016c28b3f..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/components/FeatureCard.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-interface FeatureCardProps {
- title: string;
- description: string;
- icon: string;
-}
-
-export function FeatureCard({ title, description, icon }: FeatureCardProps) {
- return (
-
-
{icon}
-
{title}
-
{description}
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/components/PricingCard.tsx b/tests/fixtures/antipatterns/framework-next-tailwind/components/PricingCard.tsx
deleted file mode 100644
index 25ee66bb5..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/components/PricingCard.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-interface PricingCardProps {
- name: string;
- price: string;
- features: string[];
- highlighted?: boolean;
-}
-
-export function PricingCard({ name, price, features, highlighted }: PricingCardProps) {
- return (
-
-
{name}
-
- {price}
- /mo
-
-
- {features.map((feature) => (
-
- ✓
- {feature}
-
- ))}
-
-
- Get Started
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/next.config.mjs b/tests/fixtures/antipatterns/framework-next-tailwind/next.config.mjs
deleted file mode 100644
index 4678774e6..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/next.config.mjs
+++ /dev/null
@@ -1,4 +0,0 @@
-/** @type {import('next').NextConfig} */
-const nextConfig = {};
-
-export default nextConfig;
diff --git a/tests/fixtures/antipatterns/framework-next-tailwind/tailwind.config.ts b/tests/fixtures/antipatterns/framework-next-tailwind/tailwind.config.ts
deleted file mode 100644
index 7e4bd91a0..000000000
--- a/tests/fixtures/antipatterns/framework-next-tailwind/tailwind.config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { Config } from "tailwindcss";
-
-const config: Config = {
- content: [
- "./pages/**/*.{js,ts,jsx,tsx,mdx}",
- "./components/**/*.{js,ts,jsx,tsx,mdx}",
- "./app/**/*.{js,ts,jsx,tsx,mdx}",
- ],
- theme: {
- extend: {
- backgroundImage: {
- "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
- "gradient-conic":
- "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
- },
- },
- },
- plugins: [],
-};
-export default config;
diff --git a/tests/fixtures/antipatterns/framework-vite/main.tsx b/tests/fixtures/antipatterns/framework-vite/main.tsx
deleted file mode 100644
index 26f65145e..000000000
--- a/tests/fixtures/antipatterns/framework-vite/main.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react';
-
-export function App() {
- return (
-
-
Hello Vite
-
- );
-}
diff --git a/tests/fixtures/antipatterns/framework-vite/vite.config.ts b/tests/fixtures/antipatterns/framework-vite/vite.config.ts
deleted file mode 100644
index eaa85a2e0..000000000
--- a/tests/fixtures/antipatterns/framework-vite/vite.config.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-
-export default defineConfig({
- plugins: [react()],
- server: {
- port: 8080,
- },
-});
diff --git a/tests/fixtures/antipatterns/glow-should-flag.html b/tests/fixtures/antipatterns/glow-should-flag.html
deleted file mode 100644
index 6dfa81222..000000000
--- a/tests/fixtures/antipatterns/glow-should-flag.html
+++ /dev/null
@@ -1,88 +0,0 @@
-
-
-
-
-
- Dark Glow Anti-Patterns That Should Be Flagged
-
-
-
- Dark Glow: Should Flag
- Every glow effect on this dark page should be detected.
-
- CSS Colored Glows on Dark Background
-
-
-
Blue glow
-
box-shadow: 0 0 20px rgba(59, 130, 246, 0.4)
-
-
-
Purple glow
-
box-shadow: 0 0 25px rgba(139, 92, 246, 0.35)
-
-
-
Cyan glow
-
box-shadow: 0 0 15px rgba(6, 182, 212, 0.5)
-
-
-
Multi-shadow with colored glow
-
Normal shadow + purple glow combined
-
-
-
- Neon Buttons
-
-
-
Glowing Button
-
Neon glow effect on button.
-
-
-
- Inline Style Glow
-
-
-
Inline pink glow
-
Inline style for regex detection path.
-
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/glow-should-pass.html b/tests/fixtures/antipatterns/glow-should-pass.html
deleted file mode 100644
index f879599c9..000000000
--- a/tests/fixtures/antipatterns/glow-should-pass.html
+++ /dev/null
@@ -1,212 +0,0 @@
-
-
-
-
-
- Dark/Shadow Patterns That Should Pass
-
-
-
- Glow Patterns: Should Pass
- None of these should trigger dark-glow warnings.
-
- Light Page + Colored Shadow
-
-
-
Colored shadow on light background
-
Not dark mode, colored shadow is fine.
-
-
-
- Typical Light Elevated Cards
-
-
-
Small elevation (Tailwind shadow-sm)
-
Standard subtle card shadow.
-
-
-
Medium elevation (Tailwind shadow-md)
-
Standard card shadow.
-
-
-
Large elevation (Tailwind shadow-lg)
-
Prominent card shadow.
-
-
-
Extra large elevation (Tailwind shadow-xl)
-
Modal-style shadow.
-
-
-
Warm deep shadow
-
Large spread neutral shadow.
-
-
-
- Typical Dark Elevated Cards
-
-
-
-
Small dark elevation
-
Subtle shadow on dark card.
-
-
-
Medium dark elevation
-
Standard dark card shadow.
-
-
-
Large dark elevation
-
Prominent shadow on dark card.
-
-
-
Inset shadow
-
Inner shadow, not a glow.
-
-
-
Multi-shadow dark card
-
Layered gray shadows, no color glow.
-
-
-
-
- Dark Element + Normal Shadow
-
-
-
Normal gray shadow on dark card
-
Gray/black shadow is not a glow.
-
-
-
- Dark Element + Focus Ring
-
-
-
Focus ring (spread, no blur)
-
Functional ring, not decorative glow.
-
-
-
- Dark Element + Subtle Shadow
-
-
-
Tiny blur (<5px)
-
Too subtle to be "glowing".
-
-
-
- Dark Element + No Shadow
-
-
-
No shadow at all
-
Just a dark card, no glow.
-
-
-
- Medium Gray + Colored Shadow
-
-
-
Not dark enough background
-
Medium gray doesn't qualify as "dark mode".
-
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/jsx-should-flag.jsx b/tests/fixtures/antipatterns/jsx-should-flag.jsx
deleted file mode 100644
index 525eef74c..000000000
--- a/tests/fixtures/antipatterns/jsx-should-flag.jsx
+++ /dev/null
@@ -1,56 +0,0 @@
-// A typical Next.js/React component with anti-patterns
-
-import React from 'react';
-import { motion } from 'framer-motion';
-
-export function FeatureCard({ title, description, icon }) {
- return (
-
- );
-}
-
-export function HeroSection() {
- return (
-
-
- Welcome to the Future
-
- Build something amazing today
-
- );
-}
-
-export function StatsCard({ value, label }) {
- return (
-
- );
-}
-
-export function AnimatedPanel({ children }) {
- return (
-
- {children}
-
- );
-}
diff --git a/tests/fixtures/antipatterns/jsx-should-pass.jsx b/tests/fixtures/antipatterns/jsx-should-pass.jsx
deleted file mode 100644
index b58305a81..000000000
--- a/tests/fixtures/antipatterns/jsx-should-pass.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-// Clean React component -- no anti-patterns
-
-import React from 'react';
-
-export function FeatureCard({ title, description, icon }) {
- return (
-
- );
-}
-
-export function HeroSection() {
- return (
-
- Welcome
- Build something amazing today
-
- );
-}
-
-export function StatsCard({ value, label }) {
- return (
-
- );
-}
diff --git a/tests/fixtures/antipatterns/layout-should-flag.html b/tests/fixtures/antipatterns/layout-should-flag.html
deleted file mode 100644
index 9054099aa..000000000
--- a/tests/fixtures/antipatterns/layout-should-flag.html
+++ /dev/null
@@ -1,137 +0,0 @@
-
-
-
-
-
- Layout Anti-Patterns — Should Flag
-
-
-
-
- Layout Anti-Patterns
- These should all be flagged by the detector.
-
-
-
-
- Nested Cards (Cardocalypse)
-
-
-
-
Outer Card
-
-
Inner Card
-
Card inside card — the classic cardocalypse.
-
-
-
-
-
-
Level 1
-
-
Level 2
-
-
Level 3 — nesting inception.
-
-
-
-
-
-
-
-
-
-
shadcn-style Outer Card
-
-
Another shadcn Card nested inside — still bad.
-
-
-
-
-
-
- Identical Card Grid
-
-
-
-
-
-
Fast Performance
-
Lightning fast response times with optimized infrastructure.
-
-
-
-
Reliable Uptime
-
99.99% uptime guarantee with automatic failover.
-
-
-
-
Bank-Level Security
-
Enterprise-grade encryption and security controls.
-
-
-
-
Team Collaboration
-
Built for teams with real-time collaboration tools.
-
-
-
-
-
-
- Monotonous Spacing
-
-
-
-
-
Section One
-
Every margin and padding is exactly 16px.
-
-
Even the inner padding is 16px.
-
-
-
-
Section Two
-
No rhythm, no variation, just 16px everywhere.
-
-
-
-
-
-
- Everything Centered
-
-
-
Welcome to Our Platform
-
We provide the best solutions for your business needs.
-
- Get Started
- Learn More
-
-
Trusted by over 10,000 companies worldwide.
-
-
Every. Single. Element. Is. Centered.
-
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/layout-should-pass.html b/tests/fixtures/antipatterns/layout-should-pass.html
deleted file mode 100644
index 02a1c75ec..000000000
--- a/tests/fixtures/antipatterns/layout-should-pass.html
+++ /dev/null
@@ -1,242 +0,0 @@
-
-
-
-
-
- Layout — Clean Patterns (Should NOT Flag)
-
-
-
-
- Layout — Should Pass
- All patterns here are legitimate. None should be flagged.
-
-
-
-
- shadcn Card Sub-Components
-
-
-
-
-
Card Title
-
Card description goes here.
-
-
-
This is CardContent — a sub-section, not a nested card. No shadow, no border-radius of its own.
-
-
- Action
-
-
-
-
-
-
- Card with Form Inputs
-
-
-
-
-
-
- Card with Dropdown
-
-
-
Select Plan
-
Choose your subscription tier.
-
-
-
Free
-
Pro
-
Enterprise
-
-
-
-
-
-
-
-
- Card with Code Block
-
-
-
Installation
-
npm install @acme/sdk
-
-
-
-
-
- Card with Badges
-
-
-
Tags
-
- React
- TypeScript
- Tailwind
-
-
-
-
-
-
- Card with Accordion
-
-
-
- How does billing work?
- +
-
-
- Can I cancel anytime?
- +
-
-
- Do you offer refunds?
- +
-
-
-
-
-
-
- Card with Styled Image
-
-
-
-
Project Preview
-
Image placeholder with rounded corners and shadow.
-
-
-
-
-
- Card with Tabs
-
-
-
- Overview
- Analytics
- Settings
-
-
-
Tab content area — structured content inside a card, not a nested card.
-
-
-
-
-
-
- Pricing Cards (Intentionally Similar)
-
-
-
-
Free
-
$0
-
- 5 projects
- 1 GB storage
- Community support
-
-
-
-
Pro
-
$29
-
- Unlimited projects
- 50 GB storage
- Priority support
-
-
-
-
Enterprise
-
Custom
-
- Everything in Pro
- Unlimited storage
- Dedicated support
-
-
-
-
-
-
-
- Varied Spacing (Good Rhythm)
-
-
-
-
Heading with tight spacing
-
Body text with medium spacing below.
-
-
Nested content with different padding.
-
-
-
-
Another section
-
Different padding than above — intentional rhythm.
-
-
-
-
-
-
- Centered Hero (Legitimate)
-
-
-
Hero Heading
-
A centered hero section is fine — it's the rest of the page that shouldn't all be centered too.
-
Get Started
-
-
-
-
-
Features
-
This content is left-aligned, creating contrast with the centered hero above.
-
- Left-aligned list items
- Natural reading direction
- Intentional layout variety
-
-
-
-
-
-
- Grid with Varied Card Structures
-
-
-
-
Featured Item
-
This card spans the full width — different from the others.
-
-
-
Compact Card
-
Smaller, less padding.
-
-
-
Highlighted
-
Different bg, no shadow — visual variety.
-
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/legitimate-borders.html b/tests/fixtures/antipatterns/legitimate-borders.html
deleted file mode 100644
index 4b89da377..000000000
--- a/tests/fixtures/antipatterns/legitimate-borders.html
+++ /dev/null
@@ -1,113 +0,0 @@
-
-
-
-
-
- Legitimate Border Patterns — Should NOT Flag
-
-
-
- Legitimate Border Patterns
- Every border here is a well-established web pattern. None should be flagged.
-
-
- Blockquotes
-
-
- "Design is not just what it looks like and feels like. Design is how it works."
-
-
-
-
- Sidebar Navigation (Active State)
-
-
-
- Form Validation
-
-
-
Email
-
-
Please enter a valid email address.
-
-
-
-
- Timeline
-
-
-
-
-
Order placed
-
March 15, 2026
-
-
-
-
Shipped
-
March 16, 2026
-
-
-
-
Delivered
-
Expected March 18
-
-
-
-
-
- Code Diff Highlighting
-
-
-- const old = getValue();
-+ const value = getNewValue();
- return value;
-
-
-
- Tab Navigation
-
-
- Overview
- Analytics
- Settings
-
-
-
-
- Data Table
-
-
-
-
- Name
- Revenue
-
-
-
- Acme Corp $1.2M
- Globex $850K
-
-
-
-
-
- Alert Banner
-
-
-
-
diff --git a/tests/fixtures/antipatterns/linked-stylesheet.html b/tests/fixtures/antipatterns/linked-stylesheet.html
deleted file mode 100644
index a679dd432..000000000
--- a/tests/fixtures/antipatterns/linked-stylesheet.html
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
- Anti-Patterns From Linked Stylesheet
-
-
-
-
- Linked Stylesheet Anti-Patterns
-
- These elements get their anti-pattern styles from an external CSS file.
- Regex-only scanning misses these — only computed style analysis catches them.
-
-
- Side-Tab (from external CSS)
-
-
-
External side-tab class
-
border-left + border-radius from linked stylesheet.
-
-
-
- Top Accent + Rounded (from external CSS)
-
-
-
External top accent class
-
border-top + border-radius from linked stylesheet.
-
-
-
- Clean Card (from external CSS)
-
-
-
External clean card
-
Uniform 1px border — should NOT flag.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/motion-should-flag.html b/tests/fixtures/antipatterns/motion-should-flag.html
deleted file mode 100644
index d53825d7c..000000000
--- a/tests/fixtures/antipatterns/motion-should-flag.html
+++ /dev/null
@@ -1,118 +0,0 @@
-
-
-
-
-
- Motion Anti-Patterns That Should Be Flagged
-
-
-
- Motion Anti-Patterns: Should Flag
- Every example on this page should be detected by the motion anti-pattern scanner.
-
- Bounce / Elastic Easing
-
-
-
CSS bounce animation
-
animation: bounce 1s infinite
-
-
-
-
Elastic cubic-bezier
-
cubic-bezier(0.68, -0.55, 0.265, 1.55)
-
-
- Layout Property Transitions
-
-
-
transition: width
-
Animating width causes layout thrash.
-
-
-
-
transition: height
-
Animating height causes layout thrash.
-
-
-
-
transition: padding
-
Animating padding causes layout thrash.
-
-
-
-
transition: margin
-
Animating margin causes layout thrash.
-
-
-
-
transition: max-height
-
Use grid-template-rows instead.
-
-
-
-
transition: width, height
-
Multiple layout properties.
-
-
-
-
transition: width, opacity
-
Layout property mixed with OK property.
-
-
-
-
transition-property: width
-
Longhand form.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/motion-should-pass.html b/tests/fixtures/antipatterns/motion-should-pass.html
deleted file mode 100644
index d8d2568b6..000000000
--- a/tests/fixtures/antipatterns/motion-should-pass.html
+++ /dev/null
@@ -1,109 +0,0 @@
-
-
-
-
-
- Motion Patterns That Should Pass
-
-
-
- Motion Patterns: Should Pass
- None of these should trigger motion anti-pattern warnings.
-
- Good Easing
-
-
-
Smooth fade in
-
animation: fadeIn with exponential ease-out
-
-
-
-
Ease-out quart
-
cubic-bezier(0.25, 1, 0.5, 1) — smooth deceleration
-
-
-
-
Ease-out expo
-
cubic-bezier(0.16, 1, 0.3, 1) — natural feel
-
-
- Good Transitions (transform/opacity/color only)
-
-
-
-
-
transition: opacity
-
Opacity is GPU-accelerated and safe.
-
-
-
-
transition: color, background-color
-
Color transitions are paint-only, no layout.
-
-
-
-
transition: box-shadow
-
Shadow transitions are paint-only.
-
-
-
-
transition: all
-
Too common to flag — might include layout but usually paired with transform/opacity.
-
-
-
-
transition: transform, opacity, box-shadow
-
Multiple safe properties combined.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/multifile/App.tsx b/tests/fixtures/antipatterns/multifile/App.tsx
deleted file mode 100644
index 41bdf672c..000000000
--- a/tests/fixtures/antipatterns/multifile/App.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-// App that imports components -- anti-patterns are in the imported files
-import React from 'react';
-import { Card } from './Card';
-import './styles.css';
-
-export function App() {
- return (
-
- Dashboard
-
-
-
- );
-}
diff --git a/tests/fixtures/antipatterns/multifile/Card.tsx b/tests/fixtures/antipatterns/multifile/Card.tsx
deleted file mode 100644
index 259ac2212..000000000
--- a/tests/fixtures/antipatterns/multifile/Card.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-// Component with anti-patterns
-import React from 'react';
-
-interface CardProps {
- title: string;
- value: string;
-}
-
-export function Card({ title, value }: CardProps) {
- return (
-
- );
-}
diff --git a/tests/fixtures/antipatterns/multifile/styles.css b/tests/fixtures/antipatterns/multifile/styles.css
deleted file mode 100644
index cb62d0760..000000000
--- a/tests/fixtures/antipatterns/multifile/styles.css
+++ /dev/null
@@ -1,9 +0,0 @@
-/* Global styles with anti-patterns */
-body {
- font-family: 'Inter', sans-serif;
- background-color: #000000;
-}
-
-.highlight {
- animation: bounce 0.5s ease-in-out;
-}
diff --git a/tests/fixtures/antipatterns/multifile/theme.scss b/tests/fixtures/antipatterns/multifile/theme.scss
deleted file mode 100644
index 2f7cab2b2..000000000
--- a/tests/fixtures/antipatterns/multifile/theme.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-@import './variables';
-
-.card {
- border-left: 4px solid $primary;
- border-radius: 12px;
-}
diff --git a/tests/fixtures/antipatterns/multifile/variables.scss b/tests/fixtures/antipatterns/multifile/variables.scss
deleted file mode 100644
index c8c5ae8d7..000000000
--- a/tests/fixtures/antipatterns/multifile/variables.scss
+++ /dev/null
@@ -1,3 +0,0 @@
-$primary: #3b82f6;
-$radius: 12px;
-$spacing: 16px;
diff --git a/tests/fixtures/antipatterns/overlay-positioning.html b/tests/fixtures/antipatterns/overlay-positioning.html
deleted file mode 100644
index 674030846..000000000
--- a/tests/fixtures/antipatterns/overlay-positioning.html
+++ /dev/null
@@ -1,525 +0,0 @@
-
-
-
-
-
- Overlay Positioning Edge Cases
-
-
-
-
-Overlay Positioning Edge Cases
-Each scenario places a detectable anti-pattern inside a layout context that can cause overlay misalignment.
-
-
-1. Transform Ancestors
-
-
-
1a. translateY(0) container
-
- This tiny text is inside a transform: translateY(0) container. The overlay should frame this text precisely.
-
-
-
-
-
1b. rotate(0deg) container
-
- Tiny text inside a transform: rotate(0deg) container.
-
-
-
-
-
1c. scale(1) container
-
-
-
-
-
1d. Nested transforms
-
-
-
-
-
1e. Transform + absolute child
-
-
-
-
-2. Closed Details
-
-
-
2a. Closed details with anti-pattern inside
-
- Click to expand (closed by default)
- This tiny text is hidden inside a closed details element. No overlay should appear for it.
- Cramped text also hidden inside closed details.
-
-
-
-
-
2b. Open details with anti-pattern inside
-
- This details is open
- This tiny text IS visible because details is open. Overlay should frame it correctly.
-
-
-
-
-
2c. Nested details (outer open, inner closed)
-
- Outer details (open)
- Some visible content.
-
- Inner details (closed)
- Hidden tiny text inside nested closed details.
-
-
-
-
-
-
2d. Transform inside closed details
-
- Closed with transform inside
-
- Tiny text inside transform inside closed details. Double trouble.
-
-
-
-
-
-3. Sticky and Fixed Positioning
-
-
-
3a. Sticky header inside scrollable container
-
-
-
-
Scroll this container to test sticky behavior.
-
More content to enable scrolling.
-
Even more content here.
-
Cramped text below the sticky header.
-
Additional content.
-
More filler text.
-
-
-
-
-
-
3b. Fixed footer (stays at bottom of viewport)
-
The fixed footer at the bottom of the page has tiny text. Its overlay should stay fixed too.
-
-
-
-
- Tiny text in a fixed footer -- overlay should track this on scroll.
-
-
-
-4. Overflow Hidden
-
-
-
4a. Content clipped by overflow:hidden
-
-
This paragraph is visible.
-
This tiny text is below the overflow cutoff, clipped but still in DOM.
-
This content is also clipped away.
-
-
-
-
-
4b. overflow:hidden + transform ancestor
-
-
-
-
-5. Position Offsets
-
-
-
5a. Relative position with top/left offset
-
- This tiny text is shifted via position:relative + top/left offset.
-
-
-
-
-
-
5b. Absolute child in relative parent
-
-
Normal flow content at top.
-
- Absolutely positioned tiny text at bottom-right.
-
-
-
-
-
-
5c. Negative top offset
-
-
- Tiny text pulled upward via negative top offset.
-
-
-
-
-
-6. Flex and Grid Centering
-
-
-
6a. Flex-centered anti-pattern
-
- Centered tiny text inside a flex container.
-
-
-
-
-
6b. Grid-centered anti-pattern
-
-
Cramped text inside a grid-centered container.
-
-
-
-
-
6c. Flex with transform
-
- AI Gradient Button in Flex + Transform
-
-
-
-
-7. Containing Block Creators
-
-
-
7a. will-change: transform
-
- Tiny text inside a will-change: transform container.
-
-
-
-
-
7b. contain: layout
-
- Tiny text inside a contain: layout container.
-
-
-
-
-
7c. filter: brightness(1)
-
- Tiny text inside a filter container (creates containing block).
-
-
-
-
-
7d. backdrop-filter
-
- Tiny text inside a backdrop-filter container.
-
-
-
-
-8. Margin Collapse and Negative Margins
-
-
-
8a. Negative margin shifting element position
-
-
- Tiny text pulled out of its parent via negative margins.
-
-
-
-
-
-9. Combined Edge Cases
-
-
-
9a. Transform + sticky + overflow
-
-
-
-
-
9b. Closed details + transform + flex
-
- Closed combo scenario
-
- Triple-nested: closed details > flex > transform > tiny text.
-
-
-
-
-
-
9c. Grid + absolute + transform
-
-
- Absolute inside grid inside transform: overlays must track all three contexts.
-
-
-
Absolute cramped box at bottom-right of grid.
-
-
-
-
-
-
9d. Overflow hidden + absolute breakout
-
-
Visible content.
-
- Absolute-positioned tiny text that breaks out below overflow:hidden.
-
-
-
-
-
-
9e. Side-tab card inside transform + relative offset
-
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/partial-component.html b/tests/fixtures/antipatterns/partial-component.html
deleted file mode 100644
index 03324748b..000000000
--- a/tests/fixtures/antipatterns/partial-component.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
Card Title
-
Card description with close font sizes.
-
-
-
diff --git a/tests/fixtures/antipatterns/quality-should-flag.html b/tests/fixtures/antipatterns/quality-should-flag.html
deleted file mode 100644
index 9549429c0..000000000
--- a/tests/fixtures/antipatterns/quality-should-flag.html
+++ /dev/null
@@ -1,340 +0,0 @@
-
-
-
-
-
- General Design Quality Issues That Should Be Flagged
-
-
-
- Design Quality Issues: Should Flag
- Every example on this page has a common design quality problem.
-
-
- 1. Line Length Too Long
-
-
This paragraph has no max-width constraint at all, which means on a wide monitor or ultrawide display, each line of text can stretch to 150 or even 200 characters wide. Research consistently shows that line lengths beyond 75 characters significantly reduce reading speed and comprehension. The eye has to travel too far to find the beginning of the next line, causing readers to lose their place. This is one of the most common and easily fixable typographic issues on the web, yet it persists because developers test on narrow browser windows and never see the problem. A simple max-width of 65ch to 75ch on the paragraph or its container would fix this entirely. But without that constraint, this text will just keep going and going across the full width of whatever viewport or container it finds itself in, making it genuinely unpleasant to read.
-
-
-
- 2. Cramped Padding
-
-
This text is crammed against the border with only 4px padding. It feels claustrophobic and hard to read.
-
-
-
Cramped background padding
-
-
-
Zero padding on a bordered element. The text is literally touching the border.
-
-
-
- 3. Tiny Body Text
-
-
This body text is only 10px. While this might be fine for a disclaimer or legal footnote, it's too small for primary content that users need to actually read. Accessibility guidelines generally recommend a minimum of 16px for body text, with 12px as an absolute floor.
-
-
-
This is 11px body text. Still too small for comfortable reading, especially on high-DPI screens where the physical size is even smaller than the pixel count suggests.
-
-
-
- 4. Tight Line Height
-
-
This paragraph has a line-height of 1.0, which means the lines are touching. Multi-line body text needs breathing room between lines for readability. A line-height of 1.5 to 1.7 is generally recommended for body text.
-
-
-
This paragraph uses line-height: 16px with font-size: 16px, giving an effective ratio of 1.0. Same problem expressed differently.
-
-
-
- 5. Justified Text
-
-
This paragraph uses text-align: justify, which forces each line to stretch to fill the full width. Without hyphenation support, this creates uneven gaps between words known as "rivers of white space" that flow vertically through the text. These rivers make the text harder to read because the inconsistent spacing disrupts the reading rhythm. Left-aligned text with a ragged right edge is almost always more readable on the web.
-
-
-
- 6. Missing Focus Styles
-
- outline: none (try tabbing)
- outline: 0 (try tabbing)
-
-
-
- 7. Small Touch Targets
-
-
-
- 8. Skipped Heading Levels
-
-
This H3 follows the H2 above (OK)
-
But this H5 skips H4 entirely (bad for accessibility and document structure)
-
Screen readers use heading levels to build a document outline. Skipping levels breaks that navigation.
-
-
-
- 9. Z-Index Abuse
-
-
z-index: 99999 (why?)
-
z-index: 2147483647 (the maximum 32-bit integer)
-
-
-
- 10. Fixed Pixel Widths
-
-
This element has width: 800px. On any screen narrower than 800px, it will overflow and cause horizontal scrolling.
-
-
-
- 11. All-Caps Body Text
-
-
This entire paragraph is in uppercase via text-transform. While all-caps works for short labels, headings, or navigation items, longer body text in uppercase is significantly harder to read because we lose the word shape cues that come from ascenders and descenders in mixed-case text.
-
-
-
- 12. !important Overuse
-
-
This element has 5 !important declarations. It's a sign of specificity wars and unmaintainable CSS.
-
-
-
- 13. Inconsistent Border Radius
-
-
-
2px radius
-
8px radius
-
16px radius
-
pill
-
-
-
-
- 14. Wide Letter Spacing
-
-
This body text has letter-spacing: 0.15em applied to it. While subtle tracking adjustments can improve readability for headings or all-caps text, adding significant letter spacing to body text actually makes it harder to read by disrupting natural character groupings.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/quality-should-pass.html b/tests/fixtures/antipatterns/quality-should-pass.html
deleted file mode 100644
index aae801d66..000000000
--- a/tests/fixtures/antipatterns/quality-should-pass.html
+++ /dev/null
@@ -1,183 +0,0 @@
-
-
-
-
-
- Good Design Quality Patterns That Should Pass
-
-
-
- Design Quality: Should Pass
- None of these should trigger quality warnings.
-
- Good Line Length
-
-
This paragraph has a max-width of 65ch, keeping the line length comfortable for reading. The eye can easily track from the end of one line to the beginning of the next.
-
-
- Short Text in Wide Container (OK)
-
-
This is a short sentence in a wide container.
-
Just a few words here.
-
-
- Good Padding
-
-
This container has 16px padding, giving the text room to breathe within its border.
-
-
- Good Text Sizes
-
-
This is 16px body text with 1.6 line-height. Comfortable to read.
-
This is a 12px caption. Small but appropriate for its purpose.
-
-
- Good Touch Targets
-
- Properly Sized Button
-
-
- Proper Heading Hierarchy
-
-
This H3 follows H2 correctly
-
No skipped levels.
-
-
- Short Labels in Caps (OK)
-
- Category Label
-
-
- Custom Focus Style
-
- Custom focus ring
-
-
- Consistent Border Radius
-
-
-
Card A
-
Card B
-
Card C
-
-
-
- Reasonable Z-Index
-
-
- Responsive Width
-
-
max-width: 800px, width: 100%. Adapts to any screen.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/should-flag.html b/tests/fixtures/antipatterns/should-flag.html
deleted file mode 100644
index 714bff2ed..000000000
--- a/tests/fixtures/antipatterns/should-flag.html
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
- Anti-Patterns That Should Be Flagged
-
-
-
-
- Anti-Patterns: Should Flag
- Every example on this page should be detected by the anti-pattern scanner.
-
-
- Tailwind Side-Tab
-
-
-
border-l-4 + rounded-r
-
The classic AI tell.
-
-
-
border-l-2 + rounded-r
-
Thin but still recognizable with rounded corners.
-
-
-
border-r-4 + rounded-l
-
Right side variant.
-
-
-
border-s-3 + rounded-r
-
Logical inline-start.
-
-
-
border-e-8 + rounded-l
-
Logical inline-end, extra thick.
-
-
-
-
- CSS Side-Tab
-
-
-
border-left: 4px solid
-
CSS shorthand.
-
-
-
border-right: 5px solid
-
CSS shorthand right.
-
-
-
border-left-width: 3px
-
CSS longhand.
-
-
-
border-right-width: 6px
-
CSS longhand right.
-
-
-
border-inline-start: 4px solid
-
CSS logical start.
-
-
-
border-inline-end: 3px solid
-
CSS logical end.
-
-
-
border-inline-start-width: 5px
-
CSS logical longhand.
-
-
-
-
- Top/Bottom + Rounded
-
-
-
border-t-4 + rounded-lg
-
Top accent on rounded card.
-
-
-
border-b-4 + rounded-xl
-
Bottom accent on rounded card.
-
-
-
border-t-2 + rounded-md
-
Even thin top border on rounded.
-
-
-
CSS border-top + border-radius
-
Top border from style block.
-
-
-
CSS border-bottom + border-radius
-
Bottom border from style block.
-
-
-
- Dark Mode
-
-
-
Dark card + border-l-4 + rounded
-
Dark background doesn't make the side-tab OK.
-
-
-
Dark card + border-t-2 + rounded
-
Top accent on dark rounded card.
-
-
-
Dark CSS card + side border + radius
-
Inline dark card with side-tab.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/should-pass.html b/tests/fixtures/antipatterns/should-pass.html
deleted file mode 100644
index 5ea9f3baf..000000000
--- a/tests/fixtures/antipatterns/should-pass.html
+++ /dev/null
@@ -1,84 +0,0 @@
-
-
-
-
-
- Clean Patterns — Should NOT Flag
-
-
-
- Clean Patterns: Should Pass
- None of these should be flagged. They're all intentional, well-established web patterns.
-
-
- Clean Cards
-
-
-
Full border card
-
Subtle 1px border all around. Clean and intentional.
-
-
-
-
Top border, no radius
-
Top accent without rounded corners is a clean section divider.
-
-
-
-
Bottom border, no radius
-
Bottom accent without rounded corners is also clean.
-
-
-
-
New
-
No border at all
-
Just a shadow. Simple and effective.
-
-
-
-
- Below Threshold (Thin, No Radius)
-
-
-
border-left: 1px solid, no radius — not flagged
-
-
-
border-right: 2px solid, no radius — not flagged
-
-
-
1px inline border-left, no radius — not flagged
-
-
-
- Dark Mode (Clean)
-
-
-
Dark card, full border
-
Uniform 1px border all around. Clean.
-
-
-
Dark section, bottom border, no radius
-
Bottom accent without radius is fine.
-
-
-
Dark card, no border
-
Shadow only. Clean.
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/svelte-should-flag.svelte b/tests/fixtures/antipatterns/svelte-should-flag.svelte
deleted file mode 100644
index ade40a091..000000000
--- a/tests/fixtures/antipatterns/svelte-should-flag.svelte
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/svelte-should-pass.svelte b/tests/fixtures/antipatterns/svelte-should-pass.svelte
deleted file mode 100644
index d009f97aa..000000000
--- a/tests/fixtures/antipatterns/svelte-should-pass.svelte
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/typography-should-flag.html b/tests/fixtures/antipatterns/typography-should-flag.html
deleted file mode 100644
index e51ece009..000000000
--- a/tests/fixtures/antipatterns/typography-should-flag.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
-
- Typography Anti-Patterns — Should Flag
-
-
-
-
- Typography Anti-Patterns
- This page triggers three typography detections:
-
- 1. Overused Font
- Inter is loaded from Google Fonts and set as the only font-family. It's the most common AI default.
-
- 2. Single Font
- There's no second font for headings or display text. Everything uses Inter — no typographic variety.
-
- 3. Flat Type Hierarchy
- The font sizes are 13px, 14px, 15px, 16px, 18px — all crammed into a 5px range. No visual contrast between heading and body.
- This caption is barely distinguishable from body text.
-
- A Subheading
- Can you tell this is a subheading? Exactly.
-
-
-
diff --git a/tests/fixtures/antipatterns/typography-should-pass.html b/tests/fixtures/antipatterns/typography-should-pass.html
deleted file mode 100644
index f4a91c47f..000000000
--- a/tests/fixtures/antipatterns/typography-should-pass.html
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
- Typography — Clean Patterns
-
-
-
-
- Good Typography
- This page uses distinctive fonts, proper pairing, and strong hierarchy.
-
- Two Font Families
- Fraunces (serif) for headings, Instrument Sans for body. Clear contrast in both structure and personality.
-
- Strong Size Hierarchy
- Sizes range from 12px to 48px — a 4:1 ratio with clear visual steps.
- Caption text is clearly distinct from body.
-
-
-
diff --git a/tests/fixtures/antipatterns/vue-should-flag.vue b/tests/fixtures/antipatterns/vue-should-flag.vue
deleted file mode 100644
index 69b20ec34..000000000
--- a/tests/fixtures/antipatterns/vue-should-flag.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
{{ title }}
-
{{ description }}
-
- Get Started
-
-
-
-
-
-
-
diff --git a/tests/fixtures/antipatterns/vue-should-pass.vue b/tests/fixtures/antipatterns/vue-should-pass.vue
deleted file mode 100644
index d36be3176..000000000
--- a/tests/fixtures/antipatterns/vue-should-pass.vue
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
{{ title }}
-
{{ description }}
-
- Get Started
-
-
-
-
-
-
-