From b5c203f38a07f20b21787afc17debd535a84b41a Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 29 Apr 2026 11:31:38 -0700 Subject: [PATCH] feat(detector): flag Fraunces, Geist, and the new monoculture fonts Adds Fraunces, Geist (Sans/Mono), Mona Sans, Plus Jakarta Sans, Space Grotesk, Recoleta, and Instrument Sans to OVERUSED_FONTS. Brand-domain exceptions for Geist on vercel.com / nextjs.org / v0.app and Mona Sans on github.com / githubnext.com. Updates four should-pass fixtures that used the now-flagged faces as "good examples"; switches to Newsreader + Karla. Adds positive assertions for Fraunces and Geist. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/detect-antipatterns-browser.js | 14 +++++++++++++- src/detect-antipatterns.mjs | 18 +++++++++++++++--- tests/detect-antipatterns.test.js | 12 +++++++++++- .../antipatterns/cssinjs-should-pass.tsx | 2 +- .../antipatterns/svelte-should-pass.svelte | 2 +- .../antipatterns/typography-should-pass.html | 10 +++++----- .../fixtures/antipatterns/vue-should-pass.vue | 2 +- 7 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/detect-antipatterns-browser.js b/src/detect-antipatterns-browser.js index b147f45fb..5c9e16391 100644 --- a/src/detect-antipatterns-browser.js +++ b/src/detect-antipatterns-browser.js @@ -61,7 +61,13 @@ const BORDER_SAFE_TAGS = new Set( ); const OVERUSED_FONTS = new Set([ + // Older monoculture (still ubiquitous): 'inter', 'roboto', 'open sans', 'lato', 'montserrat', 'arial', 'helvetica', + // Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave): + 'fraunces', 'instrument sans', + 'geist', 'geist sans', 'geist mono', + 'mona sans', + 'plus jakarta sans', 'space grotesk', 'recoleta', ]); // Brand-associated fonts: don't flag these as "overused" on the brand's own domains. @@ -70,10 +76,16 @@ const GOOGLE_DOMAINS = [ 'google.com', 'youtube.com', 'android.com', 'chromium.org', 'chrome.com', 'web.dev', 'gstatic.com', 'firebase.google.com', ]; +const VERCEL_DOMAINS = ['vercel.com', 'nextjs.org', 'v0.app']; +const GITHUB_DOMAINS = ['github.com', 'githubnext.com']; const BRAND_FONT_DOMAINS = { 'roboto': GOOGLE_DOMAINS, 'google sans': GOOGLE_DOMAINS, 'product sans': GOOGLE_DOMAINS, + 'geist': VERCEL_DOMAINS, + 'geist sans': VERCEL_DOMAINS, + 'geist mono': VERCEL_DOMAINS, + 'mona sans': GITHUB_DOMAINS, }; function isBrandFontOnOwnDomain(font) { @@ -116,7 +128,7 @@ const ANTIPATTERNS = [ category: 'slop', name: 'Overused font', description: - 'Inter, Roboto, Open Sans, Lato, Montserrat, and Arial are used on millions of sites. Choose a distinctive font that gives your interface personality.', + 'Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.', skillSection: 'Typography', skillGuideline: 'overused fonts like Inter', }, diff --git a/src/detect-antipatterns.mjs b/src/detect-antipatterns.mjs index 1cc2cb670..ab392a853 100644 --- a/src/detect-antipatterns.mjs +++ b/src/detect-antipatterns.mjs @@ -57,7 +57,13 @@ const BORDER_SAFE_TAGS = new Set( ); const OVERUSED_FONTS = new Set([ + // Older monoculture (still ubiquitous): 'inter', 'roboto', 'open sans', 'lato', 'montserrat', 'arial', 'helvetica', + // Newer monoculture (the Anthropic-skill / Vercel / GitHub default wave): + 'fraunces', 'instrument sans', + 'geist', 'geist sans', 'geist mono', + 'mona sans', + 'plus jakarta sans', 'space grotesk', 'recoleta', ]); // Brand-associated fonts: don't flag these as "overused" on the brand's own domains. @@ -66,10 +72,16 @@ const GOOGLE_DOMAINS = [ 'google.com', 'youtube.com', 'android.com', 'chromium.org', 'chrome.com', 'web.dev', 'gstatic.com', 'firebase.google.com', ]; +const VERCEL_DOMAINS = ['vercel.com', 'nextjs.org', 'v0.app']; +const GITHUB_DOMAINS = ['github.com', 'githubnext.com']; const BRAND_FONT_DOMAINS = { 'roboto': GOOGLE_DOMAINS, 'google sans': GOOGLE_DOMAINS, 'product sans': GOOGLE_DOMAINS, + 'geist': VERCEL_DOMAINS, + 'geist sans': VERCEL_DOMAINS, + 'geist mono': VERCEL_DOMAINS, + 'mona sans': GITHUB_DOMAINS, }; function isBrandFontOnOwnDomain(font) { @@ -112,7 +124,7 @@ const ANTIPATTERNS = [ category: 'slop', name: 'Overused font', description: - 'Inter, Roboto, Open Sans, Lato, Montserrat, and Arial are used on millions of sites. Choose a distinctive font that gives your interface personality.', + 'Inter, Roboto, Fraunces, Geist, Plus Jakarta Sans, and Space Grotesk are used on so many sites they no longer feel distinctive. Each new wave of AI-generated UIs converges on the same handful of faces. Choose a face that gives your interface personality.', skillSection: 'Typography', skillGuideline: 'overused fonts like Inter', }, @@ -2870,10 +2882,10 @@ const REGEX_MATCHERS = [ test: (m, line) => +m[1] >= 3 && hasBorderRadius(line), fmt: (m) => m[0] }, // --- Overused font --- - { id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica)\b/gi, + { id: 'overused-font', regex: /font-family\s*:\s*['"]?(Inter|Roboto|Open Sans|Lato|Montserrat|Arial|Helvetica|Fraunces|Geist Sans|Geist Mono|Geist|Mona Sans|Plus Jakarta Sans|Space Grotesk|Recoleta|Instrument Sans)\b/gi, test: () => true, fmt: (m) => m[0] }, - { id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat)\b/gi, + { id: 'overused-font', regex: /fonts\.googleapis\.com\/css2?\?family=(Inter|Roboto|Open\+Sans|Lato|Montserrat|Fraunces|Plus\+Jakarta\+Sans|Space\+Grotesk|Instrument\+Sans|Mona\+Sans|Geist)\b/gi, test: () => true, fmt: (m) => `Google Fonts: ${m[1].replace(/\+/g, ' ')}` }, // --- Pure black background --- diff --git a/tests/detect-antipatterns.test.js b/tests/detect-antipatterns.test.js index d31f346e4..f1018ee92 100644 --- a/tests/detect-antipatterns.test.js +++ b/tests/detect-antipatterns.test.js @@ -140,8 +140,18 @@ describe('detectText — overused fonts', () => { expect(f.some(r => r.antipattern === 'overused-font')).toBe(true); }); + test('detects Fraunces (current AI-default monoculture)', () => { + const f = detectText("h1 { font-family: 'Fraunces', Georgia, serif; }", 'test.css'); + expect(f.some(r => r.antipattern === 'overused-font')).toBe(true); + }); + + test('detects Geist (Vercel-default monoculture)', () => { + const f = detectText("body { font-family: 'Geist', sans-serif; }", 'test.css'); + expect(f.some(r => r.antipattern === 'overused-font')).toBe(true); + }); + test('does not flag distinctive fonts', () => { - const f = detectText("body { font-family: 'Instrument Sans', sans-serif; }", 'test.css'); + const f = detectText("body { font-family: 'Karla', sans-serif; }", 'test.css'); expect(f.filter(r => r.antipattern === 'overused-font')).toHaveLength(0); }); }); diff --git a/tests/fixtures/antipatterns/cssinjs-should-pass.tsx b/tests/fixtures/antipatterns/cssinjs-should-pass.tsx index 161967ce8..bf4f39149 100644 --- a/tests/fixtures/antipatterns/cssinjs-should-pass.tsx +++ b/tests/fixtures/antipatterns/cssinjs-should-pass.tsx @@ -7,7 +7,7 @@ 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; + font-family: 'Karla', system-ui, sans-serif; `; export const Hero = styled.section` diff --git a/tests/fixtures/antipatterns/svelte-should-pass.svelte b/tests/fixtures/antipatterns/svelte-should-pass.svelte index d009f97aa..71db03da9 100644 --- a/tests/fixtures/antipatterns/svelte-should-pass.svelte +++ b/tests/fixtures/antipatterns/svelte-should-pass.svelte @@ -14,7 +14,7 @@ diff --git a/tests/fixtures/antipatterns/typography-should-pass.html b/tests/fixtures/antipatterns/typography-should-pass.html index f4a91c47f..09cd5bfcf 100644 --- a/tests/fixtures/antipatterns/typography-should-pass.html +++ b/tests/fixtures/antipatterns/typography-should-pass.html @@ -4,23 +4,23 @@ Typography — Clean Patterns - +