A tool to score any website for common AI design patterns. Live: www.slopcop.adriankrebs.ch — paste a URL, get a scored verdict. Basis for the blog post and the HN discussion "Scoring Show HN submissions for AI design patterns".
The tool loads each site in a headless browser, analyzes the DOM, and reports which of 16 deterministic AI design patterns are found. Manual verification across ~150 labeled sites suggests ~5–10% false positives. Still, take it with a grain of salt :)
Install
git clone https://github.com/AdrianKrebs/design-slop-cop cd design-slop-cop && npm install
Requires Node 18+.
Check a single URL
A clean site (no AI design patterns triggered):
$ node check.js https://news.ycombinator.com/ https://news.ycombinator.com/ Low slop · score 0/100 · 0/16 patterns
A heavy one (7 patterns triggered):
$ node check.js https://engagemii.com/aeo https://engagemii.com/aeo High slop · score 44/100 · 7/16 patterns Triggered: • Vibe purple • Gradients • Perma dark • 1·2·3 steps • Stat banner • Headline badge • FAQ
Add --json for machine-readable output:
$ node check.js https://engagemii.com/aeo --json
{
"url": "https://engagemii.com/aeo",
"score": 44,
"tierLabel": "Heavy",
"patternsFlagged": 7,
"patternsTotal": 16,
"patterns": [
{ "id": "purple_accent", "label": "Vibe purple", "triggered": true, "evidence": {...} },
...
]
}To update an existing clone, git pull inside the directory.
Run the web frontend
The same UI as the live site at www.slopcop.adriankrebs.ch — paste a URL, get a scored verdict and screenshot:
npm start # → http://localhost:8080The first scan is slow (it warms up a headless browser); subsequent scans reuse it. /show serves the browsable Show HN gallery. To deploy it publicly, see DEPLOY.md.
Run the full corpus
For batch analysis (e.g. the bundled urls.txt with ~1000 Show HN posts) — same install as above, then:
npm run analyze # sequential, ~100 min for 1k URLs node src/run.js --concurrency=4 # 4 parallel, ~25 min, ~600 MB RAM node src/run.js --skip-existing # only fetch URLs not yet cached
Results go to results/:
results/raw/<slug>.json— per-URL signals + scoreresults/screenshots/<slug>.png— full-page screenshotresults/all-results.json— all scored entries combined
Patterns
| # | Pattern | The tell |
|---|---|---|
| 1 | Templated display fonts | Space Grotesk, Instrument Serif, Geist, Syne, or Fraunces used as the page default |
| 2 | Hero font mix | One hero word set apart with a second font, an italic, or a different color |
| 3 | Vibe purple | Indigo/violet accent on CTAs and links |
| 4 | Gradients | Gradient backgrounds, or gradient-clipped hero text |
| 5 | Accent stripe | Colored stripe on a card's top or left edge |
| 6 | Glassmorphism | Backdrop-blur on translucent floating panels |
| 7 | Colored glow | Saturated box-shadow glow on buttons and cards |
| 8 | Emoji nav | Nav or sidebar items prefixed with emoji |
| 9 | Centered + Inter | Centered hero set in Inter or a generic sans |
| 10 | All-caps headings | Section labels and nav set in caps |
| 11 | Perma dark | Dark background with muted grey body text |
| 12 | Icon cards | A row of identical icon + title + blurb cards |
| 13 | Numbered steps | A "1 · 2 · 3" step sequence |
| 14 | Stat banner | "10K+ users · 99.9% uptime · 4.9★" stat row |
| 15 | Headline badge | A pill badge floating above the H1 |
| 16 | FAQ accordion | "Frequently asked questions" with 3+ collapsible Q&As |
The full rule for each pattern lives in src/patterns/<id>.js.
Score
score = round(100 × patternsFlagged / patternsTotal)
slop level = ≥4 High · 2–3 Medium · 0–1 Low
Tools
npm run scan # → http://localhost:7788 minimal dev scanner (see "Run the web frontend" for the full UI) npm run label # → http://localhost:7777 label sites for ground truth npm run eval # precision / recall vs dataset/labels.jsonl (165 labels shipped) npm run report # generate results/index.html — browsable tier-filtered grid npm run fetch # pull the latest 100 Show HN URLs into urls.txt
The scan UI launches a real browser per URL, so the first scan after starting the server is slower. The label UI lets you mark each pattern present / not_present / skip; saves are appended to dataset/labels.jsonl. The eval script compares the detector's verdict against those labels.
Adding a pattern
Spotted a tell we miss? Each pattern is one self-contained file. To add one:
- Copy the template:
cp src/patterns/_template.js src/patterns/<your-id>.js. It documents the full export shape, everything available inctx, and the one gotcha (below). - Fill in the exports:
{ id, label, shortLabel, description, category, thresholds, extract(ctx), score(signal, T) }.extract(ctx)runs in the browser (it's serialized withFunction.prototype.toString()), so keep it self-contained: reference onlyctx.*— no imports, closures, or outer variables.score(signal, T)runs in Node and returns{ triggered: true|false, evidence: ... }.
- Register it: import the file in
src/patterns/index.jsand append it to thePATTERNSarray (array order = display order). - Debug it:
node check.js <a-site-that-should-trigger-it> --pattern=<your-id>prints the raw signal yourextractreturned and the verdict yourscorereturned. Confirm it fires there and stays quiet on clean sites. - Check accuracy:
npm run evalto make sure precision/recall didn't regress, then open a PR.
License
MIT.