diff --git a/_includes/packages/macros.njk b/_includes/packages/macros.njk
index 0a1eba647..f3c6b0ba6 100644
--- a/_includes/packages/macros.njk
+++ b/_includes/packages/macros.njk
@@ -184,8 +184,9 @@
{% set icon_id = (label | label_icon_id) if with_icons else '' %}
{% if icon_id %}
{% set tint = (label | label_icon_tint) if with_icons else '' %}
+ {% set icon_sprite = (label | label_icon_sprite) if with_icons else '' %}
{% endif %}
{{ label }}
diff --git a/eleventy.config.mjs b/eleventy.config.mjs
index 611a9e5c9..f71f9584d 100644
--- a/eleventy.config.mjs
+++ b/eleventy.config.mjs
@@ -27,6 +27,8 @@ const FEATURED_LABELS = [
'theme',
]
const LABELS_RANK = new Map(FEATURED_LABELS.map((label, index) => [label, index]))
+const LABEL_ICON_MINIMUM_USAGE = 3
+const LABEL_ICON_RECENT_WINDOW_DAYS = 365
const MS_IN_DAY = 24 * 60 * 60 * 1000
const MAGIC_FRESHNESS_WINDOW_DAYS = 365 * 2 // bonus for packages that had updates
@@ -374,6 +376,7 @@ export default async function (eleventyConfig) {
const siteOrigin = isProd ? prodOrigin : devOrigin
const staticOutputDir = isProd ? 'static_' + util.gitHash : 'static'
const bundledScriptEntries = new Set()
+ let labelIcons = null
eleventyConfig.addPassthroughCopy(
{ static: staticOutputDir },
@@ -388,6 +391,11 @@ export default async function (eleventyConfig) {
eleventyConfig.on('eleventy.after', async ({ directories } = {}) => {
const outputDir = directories?.output ?? '_site'
await writeVendorModules(path.join(outputDir, staticOutputDir, 'vendor'))
+ writeLabelIconSprites(
+ 'static/label-icons.svg',
+ path.join(outputDir, staticOutputDir),
+ labelIcons,
+ )
if (!isProd) {
return
@@ -524,6 +532,7 @@ export default async function (eleventyConfig) {
const packages = all_packages.map(packageData)
const packagesWithMagic = computeMagicMetadata(packages)
+ const labels = util.collectLabels(all_packages)
const livingHomePackages = packages.filter(pkg => !pkg.removed)
@@ -535,6 +544,21 @@ export default async function (eleventyConfig) {
const newestHomePackages = packagesByDate('first_seen').slice(0, HOME_SECTION_PACKAGE_LIMIT)
const updatedHomePackages = packagesByDate('last_modified').slice(0, HOME_SECTION_PACKAGE_LIMIT)
+ const latestPackageUpdate = livingHomePackages.reduce(
+ (latest, pkg) => Math.max(latest, new Date(pkg.last_modified ?? 0).getTime() || 0),
+ 0,
+ )
+ const recentCutoff = latestPackageUpdate - LABEL_ICON_RECENT_WINDOW_DAYS * MS_IN_DAY
+ const recentlyUpdatedPackages = livingHomePackages.filter(
+ pkg => new Date(pkg.last_modified ?? 0).getTime() >= recentCutoff,
+ )
+ labelIcons = filters.configureLabelIcons(labels, {
+ minimumUsage: LABEL_ICON_MINIMUM_USAGE,
+ preferredPackages: [
+ ...newestHomePackages,
+ ...recentlyUpdatedPackages,
+ ],
+ })
const remarkablePackages = () => {
const alreadyFeatured = new Set()
@@ -613,9 +637,7 @@ export default async function (eleventyConfig) {
}
}
- eleventyConfig.addCollection('labels', () => {
- return util.collectLabels(all_packages)
- })
+ eleventyConfig.addCollection('labels', () => labels)
eleventyConfig.addCollection('libraries', () => {
return Object.values(workspace.libraries)
@@ -678,6 +700,7 @@ export default async function (eleventyConfig) {
// Register all named exports from external module as filters
for (const [name, fn] of Object.entries(filters)) {
+ if (name === 'configureLabelIcons') continue
eleventyConfig.addFilter(name, fn)
}
@@ -696,6 +719,32 @@ export default async function (eleventyConfig) {
}
}
+function writeLabelIconSprites(sourcePath, outputDir, labelIcons) {
+ if (!(labelIcons?.primarySources instanceof Set) || !fs.existsSync(sourcePath)) {
+ return
+ }
+
+ const source = fs.readFileSync(sourcePath, 'utf8')
+ fs.mkdirSync(outputDir, { recursive: true })
+ fs.writeFileSync(
+ path.join(outputDir, 'label-icons.svg'),
+ labelIconSpriteForSources(source, labelIcons.primarySources),
+ 'utf8',
+ )
+ fs.writeFileSync(
+ path.join(outputDir, 'label-icons-extra.svg'),
+ labelIconSpriteForSources(source, labelIcons.secondarySources),
+ 'utf8',
+ )
+}
+
+function labelIconSpriteForSources(source, sources) {
+ return source.replace(
+ /]*\bid="label-icon-([^"]+)"[^>]*>[\s\S]*?<\/symbol>\r?\n?/g,
+ (symbol, iconSource) => sources.has(iconSource) ? symbol : '',
+ )
+}
+
async function bundleJs(staticOutputDir, entries, isProd) {
if (!entries.size) {
return
diff --git a/eleventy.filters.mjs b/eleventy.filters.mjs
index ac04f6547..7f9457e08 100644
--- a/eleventy.filters.mjs
+++ b/eleventy.filters.mjs
@@ -14,6 +14,8 @@ const configPath = path.join(__dirname, 'label-icons-config.json')
let labelIconSourceSet = new Set()
let labelIconAliases = {}
let labelIconTints = {}
+let labelIconPrimarySourceSet = null
+let labelIconSecondarySourceSet = null
const longDateFormatter = new Intl.DateTimeFormat('en-US', { dateStyle: 'long' })
const compactNumberFormatter = new Intl.NumberFormat('en', { notation: 'compact' })
@@ -70,6 +72,18 @@ export function label_icon_tints_json() {
return JSON.stringify(labelIconTints)
}
+export function configureLabelIcons(labels, { minimumUsage = 1, preferredPackages = [] } = {}) {
+ labelIconPrimarySourceSet = primaryLabelIconSources(labels, minimumUsage, preferredPackages)
+ labelIconSecondarySourceSet = new Set(
+ Array.from(labelIconSourceSet).filter(source => !labelIconPrimarySourceSet.has(source)),
+ )
+
+ return {
+ primarySources: labelIconPrimarySourceSet,
+ secondarySources: labelIconSecondarySourceSet,
+ }
+}
+
export function label_normalization_note(changes) {
const sortedChanges = changes
.map(change => ({ from: String(change.from), to: String(change.to) }))
@@ -103,6 +117,7 @@ export function search_index_json(packages) {
packages: packages.map(compactSearchPackage),
label_icon_aliases: labelIconAliases,
label_icon_tints: labelIconTints,
+ label_icon_secondary: Array.from(labelIconSecondarySourceSet ?? []),
})
}
@@ -186,6 +201,53 @@ function joinAsSentenceList(parts) {
return `${parts.slice(0, -1).join(', ')}, and ${parts.at(-1)}`
}
+function primaryLabelIconSources(labels, minimumUsage, preferredPackages) {
+ const counts = new Map()
+ const threshold = Math.max(1, Number(minimumUsage) || 1)
+
+ for (const item of labels ?? []) {
+ const key = typeof item?.key === 'string' ? item.key : String(item ?? '')
+ const canonical = sourceLabelFor(key)
+ if (!canonical) continue
+
+ const count = Number(item?.count ?? 1)
+ counts.set(canonical, (counts.get(canonical) ?? 0) + (Number.isFinite(count) ? count : 1))
+ }
+
+ const sources = new Set()
+ for (const source of labelIconSourceSet) {
+ if ((counts.get(source) ?? 0) >= threshold) {
+ sources.add(source)
+ }
+ }
+
+ for (const pkg of preferredPackages) {
+ for (const label of pkg.labels ?? []) {
+ const source = sourceLabelFor(label)
+ if (source) sources.add(source)
+ }
+ }
+
+ return sources
+}
+
+function sourceLabelFor(label) {
+ if (typeof label !== 'string') return ''
+ const normalized = label.trim().toLowerCase()
+ if (!normalized) return ''
+
+ const alias = labelIconAliases[normalized]
+ if (alias && labelIconSourceSet.has(alias)) {
+ return alias
+ }
+
+ if (labelIconSourceSet.has(normalized)) {
+ return normalized
+ }
+
+ return ''
+}
+
function canonicalLabel(label) {
if (typeof label !== 'string') return ''
const normalized = label.trim().toLowerCase()
@@ -215,6 +277,14 @@ export function label_icon_tint(label) {
return labelIconTints[canonical] ?? ''
}
+export function label_icon_sprite(label) {
+ const canonical = canonicalLabel(label)
+ if (!canonical) return ''
+ return labelIconSecondarySourceSet?.has(canonical)
+ ? 'static/label-icons-extra.svg'
+ : 'static/label-icons.svg'
+}
+
// number formatting with grouping (e.g. 10,000)
export function grouping(count) {
return groupedNumberFormatter.format(count)
@@ -265,6 +335,24 @@ export function bust(p) {
if (import.meta.vitest) {
const { describe, it, expect } = import.meta.vitest
+ describe('label icon sprites', () => {
+ it('keeps common and preferred icons in the primary sprite', () => {
+ const sprites = configureLabelIcons([
+ { key: 'python', count: 3 },
+ { key: 'typst', count: 1 },
+ ], {
+ minimumUsage: 3,
+ preferredPackages: [{ labels: ['typst'] }],
+ })
+
+ expect(sprites.primarySources).toContain('python')
+ expect(sprites.primarySources).toContain('typst')
+ expect(sprites.secondarySources).toContain('audio')
+ expect(label_icon_sprite('typst')).toBe('static/label-icons.svg')
+ expect(label_icon_sprite('audio')).toBe('static/label-icons-extra.svg')
+ })
+ })
+
describe('date_time_format', () => {
it.each([
[[new Date('2025-09-08T11:12:59Z')], '2025-09-08 11:12'],
diff --git a/label-icons-config.json b/label-icons-config.json
index 8714418ae..8952e5758 100644
--- a/label-icons-config.json
+++ b/label-icons-config.json
@@ -1,9 +1,10 @@
{
"_": "Manually maintained set of icons to exclude and an alias map from labels to icon/language ids",
- "exclude": ["ai"],
+ "exclude": ["ai", "c", "c#", "c++"],
"aliases": {
"javascript": "js",
"ecmascript": "js",
- "ecmascript6": "js"
+ "ecmascript6": "js",
+ "cpp": "c++"
}
}
diff --git a/label-icons.json b/label-icons.json
index 1a2a7730c..784ca9a6f 100644
--- a/label-icons.json
+++ b/label-icons.json
@@ -10,15 +10,11 @@
"binary": "graphite",
"blade": "orange",
"bower": "yellow",
- "c": "purple",
- "c#": "blue",
- "c++": "sky",
"cairo": "graphite",
"clojure": "purple",
"cmake": "blue",
"coffeescript": "orange",
"composer": "orange",
- "cpp": "sky",
"crystal": "graphite",
"css": "blue",
"csv": "green",
diff --git a/labels.njk b/labels.njk
index 8659dba8f..319cd4000 100644
--- a/labels.njk
+++ b/labels.njk
@@ -55,8 +55,9 @@ page_type: labels
{% set icon_id = item.key | label_icon_id %}
{% if icon_id %}
{% set tint = item.key | label_icon_tint %}
+ {% set icon_sprite = item.key | label_icon_sprite %}
{% endif %}
{{ item.key }}
diff --git a/static/label-icons.svg b/static/label-icons.svg
index 17527e642..52e0e3305 100644
--- a/static/label-icons.svg
+++ b/static/label-icons.svg
@@ -38,15 +38,6 @@
-
-
-
-
-
-
-
-
-
@@ -62,9 +53,6 @@
-
-
-
diff --git a/static/module/card.js b/static/module/card.js
index f23873578..38ef2ed29 100644
--- a/static/module/card.js
+++ b/static/module/card.js
@@ -260,7 +260,9 @@ export class Card {
svg.setAttribute('class', `label-icon${tint ? ' label-icon--' + tint : ''}`)
svg.setAttribute('aria-hidden', 'true')
const use = document.createElementNS(svgNS, 'use')
- use.setAttribute('href', `${this.staticBase}label-icons.svg#${iconId}`)
+ const secondary = window.__LABEL_ICON_SECONDARY__?.has(canonical)
+ const sprite = secondary ? 'label-icons-extra.svg' : 'label-icons.svg'
+ use.setAttribute('href', `${this.staticBase}${sprite}#${iconId}`)
svg.appendChild(use)
a.appendChild(svg)
}
diff --git a/static/package-search.js b/static/package-search.js
index f1472efda..8bfccca40 100644
--- a/static/package-search.js
+++ b/static/package-search.js
@@ -37,6 +37,7 @@ const knownLabels = new Set(
)
window.__LABEL_ICON_ALIASES__ = rawIndex.label_icon_aliases ?? {}
window.__LABEL_ICON_TINTS__ = rawIndex.label_icon_tints ?? {}
+window.__LABEL_ICON_SECONDARY__ = new Set(rawIndex.label_icon_secondary ?? [])
const minisrch = createMinisearch(MiniSearch, packages)