Most "how to spot AI writing" lists are vibes. This one is measured.
I ran a deterministic scorer (regex + wordlists, no LLM) over the hero copy — headline, subhead, CTA — of 239 real SaaS landing pages, fetched 2026-07-24. Below is how often each of the 9 tells actually fired.
The headline result: the single most common tell is not a word. It's an absence. 81% of landing pages (194/239) have no concrete number anywhere in the hero. That beats every hype word, every "seamless", every em-dash theory.
| # | Tell | What fires it | Frequency | Precision |
|---|---|---|---|---|
| 4 | No concrete number in hero | no digit in headline+subhead+CTA | 81.2% (194/239) | 81.2–81.6% |
| 2 | Filler words | "solutions / platform / powerful / streamline"… | 34.3% (82/239) | exact |
| 3 | Weak CTA | "Learn more", "Get started", "Discover"… | 14.6% (35/239) | exact |
| 9 | ALL-CAPS words in hero | word >2 chars, fully uppercase | ≥ 13.0% (31/239) | 13.0–23.0% |
| 1 | Hype words | "revolutionize / unlock / seamless / leverage"… | 6.7% (16/239) | exact |
| 6 | Headline too short | < 3 words | ≥ 5.0% (12/239) | 5.0–12.6% |
| 5 | Headline too long | > 12 words | ≥ 2.9% (7/239) | 2.9–9.6% |
| 7 | Exclamation marks | any ! |
≥ 2.9% (7/239) | 2.9–11.7% |
| 8 | Emoji in hero | any emoji codepoint | ≥ 2.1% (5/239) | 2.1–12.1% |
The published CSV stores at most 3 flags per page, and it keeps them in the
scorer's fixed emission order (hype, filler, weakcta, nonum, longhl, shorthl, excl, emoji, caps). I verified this: all 239 rows are in canonical order, 0
exceptions, and no row has more than 3 flags.
So the truncation is not random — it bites the tail of that order:
hype,filler,weakctaare positions 1–3. They can never be cut. Exact.nonumis position 4. It's unknown for exactly 1 page (the one row whose three slots were already full before reaching it). Effectively exact.- Positions 5–9 are unknown for 16–24 pages each. Those are lower bounds, and I've given the upper bound too. The truth is somewhere in the interval.
I'm not going to round a lower bound up and call it a finding. If you want the exact tail frequencies, the scorer is client-side and you can re-run it.
Not measured (people ask, I have no data): em-dash rate, "delve", "tapestry", "it's not X, it's Y" constructions, tricolons, sentence-length variance. The scorer doesn't look for them, so I'm not reporting numbers on them.
Deterministic 0–100 across 5 dimensions: Anti-hype (25), Specificity (25), Clarity (25), Headline shape (13), CTA (12).
100 ███████████████████ 19
90-99 ████████████████████████ 24
80-89 ███████████████████████████████████████████████████████████████████████████ 75
70-79 ██████████████████████████████████████████████████████████████████████████████████████████ 90
60-69 ██████████████████████████ 26
50-59 ████ 4
40-49 █ 1
Median 79, mean 80.1. 19 pages at 100 (calendly.com among them). 31 pages below 70. Median hero length: 151 characters.
A 100 means zero flags fired — not that the copy is good. The scorer can only detect the tells it knows about.
| Score | Domain | Flags |
|---|---|---|
| 41 | copy.ai | filler, nonum, shorthl |
| 51 | beehiiv.com | filler, nonum, caps |
| 51 | bitwarden.com | filler, nonum, caps |
| 57 | optimizely.com | filler, nonum, longhl |
| 58 | unit.co | hype, filler, nonum |
| 61 | close.com | filler, nonum, caps |
| 61 | jotform.com | filler, nonum, caps |
| 61 | stripe.com | filler, weakcta, nonum |
copy.ai — an AI copywriting company — has the most AI-sounding hero in the dataset. I did not go looking for that. It fell out of the sort.
And stripe.com at 61 is the useful counterexample: a page can convert extremely well and still score badly here. The scorer measures how templated the copy reads, not revenue. Don't confuse the two.
| Score | Domain | Flags |
|---|---|---|
| 83 | notion.so | nonum |
| 83 | jasper.ai | nonum |
| 79 | linear.app | nonum, emoji |
| 77 | vercel.com | nonum, shorthl |
| 75 | anthropic.com | weakcta, nonum |
| 75 | figma.com | weakcta, nonum |
| 62 | asana.com | hype, filler, weakcta |
Note how many land on nonum alone. That's the whole point: the modal
"AI-sounding" landing page in 2026 isn't full of "revolutionize". It's clean,
grammatical, confident — and says nothing you could check.
- Sample: 239 SaaS/dev-tool landing pages, homepage hero only. 303 candidate URLs were collected; 239 survived extraction. The 64 that dropped out are a selection effect, not a random one — they failed precisely because their hero was harder to extract, which skews toward JS-heavy pages. Read every number below as describing statically-extractable landing pages.
- Fetch: static HTTP GET + regex extraction of headline/subhead/CTA
(
static-fetch-regex-v1), 2026-07-24. No JS execution. - Scoring: deterministic. Same input → same score, forever. No model call.
- Known biases, stated plainly:
- Static fetch means JS-rendered heroes may be mis-extracted or missed.
- Wordlists are hand-built and English-only. They encode my judgment about what "filler" is. Reasonable people will disagree — the lists are readable.
- Sample is convenience-selected toward SaaS/dev tools, not random.
- "AI-sounding" ≠ "AI-generated". A human writing on autopilot produces the same tells. This measures the output, and makes no claim about the author.
nonumis a blunt instrument: it fires on any hero with no digit, including pages where a number would be dishonest.
- Full CSV, 239 rows (url, domain, score, top_flags, hero_chars, extracted_at, method): https://gist.github.com/parweb/5ed569ba76c365f7b789a979ad6090e7
- Reproduce these numbers — the analysis above is just
csv+collections.Counterover that file. Grouping bytop_flags, plus the truncation accounting described above.
- Leaderboard of all 239, sortable: https://1h-money-store.vercel.app/leaderboard?utm_source=gist&utm_campaign=tells
- Which words actually cost points: https://1h-money-store.vercel.app/sounds-ai?utm_source=gist&utm_campaign=tells
- Open discussion — I have two unresolved questions about the wordlist and about
whether
nonumis too blunt, and I'd genuinely like arguments: parweb/landing-copy-grader#1
Disclosure: this is my own dataset and my own scorer. Everything above is checkable against the CSV; I'd rather be corrected than quoted.
CC0 — take the numbers, no attribution needed.