Inspiration

What it does

How we built it

Challenges we ran into

Accomplishments that we're proud of

What we learned

What's next for IS YOUR WEB AI SLOP

Inspiration

I went looking for a straight answer to a practical question and read six pages that said the same thing in the same shape. Same opener about the fast-paced world. Same three bullets. Same closing summary that summarized nothing. None of them was individually outrageous — that was the point. The problem wasn't one bad article, it was the sameness.

Every AI-detector I tried judged one block of text and returned a confident percentage it had no business being confident about. Two things felt wrong: the unit of analysis, and the certainty. A site is the thing you decide to trust or not, and no tool can actually prove who wrote a sentence.

So: audit the site, and be honest about what the number means.

What it does

You give it a domain. It pulls pages from the site's own sitemap — sampled across the whole archive, not the newest few — reads them, scores each one, and then does the part that matters: looks at what the pages share.

Per page it flags stock phrasing, sentences that all run one length, paragraph uniformity, bullet density, and the absence of anything checkable. Across pages it flags the things a single article can never reveal:

  • phrases repeating verbatim in 60% or more of the sampled articles
  • every article landing at the same word count
  • page scores clustering too tightly — one mould, used repeatedly
  • no byline on a single page
  • posts spread across the archive that all published within days of each other
  • headlines built to one formula

Signals run both ways. Numbers, names and quotations lower the score, because concrete detail is expensive to fake. So does the texture of someone actually talking — "turns out", "I was wrong", "took me three hours".

How I built it

Static HTML plus two serverless functions on Vercel. No API key, no LLM, no database, no build step.

/index.html      UI + the entire scoring engine, runs in the browser
/api/pages.js    sitemap discovery
/api/read.js     fetch one page, return clean text

api/pages.js tries /sitemap.xml, then the usual variants, follows a <sitemapindex> into its child sitemaps, and falls back to parsing homepage links. api/read.js strips scripts, nav, header and footer, prefers <article> or <main> when the markup says where the body is, and pulls the byline and publish date out of meta tags and JSON-LD.

Scoring happens entirely client-side. No text is stored anywhere.

Sentence uniformity is the coefficient of variation of sentence lengths:

$$CV = \frac{\sigma}{\mu}$$

Human prose is bursty — a twenty-eight word sentence, then four words. Generated prose regresses to the mean. Below \(CV < 0.48\) the flag fires, scaled by how far below.

Specificity density counts what a text commits to, per hundred words:

$$D = \frac{n_{\text{num}} + 2n_{\text{year}} + 0.6\,n_{\text{name}} + 3\,n_{\text{quote}}}{N} \times 100$$

Below \(D < 2.2\) adds points. Above \(D > 5\) subtracts them.

The site score is the mean of the page scores plus the cross-page adjustments:

$$S = \overline{s} + \sum_{i} a_i, \qquad S \in [0, 100]$$

Challenges I ran into

A browser can't read other people's websites. CORS kills the naive version immediately. That single constraint produced the whole architecture: the functions exist to fetch, the browser exists to judge.

Resisting the LLM. The obvious build calls a model and asks "is this AI slop?". I got that version working and then deleted it. Free tiers have expiry dates nobody announces — GitHub Models was retired in July 2026, Google cut free quotas 50–80% without notice. A tool that depends on someone's free tier is a tool with a countdown on it. Cutting the model out made the project better, not worse: the site-level signals are now computed from the actual text instead of being a model's impression of it, and they're reproducible.

Sampling bias. My first version took the newest N posts. Those often ship as one batch, which made every site look like a content farm. Sampling at even intervals across the full sitemap fixed it — and turned publish-date clustering into a signal rather than an artifact.

Not building an open proxy. api/read.js fetches arbitrary URLs on request. It's restricted to HTML over http(s), blocks private address ranges, caps body size, and times out at nine seconds. Rate limiting is the first thing to add if this ever sees traffic.

Calibration. Early versions punished any formal writing. Government pages and academic abstracts scored like content farms. The fix wasn't more positive signals, it was negative ones — rewarding specificity and human speech patterns. A stiff human writer still has facts in their sentences.

What I learned

The signal isn't in the text. It's between the texts. Nothing in a single article told me as much as the same six phrases appearing in all four samples.

Metadata beat prose analysis more often than I expected. Missing bylines and a publish-date burst caught farms that individually wrote clean enough sentences to pass.

And an honest tool has to state its limits in the interface, not the fine print. A site of humans working from a template scores high. Edited AI content scores low. The footer says so on every screen, because a number that looks like proof will be used as proof.

What's next

Rate limiting. Support for more sitemap shapes. Per-phrase highlighting in scan mode, which the paste mode already has. Comparing two domains side by side.

Built With

Share this project:

Updates

Submission history