From e58c8e46ef505307c888081a0de34081f7e046e5 Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Mon, 16 Feb 2026 21:34:06 +0100 Subject: [PATCH] fonts: consolidate font-family to global stack, self-host subset emoji font MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove redundant `font-family: sans-serif` from 18 component-level declarations — they now inherit the Helvetica/Arial/Noto Sans stack from the global `*` selector in app.css. Add self-hosted NotoColorEmoji subset (56 KB, down from 11 MB) as fallback for systems without the Noto Color Emoji font installed. The subset is generated at prebuild time via pyftsubset with a fixed list of the ~32 emojis actually used on the site. --- package.json | 2 +- scripts/subset-emoji-font.sh | 61 ++++++++++++++++++ src/app.css | 11 +++- src/lib/components/Header.svelte | 2 - src/lib/components/SearchInput.svelte | 1 - src/lib/components/TagCloud.svelte | 1 - .../recipes/BaseRecipeSelector.svelte | 1 - src/lib/components/recipes/Card.svelte | 1 - src/lib/components/recipes/CardAdd.svelte | 1 - .../components/recipes/CategoryFilter.svelte | 1 - .../recipes/CreateIngredientList.svelte | 3 - .../components/recipes/CreateStepList.svelte | 3 - .../components/recipes/EditRecipeNote.svelte | 1 - src/lib/components/recipes/Icon.svelte | 2 +- src/lib/components/recipes/IconFilter.svelte | 3 +- src/lib/components/recipes/IconLayout.svelte | 2 +- .../components/recipes/IngredientsPage.svelte | 3 - .../recipes/InstructionsPage.svelte | 3 - src/lib/components/recipes/Search.svelte | 1 - .../components/recipes/SeasonFilter.svelte | 1 - .../components/recipes/SeasonLayout.svelte | 1 - src/lib/components/recipes/TagFilter.svelte | 1 - .../[name]/+page.svelte | 3 - .../[recipeLang=recipeLang]/icon/+page.svelte | 2 +- static/fonts/NotoColorEmoji.ttf | Bin 0 -> 61468 bytes static/fonts/NotoColorEmoji.woff2 | Bin 0 -> 56400 bytes 26 files changed, 76 insertions(+), 35 deletions(-) create mode 100755 scripts/subset-emoji-font.sh create mode 100644 static/fonts/NotoColorEmoji.ttf create mode 100644 static/fonts/NotoColorEmoji.woff2 diff --git a/package.json b/package.json index e984198..11364dd 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite dev", - "prebuild": "npx vite-node scripts/generate-mystery-verses.ts", + "prebuild": "bash scripts/subset-emoji-font.sh && npx vite-node scripts/generate-mystery-verses.ts", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/scripts/subset-emoji-font.sh b/scripts/subset-emoji-font.sh new file mode 100755 index 0000000..b3ad34b --- /dev/null +++ b/scripts/subset-emoji-font.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Subset NotoColorEmoji to only the emojis we actually use. +# Requires: fonttools (provides pyftsubset) and woff2 (provides woff2_compress) +# +# Source font: system-installed NotoColorEmoji.ttf +# Output: static/fonts/NotoColorEmoji.woff2 + .ttf + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +OUT_DIR="$PROJECT_ROOT/static/fonts" + +SRC_FONT="/usr/share/fonts/noto/NotoColorEmoji.ttf" + +if [ ! -f "$SRC_FONT" ]; then + echo "Error: Source font not found at $SRC_FONT" >&2 + exit 1 +fi + +# ─── Fixed list of emojis to include ──────────────────────────────── +# Recipe icons (from database + hardcoded) +# Season/liturgical: ☀️ ✝️ ❄️ 🌷 🍂 🎄 🐇 +# Food/recipe: 🍽️ 🥫 +# UI/cospend categories: 🛒 🛍️ 🚆 ⚡ 🎉 🤝 💸 +# Status/feedback: ❤️ 🖤 ✅ ❌ 🚀 ⚠️ ✨ 🔄 +# Features: 📋 🖼️ 📖 🤖 🌐 🔐 🔍 🚫 + +EMOJIS="☀✝❄🌷🍂🎄🐇🍽🥫🛒🛍🚆⚡🎉🤝💸❤🖤✅❌🚀⚠✨🔄📋🖼📖🤖🌐🔐🔍🚫" +# ──────────────────────────────────────────────────────────────────── + +# Build Unicode codepoint list from the emoji string +UNICODES="" +for char in $(echo "$EMOJIS" | grep -oP '.'); do + code=$(printf 'U+%04X' "'$char") + if [ -n "$UNICODES" ]; then + UNICODES="$UNICODES,$code" + else + UNICODES="$code" + fi +done + +echo "Subsetting NotoColorEmoji with $(echo "$EMOJIS" | grep -oP '.' | wc -l) glyphs..." + +# Subset to TTF +pyftsubset "$SRC_FONT" \ + --unicodes="$UNICODES" \ + --output-file="$OUT_DIR/NotoColorEmoji.ttf" \ + --no-ignore-missing-unicodes + +# Convert to WOFF2 +woff2_compress "$OUT_DIR/NotoColorEmoji.ttf" + +ORIG_SIZE=$(stat -c%s "$SRC_FONT" 2>/dev/null || stat -f%z "$SRC_FONT") +TTF_SIZE=$(stat -c%s "$OUT_DIR/NotoColorEmoji.ttf" 2>/dev/null || stat -f%z "$OUT_DIR/NotoColorEmoji.ttf") +WOFF2_SIZE=$(stat -c%s "$OUT_DIR/NotoColorEmoji.woff2" 2>/dev/null || stat -f%z "$OUT_DIR/NotoColorEmoji.woff2") + +echo "Done!" +echo " Original: $(numfmt --to=iec "$ORIG_SIZE")" +echo " TTF: $(numfmt --to=iec "$TTF_SIZE")" +echo " WOFF2: $(numfmt --to=iec "$WOFF2_SIZE")" diff --git a/src/app.css b/src/app.css index d661373..4a4a96c 100644 --- a/src/app.css +++ b/src/app.css @@ -15,6 +15,15 @@ url(/fonts/crosses.ttf) format('truetype'); } +@font-face { + font-family: 'Noto Color Emoji Subset'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url(/fonts/NotoColorEmoji.woff2) format('woff2'), + url(/fonts/NotoColorEmoji.ttf) format('truetype'); +} + /* ============================================ COLOR SYSTEM Based on Nord Theme with semantic naming @@ -294,7 +303,7 @@ a:focus-visible { /* Icon badge (circular icon container) */ .g-icon-badge { - font-family: "Noto Color Emoji", emoji, sans-serif; + font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif; display: flex; align-items: center; justify-content: center; diff --git a/src/lib/components/Header.svelte b/src/lib/components/Header.svelte index 38f3dcb..acb750b 100644 --- a/src/lib/components/Header.svelte +++ b/src/lib/components/Header.svelte @@ -116,7 +116,6 @@ nav[hidden]{ :global(.site_header li>a) { text-decoration: none; - font-family: sans-serif; font-size: 1.2rem; color: inherit; border-radius: var(--radius-pill); @@ -127,7 +126,6 @@ nav[hidden]{ :global(a.entry:visited) { text-decoration: none; - font-family: sans-serif; font-size: 1.2rem; color: white !important; border-radius: var(--radius-pill); diff --git a/src/lib/components/SearchInput.svelte b/src/lib/components/SearchInput.svelte index 3a2383d..8f43d54 100644 --- a/src/lib/components/SearchInput.svelte +++ b/src/lib/components/SearchInput.svelte @@ -16,7 +16,6 @@ input { all: unset; box-sizing: border-box; - font-family: sans-serif; background: var(--nord0); color: #fff; padding: 0.7rem 2rem; diff --git a/src/lib/components/TagCloud.svelte b/src/lib/components/TagCloud.svelte index 356f139..c83041e 100644 --- a/src/lib/components/TagCloud.svelte +++ b/src/lib/components/TagCloud.svelte @@ -7,7 +7,6 @@ div{ margin-inline:auto; gap: 1rem; justify-content: space-evenly; - font-family: sans-serif; } diff --git a/src/lib/components/recipes/BaseRecipeSelector.svelte b/src/lib/components/recipes/BaseRecipeSelector.svelte index a27d709..b7d24d7 100644 --- a/src/lib/components/recipes/BaseRecipeSelector.svelte +++ b/src/lib/components/recipes/BaseRecipeSelector.svelte @@ -108,7 +108,6 @@ dialog[open]::backdrop { dialog h2 { font-size: 3rem; - font-family: sans-serif; color: white; text-align: center; margin-top: 30vh; diff --git a/src/lib/components/recipes/Card.svelte b/src/lib/components/recipes/Card.svelte index 01aab36..2cb9654 100644 --- a/src/lib/components/recipes/Card.svelte +++ b/src/lib/components/recipes/Card.svelte @@ -62,7 +62,6 @@ const img_alt = $derived( transition: var(--transition-normal); text-decoration: none; box-sizing: border-box; - font-family: sans-serif; cursor: pointer; height: 525px; width: 300px; diff --git a/src/lib/components/recipes/CardAdd.svelte b/src/lib/components/recipes/CardAdd.svelte index 632dff1..848a9d0 100644 --- a/src/lib/components/recipes/CardAdd.svelte +++ b/src/lib/components/recipes/CardAdd.svelte @@ -130,7 +130,6 @@ function remove_on_enter(event: KeyboardEvent, tag: string) { text-decoration: none; position: relative; box-sizing: border-box; - font-family: sans-serif; width: var(--card-width); aspect-ratio: 4/7; border-radius: var(--radius-card); diff --git a/src/lib/components/recipes/CategoryFilter.svelte b/src/lib/components/recipes/CategoryFilter.svelte index e7c056a..f295815 100644 --- a/src/lib/components/recipes/CategoryFilter.svelte +++ b/src/lib/components/recipes/CategoryFilter.svelte @@ -134,7 +134,6 @@ input { all: unset; box-sizing: border-box; - font-family: sans-serif; background: var(--nord0); color: var(--nord6); padding: 0.5rem 0.7rem; diff --git a/src/lib/components/recipes/CreateIngredientList.svelte b/src/lib/components/recipes/CreateIngredientList.svelte index 03630a8..71479b9 100644 --- a/src/lib/components/recipes/CreateIngredientList.svelte +++ b/src/lib/components/recipes/CreateIngredientList.svelte @@ -450,7 +450,6 @@ input.heading:hover{ --font_size: 1.5rem; top: -1em; left: -1em; - font-family: sans-serif; font-size: 1.5rem; background-color: var(--nord0); color: var(--nord4); @@ -471,7 +470,6 @@ input.heading:hover{ } .add_ingredient{ - font-family: sans-serif; width: 100%; display: flex; flex-direction: row; @@ -537,7 +535,6 @@ dialog .adder{ } dialog h2{ font-size: 3rem; - font-family: sans-serif; color: white; text-align: center; margin-top: 30vh; diff --git a/src/lib/components/recipes/CreateStepList.svelte b/src/lib/components/recipes/CreateStepList.svelte index 3a130d4..2235161 100644 --- a/src/lib/components/recipes/CreateStepList.svelte +++ b/src/lib/components/recipes/CreateStepList.svelte @@ -496,7 +496,6 @@ dialog .adder{ --font_size: 1.5rem; top: -1em; left: -1em; - font-family: sans-serif; font-size: 1.5rem; background-color: var(--nord0); color: var(--nord4); @@ -519,7 +518,6 @@ dialog .adder{ } .add_step p{ - font-family: sans-serif; width: 100%; font-size: 1.2rem; border-radius: var(--radius-card); @@ -550,7 +548,6 @@ dialog .adder{ } dialog h2{ font-size: 3rem; - font-family: sans-serif; color: white; text-align: center; margin-top: 30vh; diff --git a/src/lib/components/recipes/EditRecipeNote.svelte b/src/lib/components/recipes/EditRecipeNote.svelte index 36102cc..ce1a993 100644 --- a/src/lib/components/recipes/EditRecipeNote.svelte +++ b/src/lib/components/recipes/EditRecipeNote.svelte @@ -25,7 +25,6 @@ textarea { font-size: 1rem; resize: vertical; margin-top: 0.5em; - font-family: sans-serif; background-color: transparent; } textarea::placeholder { diff --git a/src/lib/components/recipes/Icon.svelte b/src/lib/components/recipes/Icon.svelte index f632f8a..6e3d370 100644 --- a/src/lib/components/recipes/Icon.svelte +++ b/src/lib/components/recipes/Icon.svelte @@ -4,7 +4,7 @@