Background
The current detector catches the AI-default content of a hero (purple gradient text, three-icon-tile feature row, etc.) but does not catch the structural patterns that have become category-defining tells of late-2025/early-2026 AI-generated marketing pages. Two specific shapes are now widespread and unflagged:
- Italic-serif display headline. Oversized italic serif (Fraunces / Recoleta / Newsreader-italic / similar) used as the primary
h1. Magazine-cover affectation that reads as taste in isolation, but has become the universal "AI startup landing page" hero.
- Hero eyebrow / pill chip. Tiny uppercase letter-spaced label sitting as a sibling immediately above an oversized hero headline — e.g.
AI-NATIVE WORKFLOWS above Build faster with intelligent workflows. Now appears in nearly every newly-shipped SaaS landing page.
These two patterns frequently co-occur, but each warrants its own rule because each appears in isolation often enough.
A real-world example of both, side by side, lives at ~/code/landing-demo/index.html (the demo target page used at the Design Futures Assembly demo). That fixture should drive the test cases.
Rule 1: italic-serif-display
Detection (browser path, jsdom path)
Flag any h1 (or any element rendered at hero scale) where:
font-style === 'italic', AND
font-family resolves to a serif face (heuristic: name contains "serif" via inheritance, OR the resolved primary family matches a known serif list, OR font-feature-settings is serif-typical), AND
- computed
font-size ≥ 48px.
The serif-family check is the tricky bit. Three options, in order of preference:
- Maintain a
KNOWN_SERIF_FONTS set (Fraunces, Recoleta, Newsreader, Garamond family, Cormorant, Playfair, Tiempos, Lora, Vollkorn, Spectral, Source Serif, IBM Plex Serif, etc.) and require the primary computed family to be in it OR generic serif to be the next fallback.
- Use the
font-family stack: if any token is serif (the generic), treat the primary as serif. False positives possible (someone declares font-family: 'Inter', serif) but in practice the generic serif fallback only ships with a serif primary.
- Probe via canvas-measured glyph metrics. Most accurate, most expensive, probably overkill.
I'd start with the first approach. Fixture should cover both "primary face known to be serif" and "primary face unknown but generic serif fallback" cases.
Fixture cases (tests/fixtures/antipatterns/italic-serif-display.html)
Should-flag:
h1 with font-family: 'Fraunces', Georgia, serif; font-style: italic; font-size: 88px (the Lumina hero pattern from ~/code/landing-demo)
h1 with font-family: 'Recoleta', serif; font-style: italic; font-size: 64px
h1 with mixed inline italic emphasis (<h1>Build faster with <em>intelligent</em> workflows</h1> where the em is the italic-serif marker — this is the actual Lumina pattern, hardest to detect, possibly out of scope for v1)
Should-pass:
h1 italic but sans-serif (Karla italic at 64px) — italic display in a sans-serif face is rare and usually intentional
h1 serif, but not italic, regardless of size
h1 italic serif at < 32px (a genuine pull-quote inside body text, not a hero)
- An
<em> inside an otherwise-roman headline (because the headline as a whole is not italic)
Rule 2: hero-eyebrow-chip
Detection
Flag any element where:
- text content length ≤ 30 chars
text-transform: uppercase (or content is already all-uppercase letters)
- computed
letter-spacing ≥ 0.1em (≈1.6px at 16px)
- computed
font-size ≤ 14px
- it sits as a sibling immediately preceding an
h1 (or hero-class element) with font-size ≥ 48px
The sibling-relationship requirement is what distinguishes this from a legitimate uppercase label elsewhere on the page. Reference the existing icon-tile-stack rule for the sibling-relationship pattern (~line 425 in src/detect-antipatterns.mjs).
Fixture cases (tests/fixtures/antipatterns/hero-eyebrow-chip.html)
Should-flag:
<div class=\"eyebrow\">AI-NATIVE WORKFLOWS</div><h1 style=\"font-size: 88px\">…</h1> (the Lumina pattern)
<span>NEW</span><h1 style=\"font-size: 64px\">…</h1> where span has uppercase + tracking + small size
- Pill-shaped chip variant (background-color, border-radius: 999px, padding) immediately above oversized h1
Should-pass:
- An uppercase label not adjacent to an h1 (e.g. table header, caption)
- Eyebrow text with normal letter-spacing
- An h1 ≥ 48px without any preceding uppercase sibling
- The eyebrow pattern above an h1 that's body-sized (not hero)
Implementation checklist (per CLAUDE.md TDD order)
For each rule:
- Fixture at
tests/fixtures/antipatterns/{rule-id}.html with both columns, ≥4 flag cases and ≥5 false-positive shapes, explicit pixel dimensions for jsdom.
- Failing test in
tests/detect-antipatterns-fixtures.test.mjs using the snippet-substring pattern.
- Rule entry in
ANTIPATTERNS (category: slop, with skillSection and skillGuideline).
- Pure check function
checkXxx(opts) returning findings.
- Both adapters:
checkElementXxxDOM(el) (browser, getComputedStyle + getBoundingClientRect) and checkElementXxx(el, tag, window) (jsdom, parseFloat(style.width) instead of layout). Wire both into both element loops in src/detect-antipatterns.mjs — browser loop (~1837) and jsdom loop in detectHtml (~2058).
- Verify on
~/code/landing-demo/index.html and on the public homepage — the eyebrow rule especially needs a no-false-positive check on the impeccable site itself.
- Run all three builds + tests:
bun run build && bun run build:browser && bun run build:extension && bun run test.
Skill-side updates
If either rule lands, the SKILL.md / reference/critique.md and reference/audit.md should pick up the new patterns by name. The SKILL.md "Absolute bans" list is a candidate home for italic-serif-display once we're confident it has no register exceptions (note: editorial / magazine register may legitimately want italic-serif headlines — keep this register-aware).
Out of scope for this issue
- Mesh / radial gradient hero backgrounds
- Border-radius monotony
- Generic SaaS feature copy (content rule, separate work)
- Detecting an italic
<em> inside a roman h1 (would require richer DOM walking; revisit after v1)
Demo-driven motivation
This issue exists because the patterns above co-occur on the Lumina demo page used at the Design Futures Assembly Impeccable demo. The page already gets flagged for overused-font (Fraunces, Inter), icon-tile-stack, and low-contrast, but the most visually obvious tell — the Fraunces-italic-88px hero with a tiny uppercase eyebrow above it — slips past the detector. Closing that gap closes the gap between what the audience visually identifies as slop and what the tool reports.
Background
The current detector catches the AI-default content of a hero (purple gradient text, three-icon-tile feature row, etc.) but does not catch the structural patterns that have become category-defining tells of late-2025/early-2026 AI-generated marketing pages. Two specific shapes are now widespread and unflagged:
h1. Magazine-cover affectation that reads as taste in isolation, but has become the universal "AI startup landing page" hero.AI-NATIVE WORKFLOWSaboveBuild faster with intelligent workflows. Now appears in nearly every newly-shipped SaaS landing page.These two patterns frequently co-occur, but each warrants its own rule because each appears in isolation often enough.
A real-world example of both, side by side, lives at
~/code/landing-demo/index.html(the demo target page used at the Design Futures Assembly demo). That fixture should drive the test cases.Rule 1: italic-serif-display
Detection (browser path, jsdom path)
Flag any
h1(or any element rendered at hero scale) where:font-style === 'italic', ANDfont-familyresolves to a serif face (heuristic: name contains "serif" via inheritance, OR the resolved primary family matches a known serif list, ORfont-feature-settingsis serif-typical), ANDfont-size≥ 48px.The serif-family check is the tricky bit. Three options, in order of preference:
KNOWN_SERIF_FONTSset (Fraunces, Recoleta, Newsreader, Garamond family, Cormorant, Playfair, Tiempos, Lora, Vollkorn, Spectral, Source Serif, IBM Plex Serif, etc.) and require the primary computed family to be in it OR genericserifto be the next fallback.font-familystack: if any token isserif(the generic), treat the primary as serif. False positives possible (someone declaresfont-family: 'Inter', serif) but in practice the generic serif fallback only ships with a serif primary.I'd start with the first approach. Fixture should cover both "primary face known to be serif" and "primary face unknown but generic serif fallback" cases.
Fixture cases (
tests/fixtures/antipatterns/italic-serif-display.html)Should-flag:
h1withfont-family: 'Fraunces', Georgia, serif; font-style: italic; font-size: 88px(the Lumina hero pattern from~/code/landing-demo)h1withfont-family: 'Recoleta', serif; font-style: italic; font-size: 64pxh1with mixed inline italic emphasis (<h1>Build faster with <em>intelligent</em> workflows</h1>where theemis the italic-serif marker — this is the actual Lumina pattern, hardest to detect, possibly out of scope for v1)Should-pass:
h1italic but sans-serif (Karla italic at 64px) — italic display in a sans-serif face is rare and usually intentionalh1serif, but not italic, regardless of sizeh1italic serif at < 32px (a genuine pull-quote inside body text, not a hero)<em>inside an otherwise-roman headline (because the headline as a whole is not italic)Rule 2: hero-eyebrow-chip
Detection
Flag any element where:
text-transform: uppercase(or content is already all-uppercase letters)letter-spacing≥ 0.1em (≈1.6px at 16px)font-size≤ 14pxh1(or hero-class element) withfont-size≥ 48pxThe sibling-relationship requirement is what distinguishes this from a legitimate uppercase label elsewhere on the page. Reference the existing
icon-tile-stackrule for the sibling-relationship pattern (~line 425 insrc/detect-antipatterns.mjs).Fixture cases (
tests/fixtures/antipatterns/hero-eyebrow-chip.html)Should-flag:
<div class=\"eyebrow\">AI-NATIVE WORKFLOWS</div><h1 style=\"font-size: 88px\">…</h1>(the Lumina pattern)<span>NEW</span><h1 style=\"font-size: 64px\">…</h1>where span has uppercase + tracking + small sizeShould-pass:
Implementation checklist (per
CLAUDE.mdTDD order)For each rule:
tests/fixtures/antipatterns/{rule-id}.htmlwith both columns, ≥4 flag cases and ≥5 false-positive shapes, explicit pixel dimensions for jsdom.tests/detect-antipatterns-fixtures.test.mjsusing the snippet-substring pattern.ANTIPATTERNS(category:slop, withskillSectionandskillGuideline).checkXxx(opts)returning findings.checkElementXxxDOM(el)(browser,getComputedStyle+getBoundingClientRect) andcheckElementXxx(el, tag, window)(jsdom,parseFloat(style.width)instead of layout). Wire both into both element loops insrc/detect-antipatterns.mjs— browser loop (~1837) and jsdom loop indetectHtml(~2058).~/code/landing-demo/index.htmland on the public homepage — the eyebrow rule especially needs a no-false-positive check on the impeccable site itself.bun run build && bun run build:browser && bun run build:extension && bun run test.Skill-side updates
If either rule lands, the SKILL.md /
reference/critique.mdandreference/audit.mdshould pick up the new patterns by name. The SKILL.md "Absolute bans" list is a candidate home for italic-serif-display once we're confident it has no register exceptions (note: editorial / magazine register may legitimately want italic-serif headlines — keep this register-aware).Out of scope for this issue
<em>inside a roman h1 (would require richer DOM walking; revisit after v1)Demo-driven motivation
This issue exists because the patterns above co-occur on the Lumina demo page used at the Design Futures Assembly Impeccable demo. The page already gets flagged for
overused-font(Fraunces, Inter),icon-tile-stack, andlow-contrast, but the most visually obvious tell — the Fraunces-italic-88px hero with a tiny uppercase eyebrow above it — slips past the detector. Closing that gap closes the gap between what the audience visually identifies as slop and what the tool reports.