fonts: consolidate font-family to global stack, self-host subset emoji font
All checks were successful
CI / update (push) Successful in 8s
All checks were successful
CI / update (push) Successful in 8s
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.
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"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",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
|
|||||||
61
scripts/subset-emoji-font.sh
Executable file
61
scripts/subset-emoji-font.sh
Executable file
@@ -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")"
|
||||||
11
src/app.css
11
src/app.css
@@ -15,6 +15,15 @@
|
|||||||
url(/fonts/crosses.ttf) format('truetype');
|
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
|
COLOR SYSTEM
|
||||||
Based on Nord Theme with semantic naming
|
Based on Nord Theme with semantic naming
|
||||||
@@ -294,7 +303,7 @@ a:focus-visible {
|
|||||||
|
|
||||||
/* Icon badge (circular icon container) */
|
/* Icon badge (circular icon container) */
|
||||||
.g-icon-badge {
|
.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;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ nav[hidden]{
|
|||||||
:global(.site_header li>a)
|
:global(.site_header li>a)
|
||||||
{
|
{
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
@@ -127,7 +126,6 @@ nav[hidden]{
|
|||||||
:global(a.entry:visited)
|
:global(a.entry:visited)
|
||||||
{
|
{
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.7rem 2rem;
|
padding: 0.7rem 2rem;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ div{
|
|||||||
margin-inline:auto;
|
margin-inline:auto;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ dialog[open]::backdrop {
|
|||||||
|
|
||||||
dialog h2 {
|
dialog h2 {
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ const img_alt = $derived(
|
|||||||
transition: var(--transition-normal);
|
transition: var(--transition-normal);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
height: 525px;
|
height: 525px;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ function remove_on_enter(event: KeyboardEvent, tag: string) {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
width: var(--card-width);
|
width: var(--card-width);
|
||||||
aspect-ratio: 4/7;
|
aspect-ratio: 4/7;
|
||||||
border-radius: var(--radius-card);
|
border-radius: var(--radius-card);
|
||||||
|
|||||||
@@ -134,7 +134,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
|
|||||||
@@ -450,7 +450,6 @@ input.heading:hover{
|
|||||||
--font_size: 1.5rem;
|
--font_size: 1.5rem;
|
||||||
top: -1em;
|
top: -1em;
|
||||||
left: -1em;
|
left: -1em;
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
@@ -471,7 +470,6 @@ input.heading:hover{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add_ingredient{
|
.add_ingredient{
|
||||||
font-family: sans-serif;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -537,7 +535,6 @@ dialog .adder{
|
|||||||
}
|
}
|
||||||
dialog h2{
|
dialog h2{
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
|
|||||||
@@ -496,7 +496,6 @@ dialog .adder{
|
|||||||
--font_size: 1.5rem;
|
--font_size: 1.5rem;
|
||||||
top: -1em;
|
top: -1em;
|
||||||
left: -1em;
|
left: -1em;
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
background-color: var(--nord0);
|
background-color: var(--nord0);
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
@@ -519,7 +518,6 @@ dialog .adder{
|
|||||||
}
|
}
|
||||||
|
|
||||||
.add_step p{
|
.add_step p{
|
||||||
font-family: sans-serif;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
border-radius: var(--radius-card);
|
border-radius: var(--radius-card);
|
||||||
@@ -550,7 +548,6 @@ dialog .adder{
|
|||||||
}
|
}
|
||||||
dialog h2{
|
dialog h2{
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
font-family: sans-serif;
|
|
||||||
color: white;
|
color: white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 30vh;
|
margin-top: 30vh;
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ textarea {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
font-family: sans-serif;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
textarea::placeholder {
|
textarea::placeholder {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
a{
|
a{
|
||||||
font-family: "Noto Color Emoji", emoji;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
@@ -146,7 +146,6 @@
|
|||||||
|
|
||||||
input::placeholder {
|
input::placeholder {
|
||||||
color: var(--nord4);
|
color: var(--nord4);
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input:hover {
|
input:hover {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
a{
|
a{
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|||||||
@@ -308,9 +308,6 @@ function adjust_amount(string, multiplier){
|
|||||||
// No need for complex yeast toggle handling - everything is calculated server-side now
|
// No need for complex yeast toggle handling - everything is calculated server-side now
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
.ingredients{
|
.ingredients{
|
||||||
flex-basis: 0;
|
flex-basis: 0;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
|||||||
@@ -100,9 +100,6 @@ const labels = $derived({
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
ol li::marker{
|
ol li::marker{
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: var(--blue);
|
color: var(--blue);
|
||||||
|
|||||||
@@ -307,7 +307,6 @@
|
|||||||
input#search {
|
input#search {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 0.7rem 2rem;
|
padding: 0.7rem 2rem;
|
||||||
|
|||||||
@@ -120,7 +120,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
<style>
|
<style>
|
||||||
a.month{
|
a.month{
|
||||||
text-decoration: unset;
|
text-decoration: unset;
|
||||||
font-family: sans-serif;
|
|
||||||
border-radius: var(--radius-pill);
|
border-radius: var(--radius-pill);
|
||||||
background-color: var(--blue);
|
background-color: var(--blue);
|
||||||
color: var(--nord5);
|
color: var(--nord5);
|
||||||
|
|||||||
@@ -117,7 +117,6 @@
|
|||||||
input {
|
input {
|
||||||
all: unset;
|
all: unset;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
font-family: sans-serif;
|
|
||||||
background: var(--nord0);
|
background: var(--nord0);
|
||||||
color: var(--nord6);
|
color: var(--nord6);
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
|
|||||||
@@ -106,9 +106,6 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
*{
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
h1{
|
h1{
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding-block: 0.5em;
|
padding-block: 0.5em;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
<style>
|
<style>
|
||||||
a{
|
a{
|
||||||
font-family: "Noto Color Emoji", emoji, sans-serif;
|
font-family: "Noto Color Emoji", "Noto Color Emoji Subset", emoji, sans-serif;
|
||||||
--padding: 0.5em;
|
--padding: 0.5em;
|
||||||
font-size: 3rem;
|
font-size: 3rem;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
BIN
static/fonts/NotoColorEmoji.ttf
Normal file
BIN
static/fonts/NotoColorEmoji.ttf
Normal file
Binary file not shown.
BIN
static/fonts/NotoColorEmoji.woff2
Normal file
BIN
static/fonts/NotoColorEmoji.woff2
Normal file
Binary file not shown.
Reference in New Issue
Block a user