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.
This commit is contained in:
Manav Arya Singh
2026-09-15 17:51:10 +04:00
parent 7fe2afcd98
commit 31bd34f487
4 changed files with 52 additions and 4 deletions
+9 -3
View File
@@ -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']);
+7 -1
View File
@@ -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);
+21
View File
@@ -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(),
+15
View File
@@ -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');