Remove detection engine files, now in @impeccable/detect package

Remove all detection engine source, tests, fixtures, and build scripts
that have been extracted to the @impeccable/detect package (BSL-1.1).

- Delete source/skills/critique/scripts/ (detect-antipatterns.mjs, browser.js)
- Delete scripts/build-browser-detector.js
- Delete tests/detect-antipatterns*.test.* and tests/fixtures/antipatterns/
- Delete .claude/skills/critique/scripts/detect-antipatterns-browser.js
- Update scripts/build.js to read detection count from npm package
- Update server/index.js to serve browser script from npm package
- Update CLAUDE.md to reference @impeccable/detect
- Update package.json test script (detection tests removed)
- Update .gitignore (remove obsolete browser script entry)
- Rebuild all provider skill distributions with updated critique skill

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-03 16:32:57 -07:00
co-authored by Claude Opus 4.6
parent 1a28702d24
commit 3974789903
78 changed files with 114 additions and 9344 deletions
+8 -7
View File
@@ -54,7 +54,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -67,25 +67,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+12 -11
View File
@@ -4,10 +4,10 @@ description: Evaluate design from a UX perspective, assessing visual hierarchy,
user-invocable: true
argument-hint: "[area (feature, page, component...)]"
allowed-tools:
- Bash(python3 -m http.server 8384 *)
- Bash(kill $(lsof -ti:8384)*)
- Bash(lsof -ti:8384*)
- Bash(node *detect-antipatterns*)
- Bash(npx @impeccable/detect *)
- Bash(npx impeccable detect *)
- Bash(kill $(lsof -ti:*)*)
- Bash(lsof -ti:*)
---
## STEPS
@@ -59,7 +59,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -72,25 +72,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
File diff suppressed because it is too large Load Diff
+8 -7
View File
@@ -53,7 +53,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -66,25 +66,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+8 -7
View File
@@ -52,7 +52,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -65,25 +65,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+8 -7
View File
@@ -52,7 +52,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -65,25 +65,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+1 -1
View File
@@ -7,7 +7,6 @@ build/
# Build artifacts
*.log
source/skills/critique/scripts/detect-antipatterns-browser.js
# OS files
.DS_Store
@@ -16,6 +15,7 @@ Thumbs.db
# IDE
.vscode/
.idea/
.claude/projects/
# Environment
.env
+8 -7
View File
@@ -52,7 +52,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -65,25 +65,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+12 -11
View File
@@ -4,10 +4,10 @@ description: Evaluate design from a UX perspective, assessing visual hierarchy,
user-invocable: true
argument-hint: "[area (feature, page, component...)]"
allowed-tools:
- Bash(python3 -m http.server 8384 *)
- Bash(kill $(lsof -ti:8384)*)
- Bash(lsof -ti:8384*)
- Bash(node *detect-antipatterns*)
- Bash(npx @impeccable/detect *)
- Bash(npx impeccable detect *)
- Bash(kill $(lsof -ti:*)*)
- Bash(lsof -ti:*)
---
## STEPS
@@ -59,7 +59,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -72,25 +72,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+12 -11
View File
@@ -2,10 +2,10 @@
name: critique
description: Evaluate design from a UX perspective, assessing visual hierarchy, information architecture, emotional resonance, cognitive load, and overall quality with quantitative scoring, persona-based testing, automated anti-pattern detection, and actionable feedback. Use when the user asks to review, critique, evaluate, or give feedback on a design or component.
allowed-tools:
- Bash(python3 -m http.server 8384 *)
- Bash(kill $(lsof -ti:8384)*)
- Bash(lsof -ti:8384*)
- Bash(node *detect-antipatterns*)
- Bash(npx @impeccable/detect *)
- Bash(npx impeccable detect *)
- Bash(kill $(lsof -ti:*)*)
- Bash(lsof -ti:*)
---
## STEPS
@@ -57,7 +57,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -70,25 +70,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+8 -7
View File
@@ -54,7 +54,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -67,25 +67,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+8 -7
View File
@@ -54,7 +54,7 @@ Run the bundled deterministic detector, which flags 25 specific patterns (AI slo
**CLI scan**:
```bash
node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
npx @impeccable/detect --json [--fast] [target]
```
- Pass HTML/JSX/TSX/Vue/Svelte files or directories as `[target]` (anything with markup). Do not pass CSS-only files.
@@ -67,25 +67,26 @@ node {{scripts_path}}/detect-antipatterns.mjs --json [--fast] [target]
The overlay is a **visual aid for the user** -- it highlights issues directly in their browser. Do NOT scroll through the page to screenshot overlays. Instead, read the console output to get the results programmatically.
1. **Serve the script**:
1. **Start the live detection server**:
```bash
python3 -m http.server 8384 -d {{scripts_path}}/ &
npx @impeccable/detect live &
```
Note the port printed to stdout (auto-assigned). Use `--port=PORT` to fix it.
2. **Create a new tab** and navigate to the page (use dev server URL for local files, or direct URL) -- do not reuse existing tabs
3. **Label the tab** via `javascript_tool` so the user can distinguish it:
```javascript
document.title = '[Human] ' + document.title;
```
4. **Scroll to top** -- ensure the page is scrolled to the very top before injection
5. **Inject** via `javascript_tool`:
5. **Inject** via `javascript_tool` (replace PORT with the port from step 1):
```javascript
const s = document.createElement('script'); s.src = 'http://localhost:8384/detect-antipatterns-browser.js'; document.head.appendChild(s);
const s = document.createElement('script'); s.src = 'http://localhost:PORT/detect.js'; document.head.appendChild(s);
```
6. Wait 2--3 seconds for the detector to render overlays
7. **Read results from console** using `read_console_messages` with pattern `impeccable` -- the detector logs all findings with the `[impeccable]` prefix. Do NOT scroll through the page to take screenshots of the overlays.
8. **Cleanup**: Kill the HTTP server when done:
8. **Cleanup**: Kill the live server when done:
```bash
kill $(lsof -ti:8384) 2>/dev/null; echo "done"
kill $(lsof -ti:PORT) 2>/dev/null; echo "done"
```
For multi-view targets, inject on 3--5 representative pages. If injection fails, continue with CLI results only.
+12 -12
View File
@@ -52,24 +52,24 @@ Unit tests (build, detector logic) run via `bun test`. Fixture tests (jsdom-base
## CLI
Impeccable includes a CLI for running anti-pattern detection outside of AI harnesses:
The detection engine lives in a separate package: [`@impeccable/detect`](https://github.com/pbakaus/impeccable-detect) (BSL-1.1).
```bash
node bin/impeccable detect [file-or-dir-or-url...] # detect anti-patterns
node bin/impeccable detect --fast --json src/ # regex-only, JSON output
node bin/impeccable --help # show help
npx @impeccable/detect [file-or-dir-or-url...] # detect anti-patterns
npx @impeccable/detect --fast --json src/ # regex-only, JSON output
npx @impeccable/detect live # start browser overlay server
npx @impeccable/detect --help # show help
```
Or via the main CLI:
```bash
node bin/impeccable detect [file-or-dir-or-url...]
node bin/impeccable live [--port=PORT]
```
**IMPORTANT**: Always use `node` (not `bun`) to run the detect CLI. Bun's jsdom implementation is extremely slow and will cause scans with HTML files to hang for minutes.
The detection script is at `source/skills/critique/scripts/detect-antipatterns.mjs`. It auto-detects browser vs Node and works as both:
- **CLI/Node**: jsdom for HTML, regex for CSS/JSX/TSX, Puppeteer for URLs
- **Browser**: visual overlays injected via `<script src="/js/detect-antipatterns-browser.js">`
Build the browser script: `node scripts/build-browser-detector.js`
**IMPORTANT**: The browser script is **generated** from `detect-antipatterns.mjs` -- never edit the browser `.js` files directly. All changes must go in the `.mjs` source. The build strips Node-specific sections and wraps it in an IIFE. Running `bun run build` also regenerates it, which will **overwrite** any direct edits to the browser script.
## Versioning
When bumping the version, update **all** of these locations to keep them in sync:
+1 -1
View File
@@ -43,7 +43,7 @@
"dev": "bun run server/index.js",
"preview": "bun run build && wrangler pages dev",
"deploy": "bun run build && wrangler pages deploy build/",
"test": "bun test tests/build.test.js tests/detect-antipatterns.test.js && node --test tests/detect-antipatterns-fixtures.test.mjs",
"test": "bun test tests/build.test.js",
"screenshot": "bun run scripts/screenshot-antipatterns.js",
"og-image": "bun run scripts/generate-og-image.js"
},
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env node
/**
* Generates .claude/skills/critique/scripts/detect-antipatterns-browser.js
* by stripping Node-specific sections from the universal source and wrapping in an IIFE.
*
* Run: node scripts/build-browser-detector.js
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, '..');
const SOURCE = path.join(ROOT, 'source/skills/critique/scripts/detect-antipatterns.mjs');
const OUTPUT = path.join(ROOT, '.claude/skills/critique/scripts/detect-antipatterns-browser.js');
// Also output next to source so the CLI's URL scanner can find it
const OUTPUT_CLI = path.join(ROOT, 'source/skills/critique/scripts/detect-antipatterns-browser.js');
let code = fs.readFileSync(SOURCE, 'utf-8');
// Strip shebang
code = code.replace(/^#!.*\n/, '');
// Strip sections between @browser-strip-start / @browser-strip-end markers
code = code.replace(/^\/\/ @browser-strip-start\n[\s\S]*?^\/\/ @browser-strip-end\n?/gm, '');
// Set IS_BROWSER = true (dead-code eliminates Node paths)
code = code.replace(/^const IS_BROWSER = .*$/m, 'const IS_BROWSER = true;');
const output = `/**
* Anti-Pattern Browser Detector for Impeccable
* GENERATED — do not edit. Source: detect-antipatterns.mjs
* Rebuild: node scripts/build-browser-detector.js
*
* Usage: <script src="detect-antipatterns-browser.js"></script>
* Re-scan: window.impeccableScan()
*/
(function () {
if (typeof window === 'undefined') return;
${code}
})();
`;
fs.mkdirSync(path.dirname(OUTPUT), { recursive: true });
fs.writeFileSync(OUTPUT, output);
fs.writeFileSync(OUTPUT_CLI, output);
console.log(`\u2713 Generated ${path.relative(ROOT, OUTPUT)} (${(output.length / 1024).toFixed(1)} KB)`);
+3 -11
View File
@@ -35,10 +35,9 @@ function generateCounts(rootDir, skills, buildDir) {
});
const commandCount = activeCommands.length;
// Count detection rules from source
const detectorSrc = fs.readFileSync(
path.join(rootDir, 'source/skills/critique/scripts/detect-antipatterns.mjs'), 'utf-8'
);
// Count detection rules from @impeccable/detect package
const detectPkgPath = path.join(rootDir, 'node_modules/@impeccable/detect/src/detect-antipatterns.mjs');
const detectorSrc = fs.readFileSync(detectPkgPath, 'utf-8');
const ruleIds = new Set();
for (const match of detectorSrc.matchAll(/^\s+id: '([^']+)'/gm)) {
ruleIds.add(match[1]);
@@ -444,13 +443,6 @@ async function build() {
// Generate authoritative counts and validate references
generateCounts(ROOT_DIR, skills, buildDir);
// Generate browser anti-pattern detector (after skill sync so it doesn't get overwritten)
try {
execSync('node scripts/build-browser-detector.js', { cwd: ROOT_DIR, stdio: 'inherit' });
} catch (error) {
console.error('Failed to build browser detector:', error.message);
}
console.log('\n✨ Build complete!');
}
+5 -4
View File
@@ -51,10 +51,11 @@ const server = serve({
const headers = { "Content-Type": "application/javascript", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY" };
const publicFile = file(`./public${url.pathname}`);
if (await publicFile.exists()) return new Response(publicFile, { headers });
// Built browser detector served at /js/detect-antipatterns-browser.js
const basename = url.pathname.split('/').pop();
const builtFile = file(`./.claude/skills/critique/scripts/${basename}`);
if (await builtFile.exists()) return new Response(builtFile, { headers });
// Browser detector served from @impeccable/detect package
if (url.pathname === '/js/detect-antipatterns-browser.js') {
const pkgFile = file('./node_modules/@impeccable/detect/src/detect-antipatterns-browser.js');
if (await pkgFile.exists()) return new Response(pkgFile, { headers });
}
return new Response("Not Found", { status: 404 });
},
// Test fixtures (for browser visual testing)
File diff suppressed because it is too large Load Diff
-266
View File
@@ -1,266 +0,0 @@
/**
* Puppeteer-powered tests for the browser detection script.
* Verifies the browser visualizer finds the same anti-patterns as the CLI.
*
* These tests start a local server and use Puppeteer to load fixture pages,
* inject the browser script, and compare findings with the CLI's jsdom output.
*
* Requires: puppeteer (npx cache or npm install)
*/
import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
import { spawn } from 'child_process';
import path from 'path';
import { detectHtml } from '../source/skills/critique/scripts/detect-antipatterns.mjs';
const FIXTURES = path.join(import.meta.dir, 'fixtures', 'antipatterns');
const PORT = 3099; // Use a different port to avoid conflicts with dev server
let serverProcess;
let puppeteer;
// Check if puppeteer is available
let hasPuppeteer = false;
try {
puppeteer = await import('puppeteer');
hasPuppeteer = true;
} catch {}
const describeIf = hasPuppeteer ? describe : describe.skip;
describeIf('browser script parity with CLI', () => {
beforeAll(async () => {
// Start a simple file server for fixtures + browser script
serverProcess = spawn('node', ['-e', `
const http = require('http');
const fs = require('fs');
const path = require('path');
const types = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript' };
http.createServer((req, res) => {
let filePath;
if (req.url.startsWith('/fixtures/')) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir))}, req.url);
} else if (req.url.startsWith('/js/')) {
// Check public/js/ first, then built artifacts
const basename = req.url.split('/').pop();
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..', 'public'))}, req.url);
if (!fs.existsSync(filePath)) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..', '.claude', 'skills', 'critique', 'scripts'))}, basename);
}
} else {
res.writeHead(404); res.end(); return;
}
try {
const content = fs.readFileSync(filePath);
const ext = path.extname(filePath);
res.writeHead(200, { 'Content-Type': types[ext] || 'text/plain' });
res.end(content);
} catch { res.writeHead(404); res.end(); }
}).listen(${PORT});
`], { stdio: 'ignore' });
// Wait for server to start
await new Promise(r => setTimeout(r, 500));
});
afterAll(() => {
if (serverProcess) serverProcess.kill();
});
async function scanWithBrowser(fixtureName) {
const browser = await puppeteer.default.launch({ headless: true });
const page = await browser.newPage();
await page.goto(`http://localhost:${PORT}/fixtures/antipatterns/${fixtureName}`, {
waitUntil: 'networkidle0',
timeout: 10000,
});
// Wait for the browser script to run (it uses setTimeout 100ms)
await new Promise(r => setTimeout(r, 300));
const results = await page.evaluate(() => {
if (!window.impeccableScan) return [];
const allFindings = window.impeccableScan();
return allFindings.flatMap(({ findings }) =>
findings.map(f => ({ type: f.type, detail: f.detail }))
);
});
await browser.close();
return results;
}
function getTypes(findings) {
return [...new Set(findings.map(f => f.antipattern || f.type))].sort();
}
test('should-flag.html: browser finds border anti-patterns', async () => {
const browserFindings = await scanWithBrowser('should-flag.html');
const types = [...new Set(browserFindings.map(f => f.type))];
expect(types).toContain('side-tab');
expect(types).toContain('border-accent-on-rounded');
}, 15000);
test('should-pass.html: browser finds no border anti-patterns', async () => {
const browserFindings = await scanWithBrowser('should-pass.html');
const borderFindings = browserFindings.filter(f =>
f.type === 'side-tab' || f.type === 'border-accent-on-rounded'
);
expect(borderFindings).toHaveLength(0);
}, 15000);
test('color-should-flag.html: browser finds color anti-patterns', async () => {
const browserFindings = await scanWithBrowser('color-should-flag.html');
const types = [...new Set(browserFindings.map(f => f.type))];
expect(types).toContain('low-contrast');
expect(types).toContain('gray-on-color');
}, 15000);
test('color-should-pass.html: browser finds zero findings', async () => {
const browserFindings = await scanWithBrowser('color-should-pass.html');
expect(browserFindings).toHaveLength(0);
}, 15000);
test('layout-should-flag.html: browser finds nested cards', async () => {
const browserFindings = await scanWithBrowser('layout-should-flag.html');
const nested = browserFindings.filter(f => f.type === 'nested-cards');
expect(nested.length).toBeGreaterThanOrEqual(3);
}, 15000);
test('layout-should-pass.html: browser finds no nested cards', async () => {
const browserFindings = await scanWithBrowser('layout-should-pass.html');
const nested = browserFindings.filter(f => f.type === 'nested-cards');
expect(nested).toHaveLength(0);
}, 15000);
test('typography-should-flag.html: browser finds typography issues', async () => {
const browserFindings = await scanWithBrowser('typography-should-flag.html');
const types = [...new Set(browserFindings.map(f => f.type))];
expect(types).toContain('overused-font');
expect(types).toContain('flat-type-hierarchy');
}, 15000);
test('partial-component.html: browser skips page-level checks', async () => {
const browserFindings = await scanWithBrowser('partial-component.html');
// Should find border issues but not typography
const hasBorder = browserFindings.some(f => f.type === 'side-tab');
const hasTypo = browserFindings.some(f =>
f.type === 'flat-type-hierarchy' || f.type === 'single-font'
);
expect(hasBorder).toBe(true);
// Browser script doesn't have isFullPage check, so we just verify borders work
}, 15000);
});
describeIf('overlay positioning accuracy', () => {
let browser, page;
const TOLERANCE = 6; // px tolerance for position comparison (2px border offset + rounding)
beforeAll(async () => {
// Reuse same server setup — these tests run in the same suite
serverProcess = spawn('node', ['-e', `
const http = require('http');
const fs = require('fs');
const path = require('path');
const types = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript' };
http.createServer((req, res) => {
let filePath;
if (req.url.startsWith('/fixtures/')) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir))}, req.url);
} else if (req.url.startsWith('/js/')) {
const basename = req.url.split('/').pop();
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..', 'public'))}, req.url);
if (!fs.existsSync(filePath)) {
filePath = path.join(${JSON.stringify(path.join(import.meta.dir, '..', '.claude', 'skills', 'critique', 'scripts'))}, basename);
}
} else {
res.writeHead(404); res.end(); return;
}
try {
const content = fs.readFileSync(filePath);
const ext = path.extname(filePath);
res.writeHead(200, { 'Content-Type': types[ext] || 'text/plain' });
res.end(content);
} catch { res.writeHead(404); res.end(); }
}).listen(${PORT + 1});
`], { stdio: 'ignore' });
await new Promise(r => setTimeout(r, 500));
browser = await puppeteer.default.launch({ headless: true });
page = await browser.newPage();
await page.setViewport({ width: 1280, height: 800 });
await page.goto(`http://localhost:${PORT + 1}/fixtures/antipatterns/overlay-positioning.html`, {
waitUntil: 'networkidle0',
timeout: 10000,
});
await new Promise(r => setTimeout(r, 300));
await page.evaluate(() => {
if (window.impeccableScan) window.impeccableScan();
});
await new Promise(r => setTimeout(r, 200));
});
afterAll(async () => {
if (browser) await browser.close();
if (serverProcess) serverProcess.kill();
});
async function getOverlayPositions() {
return page.evaluate(() => {
const overlays = document.querySelectorAll('.impeccable-overlay:not(.impeccable-banner)');
return Array.from(overlays).map(o => {
const t = o._targetEl;
if (!t) return null;
const tRect = t.getBoundingClientRect();
const oRect = o.getBoundingClientRect();
const label = o.querySelector('.impeccable-label')?.textContent || '';
return {
label,
target: { top: tRect.top, left: tRect.left, width: tRect.width, height: tRect.height },
overlay: { top: oRect.top, left: oRect.left, width: oRect.width, height: oRect.height },
overlayHidden: o.style.display === 'none',
targetVisible: tRect.width > 0 && tRect.height > 0,
inClosedDetails: !!t.closest('details:not([open])'),
};
}).filter(Boolean);
});
}
test('visible overlays are positioned within tolerance of their targets', async () => {
const positions = await getOverlayPositions();
const visible = positions.filter(p => !p.overlayHidden);
expect(visible.length).toBeGreaterThan(0);
for (const p of visible) {
const topDiff = Math.abs(p.overlay.top - p.target.top);
const leftDiff = Math.abs(p.overlay.left - p.target.left);
const widthDiff = Math.abs(p.overlay.width - p.target.width);
const heightDiff = Math.abs(p.overlay.height - p.target.height);
expect(topDiff).toBeLessThanOrEqual(TOLERANCE);
expect(leftDiff).toBeLessThanOrEqual(TOLERANCE);
expect(widthDiff).toBeLessThanOrEqual(TOLERANCE);
expect(heightDiff).toBeLessThanOrEqual(TOLERANCE);
}
}, 20000);
test('overlays for non-rendered elements are hidden', async () => {
const positions = await getOverlayPositions();
// Elements inside closed <details> should have hidden overlays
const closedDetailsOverlays = positions.filter(p => p.inClosedDetails);
expect(closedDetailsOverlays.length).toBeGreaterThan(0);
for (const p of closedDetailsOverlays) {
expect(p.overlayHidden).toBe(true);
}
}, 20000);
test('overlays inside transform ancestors are accurately positioned', async () => {
const positions = await getOverlayPositions();
const visible = positions.filter(p => !p.overlayHidden);
for (const p of visible) {
const topDiff = Math.abs(p.overlay.top - p.target.top);
const leftDiff = Math.abs(p.overlay.left - p.target.left);
expect(topDiff).toBeLessThanOrEqual(TOLERANCE);
expect(leftDiff).toBeLessThanOrEqual(TOLERANCE);
}
}, 20000);
});
-114
View File
@@ -1,114 +0,0 @@
/**
* jsdom fixture tests for anti-pattern detection.
* Run via Node's built-in test runner (not bun) to avoid jsdom resource limits.
*
* Usage: node --test tests/detect-antipatterns-fixtures.test.mjs
*/
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import path from 'path';
import { fileURLToPath } from 'url';
import {
detectHtml,
} from '../source/skills/critique/scripts/detect-antipatterns.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FIXTURES = path.join(__dirname, 'fixtures', 'antipatterns');
describe('detectHtml — jsdom fixtures', () => {
it('should-flag: catches border anti-patterns', async () => {
const f = await detectHtml(path.join(FIXTURES, 'should-flag.html'));
assert.ok(f.some(r => r.antipattern === 'side-tab'));
assert.ok(f.some(r => r.antipattern === 'border-accent-on-rounded'));
});
it('should-pass: zero border findings', async () => {
const f = await detectHtml(path.join(FIXTURES, 'should-pass.html'));
assert.equal(f.filter(r => r.antipattern === 'side-tab' || r.antipattern === 'border-accent-on-rounded').length, 0);
});
it('linked-stylesheet: catches borders, no false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'linked-stylesheet.html'));
assert.ok(f.some(r => r.antipattern === 'side-tab'));
assert.ok(f.some(r => r.antipattern === 'border-accent-on-rounded'));
assert.equal(f.filter(r => r.snippet?.includes('clean')).length, 0);
});
it('partial-component: flags borders, skips page-level', async () => {
const f = await detectHtml(path.join(FIXTURES, 'partial-component.html'));
assert.ok(f.some(r => r.antipattern === 'side-tab'));
assert.equal(f.filter(r => r.antipattern === 'flat-type-hierarchy').length, 0);
});
it('color-should-flag: detects all five color issues', async () => {
const f = await detectHtml(path.join(FIXTURES, 'color-should-flag.html'));
assert.ok(f.some(r => r.antipattern === 'pure-black-white'));
assert.ok(f.some(r => r.antipattern === 'gray-on-color'));
assert.ok(f.some(r => r.antipattern === 'low-contrast'));
assert.ok(f.some(r => r.antipattern === 'gradient-text'));
assert.ok(f.some(r => r.antipattern === 'ai-color-palette'));
});
it('color-should-pass: zero findings', async () => {
const f = await detectHtml(path.join(FIXTURES, 'color-should-pass.html'));
assert.equal(f.length, 0);
});
it('legitimate-borders: minimal false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'legitimate-borders.html'));
const borderFindings = f.filter(r => r.antipattern === 'side-tab' || r.antipattern === 'border-accent-on-rounded');
assert.ok(borderFindings.length <= 1);
});
it('typography-should-flag: detects all three issues', async () => {
const f = await detectHtml(path.join(FIXTURES, 'typography-should-flag.html'));
assert.ok(f.some(r => r.antipattern === 'overused-font'));
assert.ok(f.some(r => r.antipattern === 'single-font'));
assert.ok(f.some(r => r.antipattern === 'flat-type-hierarchy'));
});
it('typography-should-pass: zero findings', async () => {
const f = await detectHtml(path.join(FIXTURES, 'typography-should-pass.html'));
assert.equal(f.length, 0);
});
});
describe('detectHtml — layout fixtures', () => {
it('layout-should-flag: detects nested cards', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-flag.html'));
assert.ok(f.filter(r => r.antipattern === 'nested-cards').length >= 4);
});
it('layout-should-pass: no layout false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'layout-should-pass.html'));
assert.equal(f.filter(r => r.antipattern === 'nested-cards').length, 0);
assert.equal(f.filter(r => r.antipattern === 'monotonous-spacing').length, 0);
assert.equal(f.filter(r => r.antipattern === 'everything-centered').length, 0);
});
});
describe('detectHtml — motion fixtures', () => {
it('motion-should-flag: detects both motion issues', async () => {
const f = await detectHtml(path.join(FIXTURES, 'motion-should-flag.html'));
assert.ok(f.some(r => r.antipattern === 'bounce-easing'));
assert.ok(f.some(r => r.antipattern === 'layout-transition'));
});
it('motion-should-pass: no motion false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'motion-should-pass.html'));
assert.equal(f.filter(r => r.antipattern === 'bounce-easing').length, 0);
assert.equal(f.filter(r => r.antipattern === 'layout-transition').length, 0);
});
});
describe('detectHtml — dark glow fixtures', () => {
it('glow-should-flag: detects dark-glow', async () => {
const f = await detectHtml(path.join(FIXTURES, 'glow-should-flag.html'));
assert.ok(f.some(r => r.antipattern === 'dark-glow'));
});
it('glow-should-pass: no dark-glow false positives', async () => {
const f = await detectHtml(path.join(FIXTURES, 'glow-should-pass.html'));
assert.equal(f.filter(r => r.antipattern === 'dark-glow').length, 0);
});
});
File diff suppressed because it is too large Load Diff
-80
View File
@@ -1,80 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Color Anti-Patterns — Should Flag</title>
<style>
body { font-family: system-ui, sans-serif; padding: 2rem; background: #fafafa; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2rem 0 0.75rem; color: #6b7280; }
.cards { display: grid; gap: 1rem; max-width: 28rem; }
.card { padding: 1rem; border-radius: 8px; }
</style>
</head>
<body>
<h1>Color Anti-Patterns</h1>
<!-- 1. Pure black/white -->
<h2>Pure Black &amp; White</h2>
<div class="cards">
<div class="card" style="background: #000000; color: white; padding: 1rem;">
<p>Pure #000 background</p>
</div>
<div class="card" style="background: #ffffff; color: black;">
<p style="color: #000000;">Pure #000 text on pure #fff background</p>
</div>
</div>
<!-- 2. Gray on colored background -->
<h2>Gray on Color</h2>
<div class="cards">
<div class="card" style="background: rgb(59, 130, 246); padding: 1rem;">
<p style="color: rgb(150, 150, 150);">Gray text on blue background</p>
</div>
<div class="card" style="background: rgb(16, 185, 129); padding: 1rem;">
<p style="color: rgb(180, 180, 180);">Gray text on green background</p>
</div>
</div>
<!-- 3. Low contrast -->
<h2>Low Contrast</h2>
<div class="cards">
<div class="card" style="background: white;">
<p style="color: rgb(200, 200, 200);">Light gray text on white — very low contrast</p>
</div>
<div class="card" style="background: rgb(30, 30, 30);">
<p style="color: rgb(80, 80, 80);">Dark gray text on near-black — low contrast</p>
</div>
</div>
<!-- 4. Gradient text -->
<h2>Gradient Text</h2>
<div class="cards">
<h3 style="background: linear-gradient(135deg, #667eea, #764ba2); -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; font-size: 2rem;">
Gradient Heading
</h3>
</div>
<!-- 5. AI color palette -->
<h2>AI Color Palette</h2>
<div class="cards">
<h1 style="color: rgb(139, 92, 246); font-size: 2rem;">Purple heading text</h1>
</div>
<!-- 6. Tailwind color anti-patterns -->
<h2>Tailwind Colors</h2>
<div class="cards">
<div class="bg-black text-white p-4 rounded">
<p>bg-black — pure black bg</p>
</div>
<div class="bg-blue-500 text-gray-400 p-4 rounded">
<p>text-gray-400 on bg-blue-500 — gray on color</p>
</div>
<h3 class="text-purple-500 text-2xl font-bold">text-purple-500 heading</h3>
<div class="bg-gradient-to-r from-purple-500 to-indigo-500 text-white p-4 rounded">
<p>Purple-to-indigo gradient</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-49
View File
@@ -1,49 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Color — Clean Patterns</title>
<style>
body { font-family: system-ui, sans-serif; padding: 2rem; background: #fafafa; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; color: #1a1a1a; }
h2 { font-size: 1.5rem; margin: 2rem 0 0.75rem; color: #374151; }
p { font-size: 1rem; color: #4b5563; }
.cards { display: grid; gap: 1rem; max-width: 28rem; }
.card { padding: 1rem; border-radius: 8px; }
</style>
</head>
<body>
<h1>Clean Color Patterns</h1>
<!-- Tinted blacks and whites -->
<h2>Tinted Neutrals</h2>
<div class="cards">
<div class="card" style="background: rgb(26, 26, 30); padding: 1rem;">
<p style="color: rgb(240, 240, 245);">Near-black bg, near-white text — tinted, not pure</p>
</div>
<div class="card" style="background: rgb(250, 250, 252);">
<p style="color: rgb(20, 20, 25);">Near-white bg, near-black text — good contrast, tinted</p>
</div>
</div>
<!-- Good contrast -->
<h2>Good Contrast</h2>
<div class="cards">
<div class="card" style="background: rgb(30, 64, 175); padding: 1rem;">
<p style="color: rgb(240, 240, 245);">Near-white text on dark blue — high contrast, not pure white</p>
</div>
<div class="card" style="background: rgb(16, 185, 129); padding: 1rem;">
<p style="color: rgb(5, 46, 32);">Dark green text on green bg — same hue family</p>
</div>
</div>
<!-- Non-purple accent colors -->
<h2>Distinctive Accents</h2>
<div class="cards">
<h3 style="color: rgb(220, 38, 38); font-size: 1.5rem;">Red heading — not AI purple</h3>
<h3 style="color: rgb(180, 83, 9); font-size: 1.5rem;">Amber heading — distinctive</h3>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-44
View File
@@ -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',
};
-25
View File
@@ -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;
`;
-33
View File
@@ -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);
}
@@ -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 (
<Grid>
{features.map((feature) => (
<Card key={feature.title}>
<CardIcon>{feature.icon}</CardIcon>
<CardTitle>{feature.title}</CardTitle>
<CardDescription>{feature.description}</CardDescription>
</Card>
))}
</Grid>
);
}
@@ -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;
}
`;
@@ -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 (
<Section>
<Title>Build the Future</Title>
<Subtitle>
The most powerful platform for modern web development.
Ship faster, scale easier.
</Subtitle>
<CTAButton>Get Started Free</CTAButton>
</Section>
);
}
@@ -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 (
<Section>
<SectionTitle>What People Say</SectionTitle>
<Quote>
"This platform completely transformed our workflow. Deployment went from
hours to minutes."
<Author>-- Jane Smith, CTO at TechCorp</Author>
</Quote>
<Quote>
"The developer experience is unmatched. I can't imagine going back."
<Author>-- Alex Chen, Senior Engineer</Author>
</Quote>
</Section>
);
}
@@ -1,8 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;
@@ -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 (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Component {...pageProps} />
</ThemeProvider>
);
}
@@ -1,13 +0,0 @@
import { Hero } from "../components/Hero";
import { FeatureGrid } from "../components/FeatureGrid";
import { Testimonials } from "../components/Testimonials";
export default function Home() {
return (
<>
<Hero />
<FeatureGrid />
<Testimonials />
</>
);
}
@@ -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;
}
@@ -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 (
<html lang="en">
<body>{children}</body>
</html>
);
}
@@ -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;
}
@@ -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 (
<div className={styles.container}>
<Sidebar />
<main className={styles.main}>
<h1 className={styles.title}>Dashboard</h1>
<div className={styles.grid}>
<StatsCard label="Revenue" value="$48,290" change="+12.5%" />
<StatsCard label="Users" value="2,847" change="+8.1%" />
<StatsCard label="Orders" value="1,024" change="-2.3%" />
</div>
</main>
</div>
);
}
@@ -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;
}
@@ -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 (
<aside className={styles.sidebar}>
<div className={styles.logo}>Dashboard</div>
<nav className={styles.nav}>
{navItems.map((item) => (
<a key={item.label} href="#" className={styles.navItem}>
<span>{item.icon}</span>
<span>{item.label}</span>
</a>
))}
</nav>
</aside>
);
}
@@ -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;
}
@@ -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 (
<div className={styles.card}>
<span className={styles.label}>{label}</span>
<span className={styles.value}>{value}</span>
<span className={isPositive ? styles.changeUp : styles.changeDown}>
{change}
</span>
</div>
);
}
@@ -1,4 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
@@ -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;
}
@@ -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 (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
@@ -1,50 +0,0 @@
import { FeatureCard } from "../components/FeatureCard";
import { PricingCard } from "../components/PricingCard";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex">
<p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4">
Get started by editing&nbsp;
<code className="font-mono font-bold">app/page.tsx</code>
</p>
</div>
<div className="mb-32 text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:text-left">
<h1 className="text-5xl font-bold bg-gradient-to-r from-purple-400 to-cyan-400 bg-clip-text text-transparent mb-8">
Welcome to Our Platform
</h1>
<p className="text-gray-400 text-lg mb-12">
The next generation of web development
</p>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<FeatureCard
title="Fast"
description="Lightning-fast performance out of the box"
icon="⚡"
/>
<FeatureCard
title="Scalable"
description="Grows with your business needs"
icon="📈"
/>
<FeatureCard
title="Secure"
description="Enterprise-grade security built in"
icon="🔒"
/>
</div>
<div className="mt-16">
<h2 className="text-purple-500 text-3xl font-bold text-center mb-8">Pricing</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
<PricingCard name="Starter" price="$9" features={["5 projects", "Basic support"]} />
<PricingCard name="Pro" price="$29" features={["Unlimited projects", "Priority support"]} highlighted />
</div>
</div>
</div>
</main>
);
}
@@ -1,15 +0,0 @@
interface FeatureCardProps {
title: string;
description: string;
icon: string;
}
export function FeatureCard({ title, description, icon }: FeatureCardProps) {
return (
<div className="group border-l-4 border-blue-500 rounded-xl bg-white p-6 shadow-lg hover:shadow-xl transition-all">
<div className="text-4xl mb-4 animate-bounce">{icon}</div>
<h3 className="text-purple-600 text-xl font-bold mb-2">{title}</h3>
<p className="text-gray-400">{description}</p>
</div>
);
}
@@ -1,35 +0,0 @@
interface PricingCardProps {
name: string;
price: string;
features: string[];
highlighted?: boolean;
}
export function PricingCard({ name, price, features, highlighted }: PricingCardProps) {
return (
<div
className={`rounded-2xl p-8 ${
highlighted
? "bg-black border-t-4 border-violet-500 text-white"
: "bg-white border border-gray-200"
}`}
>
<h3 className="text-xl font-bold mb-2">{name}</h3>
<div className="text-4xl font-bold mb-6 bg-gradient-to-r from-violet-500 to-fuchsia-500 bg-clip-text text-transparent">
{price}
<span className="text-sm text-gray-400">/mo</span>
</div>
<ul className="space-y-3">
{features.map((feature) => (
<li key={feature} className="flex items-center gap-2">
<span className="text-violet-500"></span>
{feature}
</li>
))}
</ul>
<button className="mt-8 w-full py-3 rounded-lg bg-gradient-to-r from-purple-500 to-indigo-500 text-white font-semibold transition-all hover:scale-105">
Get Started
</button>
</div>
);
}
@@ -1,4 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;
@@ -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;
-9
View File
@@ -1,9 +0,0 @@
import React from 'react';
export function App() {
return (
<div className="border-l-4 border-indigo-500 rounded-xl p-6">
<h1>Hello Vite</h1>
</div>
);
}
@@ -1,9 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
server: {
port: 8080,
},
});
-88
View File
@@ -1,88 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark Glow Anti-Patterns That Should Be Flagged</title>
<style>
body { font-family: system-ui, sans-serif; background: #111827; color: #f3f4f6; padding: 2rem; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #9ca3af; border-bottom: 1px solid #374151; padding-bottom: 0.5rem; }
p.intro { color: #9ca3af; margin-bottom: 2rem; max-width: 36rem; font-size: 0.875rem; }
.cards { display: grid; gap: 1rem; max-width: 28rem; }
.card { padding: 1.5rem; border-radius: 0.75rem; background: #1f2937; }
.card h3 { font-weight: 600; font-size: 0.875rem; color: #f9fafb; }
.card p { font-size: 0.8125rem; color: #9ca3af; margin-top: 0.25rem; }
/* Blue glow */
.glow-blue {
box-shadow: 0 0 20px rgba(59, 130, 246, 0.4);
}
/* Purple glow */
.glow-purple {
box-shadow: 0 0 25px rgba(139, 92, 246, 0.35);
}
/* Cyan glow */
.glow-cyan {
box-shadow: 0 0 15px rgba(6, 182, 212, 0.5);
}
/* Multi-shadow with glow */
.glow-multi {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 0 30px rgba(168, 85, 247, 0.3);
}
/* Neon button */
.glow-button {
display: inline-block;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background: #2563eb;
color: white;
font-weight: 600;
box-shadow: 0 0 20px rgba(59, 130, 246, 0.6), 0 0 60px rgba(59, 130, 246, 0.2);
}
/* Inline style glow (for regex detection) */
</style>
</head>
<body>
<h1>Dark Glow: Should Flag</h1>
<p class="intro">Every glow effect on this dark page should be detected.</p>
<h2>CSS Colored Glows on Dark Background</h2>
<div class="cards">
<div class="card glow-blue">
<h3>Blue glow</h3>
<p>box-shadow: 0 0 20px rgba(59, 130, 246, 0.4)</p>
</div>
<div class="card glow-purple">
<h3>Purple glow</h3>
<p>box-shadow: 0 0 25px rgba(139, 92, 246, 0.35)</p>
</div>
<div class="card glow-cyan">
<h3>Cyan glow</h3>
<p>box-shadow: 0 0 15px rgba(6, 182, 212, 0.5)</p>
</div>
<div class="card glow-multi">
<h3>Multi-shadow with colored glow</h3>
<p>Normal shadow + purple glow combined</p>
</div>
</div>
<h2>Neon Buttons</h2>
<div class="cards">
<div class="card">
<div class="glow-button">Glowing Button</div>
<p>Neon glow effect on button.</p>
</div>
</div>
<h2>Inline Style Glow</h2>
<div class="cards">
<div class="card" style="box-shadow: 0 0 20px rgba(236, 72, 153, 0.5);">
<h3>Inline pink glow</h3>
<p>Inline style for regex detection path.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-212
View File
@@ -1,212 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dark/Shadow Patterns That Should Pass</title>
<style>
body { font-family: system-ui, sans-serif; padding: 2rem; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; font-size: 0.875rem; }
.cards { display: grid; gap: 1rem; max-width: 28rem; }
.card { padding: 1.5rem; border-radius: 0.75rem; }
.card h3 { font-weight: 600; font-size: 0.875rem; }
.card p { font-size: 0.8125rem; color: #6b7280; margin-top: 0.25rem; }
/* Light page with tinted shadow — not glow */
.light-colored-shadow {
background: white;
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.15);
}
/* Dark element with normal gray shadow — not glow */
.dark-normal-shadow {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
/* Dark element with focus ring (spread only, no blur) */
.dark-focus-ring {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
}
/* Dark element with tiny blur (< 5px) — subtle, not glow */
.dark-subtle-shadow {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 1px 3px rgba(59, 130, 246, 0.2);
}
/* Dark element with no shadow */
.dark-no-shadow {
background: #1f2937;
color: #f3f4f6;
}
/* Medium gray background — not dark enough */
.medium-gray {
background: #6b7280;
color: white;
box-shadow: 0 2px 4px rgba(59, 130, 246, 0.3);
}
/* --- Typical elevated cards on light backgrounds --- */
.elevated-sm {
background: white;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
}
.elevated-md {
background: white;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
}
.elevated-lg {
background: white;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
}
.elevated-xl {
background: white;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
.elevated-warm {
background: white;
box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.15);
}
/* --- Typical elevated cards on dark backgrounds --- */
.dark-section { background: #111827; padding: 2rem; border-radius: 0.75rem; margin-top: 1rem; }
.dark-elevated-sm {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
.dark-elevated-md {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.4);
}
.dark-elevated-lg {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
}
.dark-elevated-inset {
background: #1f2937;
color: #f3f4f6;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3);
}
.dark-elevated-multi {
background: #1f2937;
color: #f3f4f6;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3), 0 1px 3px rgba(0, 0, 0, 0.2);
}
</style>
</head>
<body style="background: #f9fafb;">
<h1>Glow Patterns: Should Pass</h1>
<p class="intro">None of these should trigger dark-glow warnings.</p>
<h2>Light Page + Colored Shadow</h2>
<div class="cards">
<div class="card light-colored-shadow">
<h3>Colored shadow on light background</h3>
<p>Not dark mode, colored shadow is fine.</p>
</div>
</div>
<h2>Typical Light Elevated Cards</h2>
<div class="cards">
<div class="card elevated-sm">
<h3>Small elevation (Tailwind shadow-sm)</h3>
<p>Standard subtle card shadow.</p>
</div>
<div class="card elevated-md">
<h3>Medium elevation (Tailwind shadow-md)</h3>
<p>Standard card shadow.</p>
</div>
<div class="card elevated-lg">
<h3>Large elevation (Tailwind shadow-lg)</h3>
<p>Prominent card shadow.</p>
</div>
<div class="card elevated-xl">
<h3>Extra large elevation (Tailwind shadow-xl)</h3>
<p>Modal-style shadow.</p>
</div>
<div class="card elevated-warm">
<h3>Warm deep shadow</h3>
<p>Large spread neutral shadow.</p>
</div>
</div>
<h2>Typical Dark Elevated Cards</h2>
<div class="dark-section">
<div class="cards">
<div class="card dark-elevated-sm">
<h3>Small dark elevation</h3>
<p>Subtle shadow on dark card.</p>
</div>
<div class="card dark-elevated-md">
<h3>Medium dark elevation</h3>
<p>Standard dark card shadow.</p>
</div>
<div class="card dark-elevated-lg">
<h3>Large dark elevation</h3>
<p>Prominent shadow on dark card.</p>
</div>
<div class="card dark-elevated-inset">
<h3>Inset shadow</h3>
<p>Inner shadow, not a glow.</p>
</div>
<div class="card dark-elevated-multi">
<h3>Multi-shadow dark card</h3>
<p>Layered gray shadows, no color glow.</p>
</div>
</div>
</div>
<h2>Dark Element + Normal Shadow</h2>
<div class="cards">
<div class="card dark-normal-shadow">
<h3>Normal gray shadow on dark card</h3>
<p>Gray/black shadow is not a glow.</p>
</div>
</div>
<h2>Dark Element + Focus Ring</h2>
<div class="cards">
<div class="card dark-focus-ring">
<h3>Focus ring (spread, no blur)</h3>
<p>Functional ring, not decorative glow.</p>
</div>
</div>
<h2>Dark Element + Subtle Shadow</h2>
<div class="cards">
<div class="card dark-subtle-shadow">
<h3>Tiny blur (&lt;5px)</h3>
<p>Too subtle to be "glowing".</p>
</div>
</div>
<h2>Dark Element + No Shadow</h2>
<div class="cards">
<div class="card dark-no-shadow">
<h3>No shadow at all</h3>
<p>Just a dark card, no glow.</p>
</div>
</div>
<h2>Medium Gray + Colored Shadow</h2>
<div class="cards">
<div class="card medium-gray">
<h3>Not dark enough background</h3>
<p>Medium gray doesn't qualify as "dark mode".</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-56
View File
@@ -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 (
<div className="border-l-4 border-blue-500 rounded-lg bg-white p-6 shadow-md">
<div className="flex items-center gap-4">
<div className="text-purple-500 text-3xl">{icon}</div>
<h3 className="text-xl font-bold">{title}</h3>
</div>
<p className="text-gray-400 mt-2">{description}</p>
</div>
);
}
export function HeroSection() {
return (
<section className="bg-black py-20 text-center">
<h1
className="text-5xl font-bold bg-gradient-to-r from-purple-400 to-cyan-400 bg-clip-text text-transparent"
>
Welcome to the Future
</h1>
<p className="mt-4 text-gray-400">Build something amazing today</p>
</section>
);
}
export function StatsCard({ value, label }) {
return (
<div
style={{
borderLeft: '4px solid #3b82f6',
borderRadius: '12px',
padding: '16px',
background: '#fff',
}}
>
<span style={{ fontSize: '32px', fontFamily: "'Inter', sans-serif" }}>{value}</span>
<p>{label}</p>
</div>
);
}
export function AnimatedPanel({ children }) {
return (
<motion.div
className="animate-bounce"
style={{ transition: 'width 0.3s ease' }}
>
{children}
</motion.div>
);
}
-33
View File
@@ -1,33 +0,0 @@
// Clean React component -- no anti-patterns
import React from 'react';
export function FeatureCard({ title, description, icon }) {
return (
<div className="rounded-lg bg-white p-6 shadow-sm ring-1 ring-gray-200">
<div className="flex items-center gap-4">
<div className="text-teal-600 text-2xl">{icon}</div>
<h3 className="text-lg font-semibold text-gray-900">{title}</h3>
</div>
<p className="text-gray-600 mt-2">{description}</p>
</div>
);
}
export function HeroSection() {
return (
<section className="bg-gray-950 py-20">
<h1 className="text-5xl font-bold text-white">Welcome</h1>
<p className="mt-4 text-gray-300">Build something amazing today</p>
</section>
);
}
export function StatsCard({ value, label }) {
return (
<div className="rounded-lg p-6 ring-1 ring-gray-200">
<span className="text-3xl font-bold tabular-nums">{value}</span>
<p className="text-gray-600 mt-1">{label}</p>
</div>
);
}
-137
View File
@@ -1,137 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Layout Anti-Patterns — Should Flag</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; }
</style>
</head>
<body>
<h1>Layout Anti-Patterns</h1>
<p class="intro">These should all be flagged by the detector.</p>
<!-- ============================================================ -->
<!-- 1. NESTED CARDS — Cardocalypse -->
<!-- ============================================================ -->
<h2>Nested Cards (Cardocalypse)</h2>
<!-- Classic: card inside card, both with shadow + rounded + bg -->
<div class="bg-white rounded-lg shadow-md p-6 max-w-md mb-4">
<h3 class="text-lg font-semibold mb-3">Outer Card</h3>
<div class="bg-white rounded-lg shadow-md p-4">
<h4 class="font-medium">Inner Card</h4>
<p class="text-sm text-gray-600">Card inside card — the classic cardocalypse.</p>
</div>
</div>
<!-- Three levels deep -->
<div class="bg-white rounded-xl shadow-lg p-6 max-w-md mb-4">
<h3 class="text-lg font-semibold mb-3">Level 1</h3>
<div class="bg-gray-50 rounded-lg shadow-sm p-4 mb-3">
<h4 class="font-medium mb-2">Level 2</h4>
<div class="bg-white rounded-md shadow-sm p-3">
<p class="text-sm">Level 3 — nesting inception.</p>
</div>
</div>
</div>
<!-- CSS variant: border + bg + shadow nesting -->
<div style="background: white; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); padding: 1.5rem; max-width: 28rem; margin-bottom: 1rem;">
<h3 style="font-weight: 600; margin-bottom: 0.75rem;">Outer (CSS)</h3>
<div style="background: #f9fafb; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.08); padding: 1rem;">
<p style="font-size: 0.875rem; color: #6b7280;">Inner card via CSS.</p>
</div>
</div>
<!-- shadcn-style Card nesting (Card inside Card, not Card sub-components) -->
<div class="rounded-lg border bg-white shadow-sm p-6 max-w-md mb-4" data-component="card">
<h3 class="font-semibold mb-3">shadcn-style Outer Card</h3>
<div class="rounded-lg border bg-white shadow-sm p-4" data-component="card">
<p class="text-sm text-gray-600">Another shadcn Card nested inside — still bad.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 2. IDENTICAL CARD GRIDS -->
<!-- ============================================================ -->
<h2>Identical Card Grid</h2>
<!-- Classic: 4 identical cards with icon + heading + text -->
<div class="grid grid-cols-2 gap-4 max-w-2xl mb-4">
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
</div>
<h3 class="font-semibold mb-2">Fast Performance</h3>
<p class="text-sm text-gray-600">Lightning fast response times with optimized infrastructure.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-green-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
</div>
<h3 class="font-semibold mb-2">Reliable Uptime</h3>
<p class="text-sm text-gray-600">99.99% uptime guarantee with automatic failover.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-purple-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/></svg>
</div>
<h3 class="font-semibold mb-2">Bank-Level Security</h3>
<p class="text-sm text-gray-600">Enterprise-grade encryption and security controls.</p>
</div>
<div class="bg-white rounded-lg shadow-sm p-6">
<div class="w-12 h-12 bg-orange-100 rounded-lg flex items-center justify-center mb-4">
<svg class="w-6 h-6 text-orange-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
</div>
<h3 class="font-semibold mb-2">Team Collaboration</h3>
<p class="text-sm text-gray-600">Built for teams with real-time collaboration tools.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 3. MONOTONOUS SPACING -->
<!-- ============================================================ -->
<h2>Monotonous Spacing</h2>
<!-- Everything padded with the same value, same gaps -->
<div style="max-width: 28rem; margin-bottom: 1rem;">
<div style="background: white; padding: 16px; margin-bottom: 16px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin-bottom: 16px; font-weight: 600;">Section One</h3>
<p style="margin-bottom: 16px; font-size: 0.875rem; color: #6b7280;">Every margin and padding is exactly 16px.</p>
<div style="padding: 16px; background: #f3f4f6; border-radius: 8px;">
<p style="font-size: 0.875rem; color: #6b7280; margin-bottom: 16px;">Even the inner padding is 16px.</p>
</div>
</div>
<div style="background: white; padding: 16px; margin-bottom: 16px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="margin-bottom: 16px; font-weight: 600;">Section Two</h3>
<p style="margin-bottom: 16px; font-size: 0.875rem; color: #6b7280;">No rhythm, no variation, just 16px everywhere.</p>
</div>
</div>
<!-- ============================================================ -->
<!-- 4. EVERYTHING CENTERED -->
<!-- ============================================================ -->
<h2>Everything Centered</h2>
<div style="text-align: center; max-width: 32rem; margin: 0 auto 1rem;">
<h3 style="text-align: center; font-size: 1.25rem; font-weight: 700; margin-bottom: 1rem;">Welcome to Our Platform</h3>
<p style="text-align: center; color: #6b7280; margin-bottom: 1rem;">We provide the best solutions for your business needs.</p>
<div style="text-align: center; display: flex; gap: 1rem; justify-content: center; margin-bottom: 1rem;">
<button style="text-align: center; padding: 0.5rem 1rem; background: #3b82f6; color: white; border: none; border-radius: 6px;">Get Started</button>
<button style="text-align: center; padding: 0.5rem 1rem; background: white; color: #374151; border: 1px solid #d1d5db; border-radius: 6px;">Learn More</button>
</div>
<p style="text-align: center; font-size: 0.75rem; color: #9ca3af;">Trusted by over 10,000 companies worldwide.</p>
<div style="text-align: center; padding: 1rem; background: #f3f4f6; border-radius: 8px; margin-top: 1rem;">
<p style="text-align: center; font-size: 0.875rem; color: #6b7280;">Every. Single. Element. Is. Centered.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-242
View File
@@ -1,242 +0,0 @@
<!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>
-113
View File
@@ -1,113 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Legitimate Border Patterns — Should NOT Flag</title>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; color: #111827; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2rem 0 0.75rem; color: #6b7280; }
.demo { max-width: 36rem; margin-bottom: 1.5rem; }
</style>
</head>
<body>
<h1>Legitimate Border Patterns</h1>
<p style="color: #6b7280; margin-bottom: 2rem;">Every border here is a well-established web pattern. None should be flagged.</p>
<!-- 1. BLOCKQUOTE — the classic border-left usage -->
<h2>Blockquotes</h2>
<div class="demo">
<blockquote style="border-left: 4px solid #d1d5db; padding-left: 1rem; margin: 0; color: #4b5563; font-style: italic;">
"Design is not just what it looks like and feels like. Design is how it works."
</blockquote>
</div>
<!-- 2. SIDEBAR NAV — active state indicator -->
<h2>Sidebar Navigation (Active State)</h2>
<div class="demo">
<nav style="width: 200px; background: white; border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden;">
<a href="#" style="display: block; padding: 0.75rem 1rem; color: #6b7280; text-decoration: none; border-left: 3px solid transparent;">Dashboard</a>
<a href="#" style="display: block; padding: 0.75rem 1rem; color: #1d4ed8; text-decoration: none; border-left: 3px solid #3b82f6; background: #eff6ff; font-weight: 500;">Projects</a>
<a href="#" style="display: block; padding: 0.75rem 1rem; color: #6b7280; text-decoration: none; border-left: 3px solid transparent;">Settings</a>
</nav>
</div>
<!-- 3. FORM VALIDATION — error indicator on input -->
<h2>Form Validation</h2>
<div class="demo">
<div style="margin-bottom: 1rem;">
<label style="display: block; font-size: 0.875rem; font-weight: 500; margin-bottom: 0.25rem;">Email</label>
<input type="email" value="not-an-email" style="width: 100%; padding: 0.5rem 0.75rem; border: 1px solid #fca5a5; border-left: 3px solid #ef4444; border-radius: 6px; outline: none; font-size: 0.875rem;">
<p style="color: #ef4444; font-size: 0.75rem; margin-top: 0.25rem;">Please enter a valid email address.</p>
</div>
</div>
<!-- 4. TIMELINE / STEPPER -->
<h2>Timeline</h2>
<div class="demo">
<div style="border-left: 3px solid #e5e7eb; padding-left: 1.5rem; margin-left: 0.75rem;">
<div style="position: relative; padding-bottom: 1.5rem;">
<div style="position: absolute; left: -2.05rem; top: 0.125rem; width: 10px; height: 10px; border-radius: 50%; background: #3b82f6; border: 2px solid white;"></div>
<p style="font-weight: 500; margin: 0;">Order placed</p>
<p style="font-size: 0.75rem; color: #6b7280; margin: 0.125rem 0 0;">March 15, 2026</p>
</div>
<div style="position: relative; padding-bottom: 1.5rem;">
<div style="position: absolute; left: -2.05rem; top: 0.125rem; width: 10px; height: 10px; border-radius: 50%; background: #3b82f6; border: 2px solid white;"></div>
<p style="font-weight: 500; margin: 0;">Shipped</p>
<p style="font-size: 0.75rem; color: #6b7280; margin: 0.125rem 0 0;">March 16, 2026</p>
</div>
<div style="position: relative;">
<div style="position: absolute; left: -2.05rem; top: 0.125rem; width: 10px; height: 10px; border-radius: 50%; background: #d1d5db; border: 2px solid white;"></div>
<p style="font-weight: 500; margin: 0; color: #9ca3af;">Delivered</p>
<p style="font-size: 0.75rem; color: #6b7280; margin: 0.125rem 0 0;">Expected March 18</p>
</div>
</div>
</div>
<!-- 5. CODE BLOCK — diff highlighting -->
<h2>Code Diff Highlighting</h2>
<div class="demo">
<pre style="background: #1e293b; color: #e2e8f0; padding: 1rem; border-radius: 8px; font-size: 0.8125rem; overflow-x: auto; margin: 0;">
<code><span style="display: block; border-left: 3px solid #ef4444; padding-left: 0.75rem; background: rgba(239,68,68,0.1);">- const old = getValue();</span>
<span style="display: block; border-left: 3px solid #22c55e; padding-left: 0.75rem; background: rgba(34,197,94,0.1);">+ const value = getNewValue();</span>
<span style="display: block; padding-left: calc(3px + 0.75rem);"> return value;</span></code></pre>
</div>
<!-- 6. TAB ACTIVE STATE — bottom border -->
<h2>Tab Navigation</h2>
<div class="demo">
<div style="display: flex; border-bottom: 1px solid #e5e7eb;">
<button style="padding: 0.75rem 1.25rem; border: none; background: none; font-size: 0.875rem; color: #1d4ed8; border-bottom: 2px solid #3b82f6; font-weight: 500; cursor: pointer;">Overview</button>
<button style="padding: 0.75rem 1.25rem; border: none; background: none; font-size: 0.875rem; color: #6b7280; border-bottom: 2px solid transparent; cursor: pointer;">Analytics</button>
<button style="padding: 0.75rem 1.25rem; border: none; background: none; font-size: 0.875rem; color: #6b7280; border-bottom: 2px solid transparent; cursor: pointer;">Settings</button>
</div>
</div>
<!-- 7. TABLE CELL BORDERS -->
<h2>Data Table</h2>
<div class="demo">
<table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">
<thead>
<tr>
<th style="text-align: left; padding: 0.5rem; border-bottom: 2px solid #111827;">Name</th>
<th style="text-align: right; padding: 0.5rem; border-bottom: 2px solid #111827;">Revenue</th>
</tr>
</thead>
<tbody>
<tr><td style="padding: 0.5rem; border-bottom: 1px solid #e5e7eb;">Acme Corp</td><td style="text-align: right; padding: 0.5rem; border-bottom: 1px solid #e5e7eb;">$1.2M</td></tr>
<tr><td style="padding: 0.5rem; border-bottom: 1px solid #e5e7eb;">Globex</td><td style="text-align: right; padding: 0.5rem; border-bottom: 1px solid #e5e7eb;">$850K</td></tr>
</tbody>
</table>
</div>
<!-- 8. ALERT BANNER — full-width, not a card -->
<h2>Alert Banner</h2>
<div class="demo">
<div style="border-left: 4px solid #f59e0b; background: #fffbeb; padding: 0.75rem 1rem; font-size: 0.875rem; color: #92400e;">
<strong>Warning:</strong> Your trial expires in 3 days. <a href="#" style="color: #d97706;">Upgrade now</a>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-48
View File
@@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Anti-Patterns From Linked Stylesheet</title>
<link rel="stylesheet" href="external-styles.css">
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2rem 0 0.75rem; color: #6b7280; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; }
.cards { display: grid; gap: 1rem; max-width: 28rem; }
</style>
</head>
<body>
<h1>Linked Stylesheet Anti-Patterns</h1>
<p class="intro">
These elements get their anti-pattern styles from an external CSS file.
Regex-only scanning misses these — only computed style analysis catches them.
</p>
<h2>Side-Tab (from external CSS)</h2>
<div class="cards">
<div class="external-side-tab">
<h3 style="font-weight: 600;">External side-tab class</h3>
<p style="font-size: 0.875rem; color: #6b7280;">border-left + border-radius from linked stylesheet.</p>
</div>
</div>
<h2>Top Accent + Rounded (from external CSS)</h2>
<div class="cards">
<div class="external-top-accent">
<h3 style="font-weight: 600;">External top accent class</h3>
<p style="font-size: 0.875rem; color: #6b7280;">border-top + border-radius from linked stylesheet.</p>
</div>
</div>
<h2>Clean Card (from external CSS)</h2>
<div class="cards">
<div class="external-clean">
<h3 style="font-weight: 600;">External clean card</h3>
<p style="font-size: 0.875rem; color: #6b7280;">Uniform 1px border — should NOT flag.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-118
View File
@@ -1,118 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Motion Anti-Patterns That Should Be Flagged</title>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; font-size: 0.875rem; }
.demo { max-width: 28rem; margin-bottom: 1rem; padding: 1rem; background: white; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.demo h3 { font-weight: 600; font-size: 0.875rem; }
.demo p { font-size: 0.8125rem; color: #6b7280; margin-top: 0.25rem; }
/* Bounce easing */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.bounce-animation {
animation: bounce 1s infinite;
}
/* Elastic cubic-bezier (overshoot) */
.elastic-transition {
transition: transform 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Layout property transitions */
.width-transition {
transition: width 0.3s ease;
}
.height-transition {
transition: height 0.4s ease-out;
}
.padding-transition {
transition: padding 0.2s linear;
}
.margin-transition {
transition: margin 0.3s ease-in;
}
.max-height-transition {
transition: max-height 0.5s ease;
}
.multi-layout-transition {
transition: width 0.3s ease, height 0.3s ease;
}
.mixed-transition {
transition: width 0.3s ease, opacity 0.3s ease;
}
.transition-property-width {
transition-property: width;
transition-duration: 0.3s;
}
</style>
</head>
<body>
<h1>Motion Anti-Patterns: Should Flag</h1>
<p class="intro">Every example on this page should be detected by the motion anti-pattern scanner.</p>
<h2>Bounce / Elastic Easing</h2>
<div class="demo bounce-animation">
<h3>CSS bounce animation</h3>
<p>animation: bounce 1s infinite</p>
</div>
<div class="demo elastic-transition">
<h3>Elastic cubic-bezier</h3>
<p>cubic-bezier(0.68, -0.55, 0.265, 1.55)</p>
</div>
<h2>Layout Property Transitions</h2>
<div class="demo width-transition">
<h3>transition: width</h3>
<p>Animating width causes layout thrash.</p>
</div>
<div class="demo height-transition">
<h3>transition: height</h3>
<p>Animating height causes layout thrash.</p>
</div>
<div class="demo padding-transition">
<h3>transition: padding</h3>
<p>Animating padding causes layout thrash.</p>
</div>
<div class="demo margin-transition">
<h3>transition: margin</h3>
<p>Animating margin causes layout thrash.</p>
</div>
<div class="demo max-height-transition">
<h3>transition: max-height</h3>
<p>Use grid-template-rows instead.</p>
</div>
<div class="demo multi-layout-transition">
<h3>transition: width, height</h3>
<p>Multiple layout properties.</p>
</div>
<div class="demo mixed-transition">
<h3>transition: width, opacity</h3>
<p>Layout property mixed with OK property.</p>
</div>
<div class="demo transition-property-width">
<h3>transition-property: width</h3>
<p>Longhand form.</p>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-109
View File
@@ -1,109 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Motion Patterns That Should Pass</title>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; font-size: 0.875rem; }
.demo { max-width: 28rem; margin-bottom: 1rem; padding: 1rem; background: white; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.demo h3 { font-weight: 600; font-size: 0.875rem; }
.demo p { font-size: 0.8125rem; color: #6b7280; margin-top: 0.25rem; }
/* Good easing — smooth exponential deceleration */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
/* Good transitions — transform and opacity only */
.transform-transition {
transition: transform 0.3s ease-out;
}
.opacity-transition {
transition: opacity 0.2s ease;
}
.color-transition {
transition: color 0.15s ease, background-color 0.15s ease;
}
.shadow-transition {
transition: box-shadow 0.2s ease;
}
.all-transition {
transition: all 0.3s ease;
}
.multi-safe-transition {
transition: transform 0.3s ease, opacity 0.3s ease, box-shadow 0.2s ease;
}
/* Standard easing curves (y values within [0, 1]) */
.ease-out-quart {
transition: transform 0.4s cubic-bezier(0.25, 1, 0.5, 1);
}
.ease-out-expo {
transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
</style>
</head>
<body>
<h1>Motion Patterns: Should Pass</h1>
<p class="intro">None of these should trigger motion anti-pattern warnings.</p>
<h2>Good Easing</h2>
<div class="demo fade-in">
<h3>Smooth fade in</h3>
<p>animation: fadeIn with exponential ease-out</p>
</div>
<div class="demo ease-out-quart">
<h3>Ease-out quart</h3>
<p>cubic-bezier(0.25, 1, 0.5, 1) — smooth deceleration</p>
</div>
<div class="demo ease-out-expo">
<h3>Ease-out expo</h3>
<p>cubic-bezier(0.16, 1, 0.3, 1) — natural feel</p>
</div>
<h2>Good Transitions (transform/opacity/color only)</h2>
<div class="demo transform-transition">
<h3>transition: transform</h3>
<p>Transform is GPU-accelerated and safe.</p>
</div>
<div class="demo opacity-transition">
<h3>transition: opacity</h3>
<p>Opacity is GPU-accelerated and safe.</p>
</div>
<div class="demo color-transition">
<h3>transition: color, background-color</h3>
<p>Color transitions are paint-only, no layout.</p>
</div>
<div class="demo shadow-transition">
<h3>transition: box-shadow</h3>
<p>Shadow transitions are paint-only.</p>
</div>
<div class="demo all-transition">
<h3>transition: all</h3>
<p>Too common to flag — might include layout but usually paired with transform/opacity.</p>
</div>
<div class="demo multi-safe-transition">
<h3>transition: transform, opacity, box-shadow</h3>
<p>Multiple safe properties combined.</p>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-14
View File
@@ -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 (
<main className="p-8">
<h1 className="text-3xl font-bold">Dashboard</h1>
<Card title="Revenue" value="$12,345" />
<Card title="Users" value="1,234" />
</main>
);
}
-16
View File
@@ -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 (
<div className="border-l-4 border-blue-500 rounded-lg p-4 bg-white shadow">
<h3 className="text-purple-500 text-xl font-bold">{title}</h3>
<p className="text-2xl tabular-nums">{value}</p>
</div>
);
}
-9
View File
@@ -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;
}
-6
View File
@@ -1,6 +0,0 @@
@import './variables';
.card {
border-left: 4px solid $primary;
border-radius: 12px;
}
-3
View File
@@ -1,3 +0,0 @@
$primary: #3b82f6;
$radius: 12px;
$spacing: 16px;
-525
View File
@@ -1,525 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Overlay Positioning Edge Cases</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #f9fafb;
padding: 2rem;
max-width: 900px;
margin: 0 auto;
}
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
.scenario { margin-bottom: 2rem; padding: 1rem; border: 1px dashed #d1d5db; border-radius: 8px; }
.scenario-label { font-size: 0.75rem; color: #6b7280; margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.05em; }
/* ============================================
1. TRANSFORM ANCESTOR
position:absolute overlays on body use document coords,
but getBoundingClientRect is viewport-relative.
Transforms on ancestors create new stacking contexts.
============================================ */
.transform-container {
transform: translateY(0);
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.transform-rotate {
transform: rotate(0deg);
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.transform-scale {
transform: scale(1);
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.transform-nested-outer {
transform: translateX(0);
padding: 1rem;
background: #e5e7eb;
border-radius: 8px;
}
.transform-nested-inner {
transform: translateY(0);
padding: 1rem;
background: #d1d5db;
border-radius: 8px;
}
/* ============================================
2. CLOSED <details>
Elements inside collapsed details may report
a bounding rect but aren't visually present.
============================================ */
details { margin-bottom: 1rem; }
summary { cursor: pointer; font-weight: 600; }
/* ============================================
3. STICKY / FIXED POSITIONING
Sticky elements change their containing block
behavior during scroll.
============================================ */
.sticky-container {
height: 200px;
overflow-y: auto;
border: 1px solid #d1d5db;
border-radius: 8px;
}
.sticky-header {
position: sticky;
top: 0;
background: #1f2937;
color: white;
padding: 0.5rem 1rem;
z-index: 10;
}
.sticky-content {
padding: 1rem;
}
/* ============================================
4. OVERFLOW HIDDEN
Elements that extend beyond an overflow:hidden
parent are visually clipped but still in the DOM.
============================================ */
.overflow-hidden-container {
overflow: hidden;
height: 60px;
border: 1px solid #d1d5db;
border-radius: 8px;
padding: 0.5rem 1rem;
}
/* ============================================
5. ABSOLUTE / RELATIVE POSITIONING
Elements that are offset from their normal flow
position via top/left.
============================================ */
.relative-offset {
position: relative;
top: 20px;
left: 40px;
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.absolute-container {
position: relative;
height: 120px;
background: #f3f4f6;
border-radius: 8px;
}
.absolute-child {
position: absolute;
bottom: 10px;
right: 10px;
}
/* ============================================
6. FLEXBOX / GRID CENTERING
Elements centered via flex/grid may have
unexpected positions vs. document flow.
============================================ */
.flex-center {
display: flex;
justify-content: center;
align-items: center;
height: 120px;
background: #f3f4f6;
border-radius: 8px;
}
.grid-center {
display: grid;
place-items: center;
height: 120px;
background: #f3f4f6;
border-radius: 8px;
}
/* ============================================
7. WILL-CHANGE / CONTAIN / FILTER
These CSS properties create new containing
blocks, similar to transforms.
============================================ */
.will-change-container {
will-change: transform;
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.contain-container {
contain: layout;
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.filter-container {
filter: brightness(1);
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
.backdrop-filter-container {
backdrop-filter: blur(0px);
padding: 1rem;
background: #f3f4f6;
border-radius: 8px;
}
/* ============================================
8. MARGIN COLLAPSE / NEGATIVE MARGINS
Elements with collapsed or negative margins
can shift from expected position.
============================================ */
.negative-margin-container {
padding: 2rem;
background: #f3f4f6;
border-radius: 8px;
}
.negative-margin-child {
margin-top: -1rem;
margin-left: -1rem;
}
/* ============================================
Anti-pattern triggers (tiny-text, cramped, ai-color)
used across scenarios.
============================================ */
.tiny { font-size: 10px; color: #374151; line-height: 1.4; }
.cramped-box { border: 1px solid #d1d5db; padding: 3px; font-size: 14px; border-radius: 4px; }
.ai-btn { background: linear-gradient(135deg, #8b5cf6, #6366f1); color: white; padding: 8px 16px; border: none; border-radius: 6px; font-size: 14px; cursor: pointer; }
.side-tab-card { border-left: 4px solid #8b5cf6; padding: 1rem; background: white; border-radius: 0 8px 8px 0; }
</style>
</head>
<body>
<h1>Overlay Positioning Edge Cases</h1>
<p>Each scenario places a detectable anti-pattern inside a layout context that can cause overlay misalignment.</p>
<!-- ═══════════════════════════════════════════
1. TRANSFORM ANCESTORS
═══════════════════════════════════════════ -->
<h2>1. Transform Ancestors</h2>
<div class="scenario">
<div class="scenario-label">1a. translateY(0) container</div>
<div class="transform-container">
<span class="tiny">This tiny text is inside a transform: translateY(0) container. The overlay should frame this text precisely.</span>
</div>
</div>
<div class="scenario">
<div class="scenario-label">1b. rotate(0deg) container</div>
<div class="transform-rotate">
<span class="tiny">Tiny text inside a transform: rotate(0deg) container.</span>
</div>
</div>
<div class="scenario">
<div class="scenario-label">1c. scale(1) container</div>
<div class="transform-scale">
<div class="cramped-box">Cramped text inside a transform: scale(1) container.</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">1d. Nested transforms</div>
<div class="transform-nested-outer">
<div class="transform-nested-inner">
<span class="tiny">Tiny text nested inside two levels of transformed parents.</span>
</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">1e. Transform + absolute child</div>
<div class="transform-container" style="position: relative; height: 100px;">
<div style="position: absolute; bottom: 8px; right: 8px;">
<span class="tiny">Absolutely positioned tiny text inside transformed parent.</span>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════
2. CLOSED <details>
═══════════════════════════════════════════ -->
<h2>2. Closed Details</h2>
<div class="scenario">
<div class="scenario-label">2a. Closed details with anti-pattern inside</div>
<details>
<summary>Click to expand (closed by default)</summary>
<span class="tiny">This tiny text is hidden inside a closed details element. No overlay should appear for it.</span>
<div class="cramped-box">Cramped text also hidden inside closed details.</div>
</details>
</div>
<div class="scenario">
<div class="scenario-label">2b. Open details with anti-pattern inside</div>
<details open>
<summary>This details is open</summary>
<span class="tiny">This tiny text IS visible because details is open. Overlay should frame it correctly.</span>
</details>
</div>
<div class="scenario">
<div class="scenario-label">2c. Nested details (outer open, inner closed)</div>
<details open>
<summary>Outer details (open)</summary>
<p>Some visible content.</p>
<details>
<summary>Inner details (closed)</summary>
<span class="tiny">Hidden tiny text inside nested closed details.</span>
</details>
</details>
</div>
<div class="scenario">
<div class="scenario-label">2d. Transform inside closed details</div>
<details>
<summary>Closed with transform inside</summary>
<div class="transform-container">
<span class="tiny">Tiny text inside transform inside closed details. Double trouble.</span>
</div>
</details>
</div>
<!-- ═══════════════════════════════════════════
3. STICKY / FIXED
═══════════════════════════════════════════ -->
<h2>3. Sticky and Fixed Positioning</h2>
<div class="scenario">
<div class="scenario-label">3a. Sticky header inside scrollable container</div>
<div class="sticky-container">
<div class="sticky-header">
<span class="tiny">Tiny text in a sticky header.</span>
</div>
<div class="sticky-content">
<p>Scroll this container to test sticky behavior.</p>
<p>More content to enable scrolling.</p>
<p>Even more content here.</p>
<div class="cramped-box">Cramped text below the sticky header.</div>
<p>Additional content.</p>
<p>More filler text.</p>
</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">3b. Fixed footer (stays at bottom of viewport)</div>
<p>The fixed footer at the bottom of the page has tiny text. Its overlay should stay fixed too.</p>
</div>
<!-- Actual fixed footer -- outside scenario container so it's truly fixed -->
<div style="position: fixed; bottom: 0; left: 0; right: 0; background: #1f2937; color: white; padding: 0.5rem 1rem; z-index: 50;">
<span class="tiny">Tiny text in a fixed footer -- overlay should track this on scroll.</span>
</div>
<!-- ═══════════════════════════════════════════
4. OVERFLOW HIDDEN
═══════════════════════════════════════════ -->
<h2>4. Overflow Hidden</h2>
<div class="scenario">
<div class="scenario-label">4a. Content clipped by overflow:hidden</div>
<div class="overflow-hidden-container">
<p>This paragraph is visible.</p>
<span class="tiny">This tiny text is below the overflow cutoff, clipped but still in DOM.</span>
<p>This content is also clipped away.</p>
</div>
</div>
<div class="scenario">
<div class="scenario-label">4b. overflow:hidden + transform ancestor</div>
<div class="transform-container">
<div class="overflow-hidden-container">
<p>Visible inside transform + overflow.</p>
<span class="tiny">Clipped tiny text inside a transformed overflow:hidden container.</span>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════
5. ABSOLUTE / RELATIVE OFFSETS
═══════════════════════════════════════════ -->
<h2>5. Position Offsets</h2>
<div class="scenario">
<div class="scenario-label">5a. Relative position with top/left offset</div>
<div class="relative-offset">
<span class="tiny">This tiny text is shifted via position:relative + top/left offset.</span>
</div>
<div style="height: 30px;"></div><!-- spacer for the offset -->
</div>
<div class="scenario">
<div class="scenario-label">5b. Absolute child in relative parent</div>
<div class="absolute-container">
<p>Normal flow content at top.</p>
<div class="absolute-child">
<span class="tiny">Absolutely positioned tiny text at bottom-right.</span>
</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">5c. Negative top offset</div>
<div style="padding-top: 2rem;">
<div style="position: relative; top: -1rem;">
<span class="tiny">Tiny text pulled upward via negative top offset.</span>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════
6. FLEX / GRID CENTERING
═══════════════════════════════════════════ -->
<h2>6. Flex and Grid Centering</h2>
<div class="scenario">
<div class="scenario-label">6a. Flex-centered anti-pattern</div>
<div class="flex-center">
<span class="tiny">Centered tiny text inside a flex container.</span>
</div>
</div>
<div class="scenario">
<div class="scenario-label">6b. Grid-centered anti-pattern</div>
<div class="grid-center">
<div class="cramped-box">Cramped text inside a grid-centered container.</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">6c. Flex with transform</div>
<div class="flex-center transform-container">
<button class="ai-btn">AI Gradient Button in Flex + Transform</button>
</div>
</div>
<!-- ═══════════════════════════════════════════
7. WILL-CHANGE / CONTAIN / FILTER
═══════════════════════════════════════════ -->
<h2>7. Containing Block Creators</h2>
<div class="scenario">
<div class="scenario-label">7a. will-change: transform</div>
<div class="will-change-container">
<span class="tiny">Tiny text inside a will-change: transform container.</span>
</div>
</div>
<div class="scenario">
<div class="scenario-label">7b. contain: layout</div>
<div class="contain-container">
<span class="tiny">Tiny text inside a contain: layout container.</span>
</div>
</div>
<div class="scenario">
<div class="scenario-label">7c. filter: brightness(1)</div>
<div class="filter-container">
<span class="tiny">Tiny text inside a filter container (creates containing block).</span>
</div>
</div>
<div class="scenario">
<div class="scenario-label">7d. backdrop-filter</div>
<div class="backdrop-filter-container">
<span class="tiny">Tiny text inside a backdrop-filter container.</span>
</div>
</div>
<!-- ═══════════════════════════════════════════
8. NEGATIVE MARGINS
═══════════════════════════════════════════ -->
<h2>8. Margin Collapse and Negative Margins</h2>
<div class="scenario">
<div class="scenario-label">8a. Negative margin shifting element position</div>
<div class="negative-margin-container">
<div class="negative-margin-child">
<span class="tiny">Tiny text pulled out of its parent via negative margins.</span>
</div>
</div>
</div>
<!-- ═══════════════════════════════════════════
9. COMBINATION SCENARIOS
═══════════════════════════════════════════ -->
<h2>9. Combined Edge Cases</h2>
<div class="scenario">
<div class="scenario-label">9a. Transform + sticky + overflow</div>
<div class="transform-container">
<div class="sticky-container">
<div class="sticky-header">
<span class="tiny">Tiny text in sticky header inside transform inside scrollable overflow.</span>
</div>
<div class="sticky-content">
<p>Scroll content line 1.</p>
<p>Scroll content line 2.</p>
<p>Scroll content line 3.</p>
<div class="cramped-box">Cramped box scrolled below sticky.</div>
<p>Scroll content line 4.</p>
<p>Scroll content line 5.</p>
</div>
</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">9b. Closed details + transform + flex</div>
<details>
<summary>Closed combo scenario</summary>
<div class="flex-center transform-container">
<span class="tiny">Triple-nested: closed details > flex > transform > tiny text.</span>
</div>
</details>
</div>
<div class="scenario">
<div class="scenario-label">9c. Grid + absolute + transform</div>
<div class="grid-center" style="position: relative; height: 150px;">
<div class="transform-container" style="position: absolute; top: 10px; left: 10px; right: 10px;">
<span class="tiny">Absolute inside grid inside transform: overlays must track all three contexts.</span>
</div>
<div style="position: absolute; bottom: 10px; right: 10px;">
<div class="cramped-box">Absolute cramped box at bottom-right of grid.</div>
</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">9d. Overflow hidden + absolute breakout</div>
<div style="position: relative; overflow: hidden; height: 80px; background: #f3f4f6; border-radius: 8px; padding: 1rem;">
<p>Visible content.</p>
<div style="position: absolute; bottom: -20px; left: 0; right: 0;">
<span class="tiny">Absolute-positioned tiny text that breaks out below overflow:hidden.</span>
</div>
</div>
</div>
<div class="scenario">
<div class="scenario-label">9e. Side-tab card inside transform + relative offset</div>
<div class="transform-container">
<div style="position: relative; left: 50px;">
<div class="side-tab-card">
This card has a side-tab border accent and is offset inside a transform container.
</div>
</div>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-11
View File
@@ -1,11 +0,0 @@
<!-- This is a partial/component — no DOCTYPE, no <html>, no <head> -->
<!-- Page-level checks (flat hierarchy, single font) should NOT run -->
<!-- But element-level border checks SHOULD still run -->
<div class="card" style="font-family: 'Inter', sans-serif;">
<div style="font-size: 14px; border-left: 4px solid #3b82f6; border-radius: 8px; padding: 1rem; background: white;">
<h3 style="font-size: 16px; font-weight: 600;">Card Title</h3>
<p style="font-size: 15px; color: #6b7280;">Card description with close font sizes.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
-340
View File
@@ -1,340 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>General Design Quality Issues That Should Be Flagged</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #f9fafb;
padding: 2rem;
/* No max-width on body - text will run edge to edge on wide screens */
}
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
.demo { margin-bottom: 1rem; }
.demo-label { font-size: 0.75rem; color: #9ca3af; margin-bottom: 0.25rem; }
/* ============================================
1. LINE LENGTH TOO LONG
Text that runs wider than ~75ch is hard to read.
============================================ */
.long-lines {
font-size: 16px;
/* No max-width - on a wide screen this paragraph will be 150+ chars per line */
}
/* ============================================
2. CRAMPED PADDING
Text crammed against the edge of a container.
============================================ */
.cramped-border {
border: 1px solid #d1d5db;
padding: 4px;
border-radius: 4px;
font-size: 14px;
}
.cramped-bg {
background: #e5e7eb;
padding: 2px 4px;
border-radius: 4px;
font-size: 14px;
}
.cramped-zero {
border: 2px solid #3b82f6;
padding: 0;
font-size: 14px;
}
/* ============================================
3. TINY BODY TEXT
Text below 12px is hard to read for body content.
============================================ */
.tiny-text {
font-size: 10px;
color: #374151;
line-height: 1.5;
}
.tiny-text-11 {
font-size: 11px;
color: #374151;
}
/* ============================================
4. TIGHT LINE HEIGHT
Line-height below 1.3 for body text makes reading difficult.
============================================ */
.tight-leading {
font-size: 16px;
line-height: 1.0;
max-width: 40ch;
}
.tight-leading-px {
font-size: 16px;
line-height: 16px; /* Same as font-size = ratio of 1.0 */
max-width: 40ch;
}
/* ============================================
5. JUSTIFIED TEXT
Creates uneven word spacing ("rivers of white").
============================================ */
.justified {
text-align: justify;
max-width: 40ch;
font-size: 14px;
}
/* ============================================
6. MISSING FOCUS STYLES
Removing outline without providing an alternative.
============================================ */
.no-focus:focus {
outline: none;
}
.no-focus-zero:focus {
outline: 0;
}
/* ============================================
7. SMALL TOUCH TARGETS
Interactive elements smaller than 44x44px.
============================================ */
.tiny-button {
padding: 2px 6px;
font-size: 11px;
border: 1px solid #d1d5db;
border-radius: 3px;
background: white;
cursor: pointer;
}
.tiny-link {
font-size: 11px;
display: inline-block;
padding: 0;
}
.tiny-icon-button {
width: 20px;
height: 20px;
border: 1px solid #d1d5db;
border-radius: 3px;
background: white;
cursor: pointer;
font-size: 10px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* ============================================
8. SKIPPED HEADING LEVELS
h1 -> h3 (missing h2), h3 -> h5 (missing h4)
============================================ */
/* ============================================
9. Z-INDEX ABUSE
Absurdly high z-index values.
============================================ */
.z-index-war {
position: relative;
z-index: 99999;
background: white;
padding: 1rem;
border: 1px solid #e5e7eb;
border-radius: 4px;
}
.z-index-war-extreme {
position: relative;
z-index: 2147483647;
background: white;
padding: 1rem;
border: 1px solid #e5e7eb;
border-radius: 4px;
}
/* ============================================
10. VIEWPORT-DEPENDENT FIXED WIDTHS
Hard-coded pixel widths that will break on smaller screens.
============================================ */
.fixed-width {
width: 800px;
background: #e5e7eb;
padding: 1rem;
border-radius: 4px;
}
/* ============================================
11. ALL-CAPS BODY TEXT
Long passages in uppercase are hard to read.
============================================ */
.all-caps-body {
text-transform: uppercase;
font-size: 14px;
max-width: 40ch;
line-height: 1.5;
}
/* ============================================
12. IMPORTANT OVERUSE
============================================ */
.important-chain {
color: red !important;
font-size: 16px !important;
margin: 0 !important;
padding: 8px !important;
background: yellow !important;
}
/* ============================================
13. INCONSISTENT BORDER RADIUS
Different radii on sibling elements that should match.
============================================ */
.radius-soup {
display: flex;
gap: 8px;
}
.radius-soup > div {
padding: 12px 16px;
background: white;
border: 1px solid #e5e7eb;
font-size: 14px;
}
.radius-2 { border-radius: 2px; }
.radius-8 { border-radius: 8px; }
.radius-16 { border-radius: 16px; }
.radius-full { border-radius: 999px; }
/* ============================================
14. LETTER SPACING ON BODY TEXT
Tracking on body text hurts readability.
============================================ */
.wide-tracking {
letter-spacing: 0.15em;
font-size: 14px;
max-width: 40ch;
line-height: 1.5;
}
/* ============================================
15. DEEP NESTING / WRAPPER DIVS
Excessive DOM depth for no structural reason.
============================================ */
</style>
</head>
<body>
<h1>Design Quality Issues: Should Flag</h1>
<p class="demo-label">Every example on this page has a common design quality problem.</p>
<!-- 1. Long lines -->
<h2>1. Line Length Too Long</h2>
<div class="demo">
<p class="long-lines">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.</p>
</div>
<!-- 2. Cramped padding -->
<h2>2. Cramped Padding</h2>
<div class="demo">
<div class="cramped-border">This text is crammed against the border with only 4px padding. It feels claustrophobic and hard to read.</div>
</div>
<div class="demo">
<div class="cramped-bg">Cramped background padding</div>
</div>
<div class="demo">
<div class="cramped-zero">Zero padding on a bordered element. The text is literally touching the border.</div>
</div>
<!-- 3. Tiny text -->
<h2>3. Tiny Body Text</h2>
<div class="demo">
<p class="tiny-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.</p>
</div>
<div class="demo">
<p class="tiny-text-11">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.</p>
</div>
<!-- 4. Tight line height -->
<h2>4. Tight Line Height</h2>
<div class="demo">
<p class="tight-leading">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.</p>
</div>
<div class="demo">
<p class="tight-leading-px">This paragraph uses line-height: 16px with font-size: 16px, giving an effective ratio of 1.0. Same problem expressed differently.</p>
</div>
<!-- 5. Justified text -->
<h2>5. Justified Text</h2>
<div class="demo">
<p class="justified">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.</p>
</div>
<!-- 6. Missing focus styles -->
<h2>6. Missing Focus Styles</h2>
<div class="demo">
<button class="no-focus">outline: none (try tabbing)</button>
<button class="no-focus-zero">outline: 0 (try tabbing)</button>
</div>
<!-- 7. Small touch targets -->
<h2>7. Small Touch Targets</h2>
<div class="demo" style="display: flex; gap: 8px; align-items: center;">
<button class="tiny-button">Tiny</button>
<a href="#" class="tiny-link">Small link</a>
<button class="tiny-icon-button">x</button>
<button class="tiny-icon-button">+</button>
</div>
<!-- 8. Skipped heading levels -->
<h2>8. Skipped Heading Levels</h2>
<div class="demo">
<h3>This H3 follows the H2 above (OK)</h3>
<h5>But this H5 skips H4 entirely (bad for accessibility and document structure)</h5>
<p>Screen readers use heading levels to build a document outline. Skipping levels breaks that navigation.</p>
</div>
<!-- 9. Z-index abuse -->
<h2>9. Z-Index Abuse</h2>
<div class="demo">
<div class="z-index-war">z-index: 99999 (why?)</div>
<div class="z-index-war-extreme">z-index: 2147483647 (the maximum 32-bit integer)</div>
</div>
<!-- 10. Fixed pixel widths -->
<h2>10. Fixed Pixel Widths</h2>
<div class="demo">
<div class="fixed-width">This element has width: 800px. On any screen narrower than 800px, it will overflow and cause horizontal scrolling.</div>
</div>
<!-- 11. All-caps body text -->
<h2>11. All-Caps Body Text</h2>
<div class="demo">
<p class="all-caps-body">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.</p>
</div>
<!-- 12. !important overuse -->
<h2>12. !important Overuse</h2>
<div class="demo">
<p class="important-chain">This element has 5 !important declarations. It's a sign of specificity wars and unmaintainable CSS.</p>
</div>
<!-- 13. Inconsistent border radius -->
<h2>13. Inconsistent Border Radius</h2>
<div class="demo">
<div class="radius-soup">
<div class="radius-2">2px radius</div>
<div class="radius-8">8px radius</div>
<div class="radius-16">16px radius</div>
<div class="radius-full">pill</div>
</div>
</div>
<!-- 14. Letter spacing on body text -->
<h2>14. Wide Letter Spacing</h2>
<div class="demo">
<p class="wide-tracking">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.</p>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-183
View File
@@ -1,183 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Good Design Quality Patterns That Should Pass</title>
<style>
body {
font-family: system-ui, sans-serif;
background: #f9fafb;
padding: 2rem;
max-width: 40rem;
}
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.5rem; margin: 2rem 0 0.75rem; }
h3 { font-size: 1.25rem; margin: 1.5rem 0 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; font-size: 0.875rem; }
.demo { margin-bottom: 1rem; }
/* Good line length */
.good-measure {
max-width: 65ch;
font-size: 16px;
line-height: 1.6;
}
/* Good padding */
.good-padding {
border: 1px solid #d1d5db;
padding: 16px;
border-radius: 8px;
font-size: 14px;
}
/* Good text size */
.good-text {
font-size: 16px;
line-height: 1.6;
max-width: 65ch;
}
/* Small text that's OK (labels, captions) */
.caption {
font-size: 12px;
color: #6b7280;
}
/* Good touch targets */
.good-button {
padding: 12px 24px;
font-size: 14px;
border: 1px solid #d1d5db;
border-radius: 8px;
background: white;
cursor: pointer;
min-height: 44px;
}
/* All-caps that's OK (short label) */
.label-caps {
text-transform: uppercase;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.1em;
color: #6b7280;
}
/* Focus with custom style (outline: none is OK here) */
.custom-focus:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
}
/* Justified text with hyphens (OK) */
.justified-with-hyphens {
text-align: justify;
hyphens: auto;
max-width: 40ch;
font-size: 14px;
}
/* Consistent border radius */
.consistent-radius {
display: flex;
gap: 8px;
}
.consistent-radius > div {
padding: 12px 16px;
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 14px;
}
/* Reasonable z-index */
.reasonable-z {
position: relative;
z-index: 10;
background: white;
padding: 1rem;
border: 1px solid #e5e7eb;
border-radius: 4px;
}
/* Responsive width */
.responsive-width {
max-width: 800px;
width: 100%;
background: #e5e7eb;
padding: 1rem;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>Design Quality: Should Pass</h1>
<p class="intro">None of these should trigger quality warnings.</p>
<h2>Good Line Length</h2>
<div class="demo">
<p class="good-measure">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.</p>
</div>
<h2>Short Text in Wide Container (OK)</h2>
<div class="demo">
<p style="max-width: none; width: 100%;">This is a short sentence in a wide container.</p>
<p style="max-width: none; width: 100%;">Just a few words here.</p>
</div>
<h2>Good Padding</h2>
<div class="demo">
<div class="good-padding">This container has 16px padding, giving the text room to breathe within its border.</div>
</div>
<h2>Good Text Sizes</h2>
<div class="demo">
<p class="good-text">This is 16px body text with 1.6 line-height. Comfortable to read.</p>
<p class="caption">This is a 12px caption. Small but appropriate for its purpose.</p>
</div>
<h2>Good Touch Targets</h2>
<div class="demo">
<button class="good-button">Properly Sized Button</button>
</div>
<h2>Proper Heading Hierarchy</h2>
<div class="demo">
<h3>This H3 follows H2 correctly</h3>
<p>No skipped levels.</p>
</div>
<h2>Short Labels in Caps (OK)</h2>
<div class="demo">
<span class="label-caps">Category Label</span>
</div>
<h2>Custom Focus Style</h2>
<div class="demo">
<button class="custom-focus good-button">Custom focus ring</button>
</div>
<h2>Consistent Border Radius</h2>
<div class="demo">
<div class="consistent-radius">
<div>Card A</div>
<div>Card B</div>
<div>Card C</div>
</div>
</div>
<h2>Reasonable Z-Index</h2>
<div class="demo">
<div class="reasonable-z">z-index: 10</div>
</div>
<h2>Responsive Width</h2>
<div class="demo">
<div class="responsive-width">max-width: 800px, width: 100%. Adapts to any screen.</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-136
View File
@@ -1,136 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Anti-Patterns That Should Be Flagged</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; }
.cards { display: grid; gap: 1rem; max-width: 28rem; }
.card { background: white; padding: 1rem; border-radius: 0.375rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.card h3 { font-weight: 600; color: #111827; font-size: 0.875rem; }
.card p { font-size: 0.8125rem; color: #6b7280; margin-top: 0.25rem; }
/* CSS side-tab variants */
.card-shorthand-left { border-left: 4px solid #3b82f6; }
.card-shorthand-right { border-right: 5px solid #8b5cf6; }
.card-longhand-left { border-left-width: 3px; border-left-style: solid; border-left-color: #10b981; }
.card-longhand-right { border-right-width: 6px; border-right-style: solid; border-right-color: #ef4444; }
.card-logical-start { border-inline-start: 4px solid #f59e0b; }
.card-logical-end { border-inline-end: 3px solid #ec4899; }
.card-logical-start-width { border-inline-start-width: 5px; border-inline-start-style: solid; border-inline-start-color: #06b6d4; }
/* CSS top/bottom + border-radius */
.card-css-top { border-radius: 12px; border-top: 4px solid #3b82f6; }
.card-css-bottom { border-radius: 12px; border-bottom: 3px solid #8b5cf6; }
</style>
</head>
<body>
<h1>Anti-Patterns: Should Flag</h1>
<p class="intro">Every example on this page should be detected by the anti-pattern scanner.</p>
<!-- TAILWIND SIDE-TAB -->
<h2>Tailwind Side-Tab</h2>
<div class="cards">
<div class="border-l-4 border-blue-500 bg-white p-4 rounded-r shadow-sm">
<h3>border-l-4 + rounded-r</h3>
<p>The classic AI tell.</p>
</div>
<div class="border-l-2 border-emerald-500 bg-white p-4 rounded-r shadow-sm">
<h3>border-l-2 + rounded-r</h3>
<p>Thin but still recognizable with rounded corners.</p>
</div>
<div class="border-r-4 border-purple-500 bg-white p-4 rounded-l shadow-sm">
<h3>border-r-4 + rounded-l</h3>
<p>Right side variant.</p>
</div>
<div class="border-s-3 border-amber-500 bg-white p-4 rounded-r shadow-sm">
<h3>border-s-3 + rounded-r</h3>
<p>Logical inline-start.</p>
</div>
<div class="border-e-8 border-red-500 bg-white p-4 rounded-l shadow-sm">
<h3>border-e-8 + rounded-l</h3>
<p>Logical inline-end, extra thick.</p>
</div>
</div>
<!-- CSS SIDE-TAB -->
<h2>CSS Side-Tab</h2>
<div class="cards">
<div class="card card-shorthand-left">
<h3>border-left: 4px solid</h3>
<p>CSS shorthand.</p>
</div>
<div class="card card-shorthand-right">
<h3>border-right: 5px solid</h3>
<p>CSS shorthand right.</p>
</div>
<div class="card card-longhand-left">
<h3>border-left-width: 3px</h3>
<p>CSS longhand.</p>
</div>
<div class="card card-longhand-right">
<h3>border-right-width: 6px</h3>
<p>CSS longhand right.</p>
</div>
<div class="card card-logical-start">
<h3>border-inline-start: 4px solid</h3>
<p>CSS logical start.</p>
</div>
<div class="card card-logical-end">
<h3>border-inline-end: 3px solid</h3>
<p>CSS logical end.</p>
</div>
<div class="card card-logical-start-width">
<h3>border-inline-start-width: 5px</h3>
<p>CSS logical longhand.</p>
</div>
</div>
<!-- TOP/BOTTOM + ROUNDED -->
<h2>Top/Bottom + Rounded</h2>
<div class="cards">
<div class="border-t-4 border-blue-500 rounded-lg bg-white p-4 shadow-sm">
<h3 class="font-semibold text-sm">border-t-4 + rounded-lg</h3>
<p class="text-sm text-gray-600">Top accent on rounded card.</p>
</div>
<div class="border-b-4 border-purple-500 rounded-xl bg-white p-4 shadow-sm">
<h3 class="font-semibold text-sm">border-b-4 + rounded-xl</h3>
<p class="text-sm text-gray-600">Bottom accent on rounded card.</p>
</div>
<div class="border-t-2 border-emerald-500 rounded-md bg-white p-4 shadow-sm">
<h3 class="font-semibold text-sm">border-t-2 + rounded-md</h3>
<p class="text-sm text-gray-600">Even thin top border on rounded.</p>
</div>
<div class="card card-css-top">
<h3>CSS border-top + border-radius</h3>
<p>Top border from style block.</p>
</div>
<div class="card card-css-bottom">
<h3>CSS border-bottom + border-radius</h3>
<p>Bottom border from style block.</p>
</div>
</div>
<!-- DARK MODE VARIANTS -->
<h2>Dark Mode</h2>
<div class="cards">
<div class="border-l-4 border-cyan-400 rounded-lg bg-gray-800 p-4 shadow-sm">
<h3 class="font-semibold text-sm text-white">Dark card + border-l-4 + rounded</h3>
<p class="text-sm text-gray-400">Dark background doesn't make the side-tab OK.</p>
</div>
<div class="border-t-2 border-violet-500 rounded-xl bg-slate-900 p-4 shadow-sm">
<h3 class="font-semibold text-sm text-white">Dark card + border-t-2 + rounded</h3>
<p class="text-sm text-gray-400">Top accent on dark rounded card.</p>
</div>
<div style="background: #1f2937; padding: 1rem; border-radius: 12px; border-left: 4px solid #f472b6; max-width: 28rem;">
<h3 style="font-weight: 600; font-size: 0.875rem; color: white;">Dark CSS card + side border + radius</h3>
<p style="font-size: 0.8125rem; color: #9ca3af;">Inline dark card with side-tab.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-84
View File
@@ -1,84 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Clean Patterns — Should NOT Flag</title>
<style>
body { font-family: system-ui, sans-serif; background: #f9fafb; padding: 2rem; }
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
h2 { font-size: 1.125rem; margin: 2.5rem 0 0.75rem; color: #6b7280; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.5rem; }
p.intro { color: #6b7280; margin-bottom: 2rem; max-width: 36rem; }
.section { max-width: 28rem; }
.card-rounded { background: white; padding: 1.5rem; border-radius: 0.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); max-width: 28rem; margin-bottom: 1rem; }
.card-rounded h3 { font-weight: 600; color: #111827; }
.card-rounded p { font-size: 0.875rem; color: #6b7280; margin-top: 0.25rem; }
.card-flat { background: white; padding: 1rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); max-width: 28rem; margin-bottom: 1rem; }
.badge { display: inline-block; font-size: 0.75rem; padding: 0.125rem 0.5rem; border-radius: 9999px; background: #dbeafe; color: #1d4ed8; }
/* Below-threshold side borders without radius */
.thin-left { border-left: 1px solid #3b82f6; }
.thin-right { border-right: 2px solid #8b5cf6; }
</style>
</head>
<body>
<h1>Clean Patterns: Should Pass</h1>
<p class="intro">None of these should be flagged. They're all intentional, well-established web patterns.</p>
<!-- CLEAN CARDS -->
<h2>Clean Cards</h2>
<div class="section">
<div class="card-rounded" style="border: 1px solid #e5e7eb;">
<h3>Full border card</h3>
<p>Subtle 1px border all around. Clean and intentional.</p>
</div>
<div style="max-width: 28rem; margin-bottom: 1rem; padding: 1.5rem; background: white; border-top: 3px solid #3b82f6; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="font-weight: 600; color: #111827;">Top border, no radius</h3>
<p style="font-size: 0.875rem; color: #6b7280; margin-top: 0.25rem;">Top accent without rounded corners is a clean section divider.</p>
</div>
<div style="max-width: 28rem; margin-bottom: 1rem; padding: 1.5rem; background: white; border-bottom: 2px solid #10b981; box-shadow: 0 1px 3px rgba(0,0,0,0.1);">
<h3 style="font-weight: 600; color: #111827;">Bottom border, no radius</h3>
<p style="font-size: 0.875rem; color: #6b7280; margin-top: 0.25rem;">Bottom accent without rounded corners is also clean.</p>
</div>
<div class="card-rounded">
<span class="badge">New</span>
<h3>No border at all</h3>
<p>Just a shadow. Simple and effective.</p>
</div>
</div>
<!-- BELOW THRESHOLD -->
<h2>Below Threshold (Thin, No Radius)</h2>
<div class="section">
<div class="card-flat thin-left">
<p>border-left: 1px solid, no radius — not flagged</p>
</div>
<div class="card-flat thin-right">
<p>border-right: 2px solid, no radius — not flagged</p>
</div>
<div class="card-flat" style="border-left: 1px solid #10b981;">
<p>1px inline border-left, no radius — not flagged</p>
</div>
</div>
<!-- DARK MODE CLEAN -->
<h2>Dark Mode (Clean)</h2>
<div class="section">
<div style="background: #1f2937; padding: 1.5rem; border-radius: 0.5rem; border: 1px solid #374151; max-width: 28rem; margin-bottom: 1rem;">
<h3 style="font-weight: 600; color: white;">Dark card, full border</h3>
<p style="font-size: 0.875rem; color: #9ca3af; margin-top: 0.25rem;">Uniform 1px border all around. Clean.</p>
</div>
<div style="background: #111827; padding: 1.5rem; max-width: 28rem; margin-bottom: 1rem; border-bottom: 2px solid #6366f1;">
<h3 style="font-weight: 600; color: white;">Dark section, bottom border, no radius</h3>
<p style="font-size: 0.875rem; color: #9ca3af; margin-top: 0.25rem;">Bottom accent without radius is fine.</p>
</div>
<div style="background: #1e293b; padding: 1.5rem; border-radius: 0.5rem; max-width: 28rem; margin-bottom: 1rem; box-shadow: 0 4px 6px rgba(0,0,0,0.3);">
<h3 style="font-weight: 600; color: white;">Dark card, no border</h3>
<p style="font-size: 0.875rem; color: #94a3b8; margin-top: 0.25rem;">Shadow only. Clean.</p>
</div>
</div>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-33
View File
@@ -1,33 +0,0 @@
<script>
export let title = '';
export let items = [];
</script>
<div class="sidebar border-r-4 border-violet-500 rounded-lg">
<h3 class="text-purple-500 text-3xl font-bold">{title}</h3>
<ul>
{#each items as item}
<li class="animate-bounce">{item.name}</li>
{/each}
</ul>
</div>
<style>
.sidebar {
border-right: 4px solid #8b5cf6;
border-radius: 16px;
font-family: 'Roboto', sans-serif;
background: #000000;
}
.sidebar h3 {
background: linear-gradient(135deg, #a855f7, #06b6d4);
-webkit-background-clip: text;
background-clip: text;
}
.animated-item {
animation: elastic 0.6s ease-out;
transition: height 0.4s ease;
}
</style>
-20
View File
@@ -1,20 +0,0 @@
<script>
export let title = '';
export let items = [];
</script>
<div class="sidebar rounded-lg ring-1 ring-gray-200 p-4">
<h3 class="text-lg font-semibold text-gray-900">{title}</h3>
<ul>
{#each items as item}
<li class="py-2">{item.name}</li>
{/each}
</ul>
</div>
<style>
.sidebar {
font-family: 'Geist', system-ui, sans-serif;
background: #fafafa;
}
</style>
-39
View File
@@ -1,39 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typography Anti-Patterns — Should Flag</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background: #f9fafb;
padding: 2rem;
}
h1 { font-size: 18px; font-weight: 700; margin-bottom: 0.5rem; }
h2 { font-size: 16px; font-weight: 600; margin: 2rem 0 0.75rem; color: #6b7280; }
h3 { font-size: 15px; font-weight: 600; }
p { font-size: 14px; color: #6b7280; margin-top: 0.25rem; }
.caption { font-size: 13px; color: #9ca3af; }
</style>
</head>
<body>
<h1>Typography Anti-Patterns</h1>
<p>This page triggers three typography detections:</p>
<h2>1. Overused Font</h2>
<p>Inter is loaded from Google Fonts and set as the only font-family. It's the most common AI default.</p>
<h2>2. Single Font</h2>
<p>There's no second font for headings or display text. Everything uses Inter — no typographic variety.</p>
<h2>3. Flat Type Hierarchy</h2>
<p>The font sizes are 13px, 14px, 15px, 16px, 18px — all crammed into a 5px range. No visual contrast between heading and body.</p>
<p class="caption">This caption is barely distinguishable from body text.</p>
<h3>A Subheading</h3>
<p>Can you tell this is a subheading? Exactly.</p>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-45
View File
@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Typography — Clean Patterns</title>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Sans:wght@400;500;600&family=Fraunces:opsz,wght@9..144,400;9..144,700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Instrument Sans', system-ui, sans-serif;
background: #f9fafb;
padding: 2rem;
font-size: 16px;
color: #111827;
}
h1 {
font-family: 'Fraunces', Georgia, serif;
font-size: 48px;
font-weight: 700;
margin-bottom: 0.5rem;
}
h2 {
font-family: 'Fraunces', Georgia, serif;
font-size: 32px;
font-weight: 700;
margin: 2rem 0 0.75rem;
}
h3 { font-size: 24px; font-weight: 600; }
p { font-size: 16px; color: #6b7280; margin-top: 0.25rem; }
.caption { font-size: 12px; color: #6b7280; }
</style>
</head>
<body>
<h1>Good Typography</h1>
<p>This page uses distinctive fonts, proper pairing, and strong hierarchy.</p>
<h2>Two Font Families</h2>
<p>Fraunces (serif) for headings, Instrument Sans for body. Clear contrast in both structure and personality.</p>
<h3>Strong Size Hierarchy</h3>
<p>Sizes range from 12px to 48px — a 4:1 ratio with clear visual steps.</p>
<p class="caption">Caption text is clearly distinct from body.</p>
<script src="/js/detect-antipatterns-browser.js"></script>
</body>
</html>
-36
View File
@@ -1,36 +0,0 @@
<template>
<div class="card border-l-4 border-indigo-500 rounded-xl p-6">
<h2 class="text-purple-600 text-2xl font-bold">{{ title }}</h2>
<p class="text-gray-400">{{ description }}</p>
<button class="animate-bounce mt-4 px-4 py-2 bg-indigo-500 text-white rounded">
Get Started
</button>
</div>
</template>
<script setup>
defineProps({
title: String,
description: String,
});
</script>
<style scoped>
.card {
border-left: 4px solid #6366f1;
border-radius: 12px;
font-family: 'Inter', sans-serif;
}
.card h2 {
background: linear-gradient(to right, #a855f7, #06b6d4);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.highlight {
animation: bounce 1s infinite;
transition: width 0.3s ease;
}
</style>
-23
View File
@@ -1,23 +0,0 @@
<template>
<div class="card rounded-xl p-6 ring-1 ring-gray-200">
<h2 class="text-lg font-semibold text-gray-900">{{ title }}</h2>
<p class="text-gray-600 mt-2">{{ description }}</p>
<button class="mt-4 px-4 py-2 bg-teal-600 text-white rounded-md">
Get Started
</button>
</div>
</template>
<script setup>
defineProps({
title: String,
description: String,
});
</script>
<style scoped>
.card {
font-family: 'Geist', system-ui, sans-serif;
background: #fff;
}
</style>