From 31bd34f4870e8bd33d2615f9cca5986b7869f807 Mon Sep 17 00:00:00 2001 From: Manav Arya Singh <144777973+Manavarya09@users.noreply.github.com> Date: Tue, 15 Sep 2026 17:51:10 +0400 Subject: [PATCH] fix(extraction): code faces aren't body fonts; weigh colour evidence by chroma Measured by replaying the extractors against cached crawls of the 27 benchmark sites, so only the code varied between runs. Typography: syntax highlighting draws every token as its own text element, so counting text-rendering elements made plexMono (tailwindcss, 450 vs Inter 131), commitMono (resend) and Departure Mono (supabase) the body font. Families whose name marks a code face are labelled "mono" and sort after the text faces. Colours: usage, interactive backgrounds and area now count in proportion to chroma (full weight from chroma 30), and area scores 60*sqrt(share). Near-black #101214 (chroma 1.5) had won atlassian on 702 uses, #231e15 had won mailchimp, and one element painting 84% of discord.com had won on area. Replay, previous -> this: primary colour 10/17 -> 13/17, body font 24/27 -> 27/27, no site regressed. Still missed: linear (brand colour on one element), slack, paypal, supabase (a lighter shade of the brand green). The three new tests fail on the previous code. --- src/extractors/colors.js | 12 +++++++++--- src/extractors/typography.js | 8 +++++++- tests/color-ranking.test.js | 21 +++++++++++++++++++++ tests/typography-usage.test.js | 15 +++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/src/extractors/colors.js b/src/extractors/colors.js index c2163e5..70a0522 100644 --- a/src/extractors/colors.js +++ b/src/extractors/colors.js @@ -99,12 +99,18 @@ export function extractColors(computedStyles) { // interactive backgrounds: a strong signal, with diminishing returns // chroma, not HSL saturation: near-black #002533 has s=100 but reads dark // usage and painted area: a brand colour is repeated across the page + // Evidence of use counts only in proportion to how colourful the cluster is: + // near-black #101214 on atlassian.com (chroma 1.5) got in through two dark + // buttons and then won on 702 uses. Area has diminishing returns, since one + // full-page background (84% of discord.com) is a surface, not repetition. function brandScore(c) { const chroma = ((100 - Math.abs(2 * c.lightness - 100)) * c.saturation) / 100; - return 40 * Math.log2(1 + c.interactiveBg) - + chroma + const colourful = Math.min(1, chroma / 30); + return chroma + colourful * ( + 40 * Math.log2(1 + c.interactiveBg) + 25 * Math.log10(Math.max(1, c.count)) - + 300 * (c.areaShare || 0); + + 60 * Math.sqrt(c.areaShare || 0) + ); } // The browser's default link colours say nothing about the brand. const UA_LINK_COLORS = new Set(['#0000ee', '#551a8b']); diff --git a/src/extractors/typography.js b/src/extractors/typography.js index f4c7ab3..2fd5d0a 100644 --- a/src/extractors/typography.js +++ b/src/extractors/typography.js @@ -21,6 +21,11 @@ function rendersText(el) { return el.hasText !== false && !NON_RENDERING_TAGS.has(el.tag); } +// Code faces. Syntax highlighting draws every token as its own text element, so +// on tailwindcss.com plexMono out-counted Inter 450 to 131. A code face is never +// the body font and sorts after the text faces. +const MONO_FAMILY_RE = /mono|code|consol|courier|menlo|monaco/i; + function normaliseFamily(raw) { if (!raw) return null; // Strip quotes + take the first stack member (sites declare e.g. @@ -349,8 +354,9 @@ export function extractTypography(computedStyles, options = {}) { // Unique font families sorted by usage const families = [...familyCount.entries()] - .sort((a, b) => b[1] - a[1]) + .sort((a, b) => MONO_FAMILY_RE.test(a[0]) - MONO_FAMILY_RE.test(b[0]) || b[1] - a[1]) .map(([name, count]) => { + if (MONO_FAMILY_RE.test(name)) return { name, count, usage: 'mono' }; const usedOn = computedStyles .filter(el => el.fontFamily?.includes(name) && rendersText(el)) .map(el => el.tag); diff --git a/tests/color-ranking.test.js b/tests/color-ranking.test.js index 216bf1b..0f81087 100644 --- a/tests/color-ranking.test.js +++ b/tests/color-ranking.test.js @@ -61,6 +61,27 @@ describe('brand colour ranking', () => { assert.equal(extractColors(styles).primary.hex, '#ff4800'); }); + it('a near-black used everywhere does not outrank the brand blue (atlassian: #101214 x702, 21% area)', () => { + const styles = [ + ...page(), + ...times(700, () => el({ tag: 'span', color: '#101214' })), + ...times(2, () => el({ tag: 'button', bg: '#101214', color: '#ffffff', area: 180_000 })), + ...times(200, () => el({ tag: 'a', color: '#1868db' })), + el({ tag: 'button', bg: '#1868db', color: '#ffffff', area: 2500 }), + ]; + assert.equal(extractColors(styles).primary.hex, '#1868db'); + }); + + it('one full-page background does not outrank the brand colour on buttons and text (discord: 84% area)', () => { + const styles = [ + ...page(), + el({ tag: 'main', bg: '#1a2081', area: 6_000_000 }), + ...times(2, () => el({ tag: 'a', bg: '#5865f2', color: '#ffffff', area: 3000 })), + ...times(33, () => el({ tag: 'span', color: '#5865f2' })), + ]; + assert.equal(extractColors(styles).primary.hex, '#5865f2'); + }); + it('a CTA colour used on many buttons still wins over a dark accent surface (hubspot)', () => { const styles = [ ...page(), diff --git a/tests/typography-usage.test.js b/tests/typography-usage.test.js index 246c243..086a339 100644 --- a/tests/typography-usage.test.js +++ b/tests/typography-usage.test.js @@ -56,6 +56,21 @@ describe('typography family usage', () => { assert.deepEqual(names, ['GDS Transport']); }); + it('a code face is not the body font, however many highlighted tokens it draws (tailwindcss, resend)', () => { + const styles = [ + // syntax highlighting: every token is its own text element + ...Array.from({ length: 450 }, () => el('span', 'plexMono, ui-monospace, monospace', { hasText: true })), + ...Array.from({ length: 90 }, () => el('p', 'inter, system-ui, sans-serif', { hasText: true })), + ...Array.from({ length: 41 }, () => el('h2', 'inter, system-ui, sans-serif', { hasText: true })), + ...Array.from({ length: 144 }, () => el('span', '"Source Code Pro", monospace', { hasText: true })), + ]; + const typo = extractTypography(styles); + assert.equal(typo.families[0].name, 'inter', 'the text face leads the family list'); + assert.equal(typo.families.find((f) => f.name === 'plexMono').usage, 'mono'); + assert.equal(typo.families.find((f) => f.name === 'Source Code Pro').usage, 'mono'); + assert.equal(typo.families.find((f) => f.name === 'inter').usage, 'all'); + }); + it('still counts records captured before hasText existed', () => { const typo = extractTypography([el('p', '"Inter", sans-serif'), el('h2', '"Inter", sans-serif')]); assert.equal(typo.families[0].name, 'Inter');