chore: add scripts/generate-og-image.sh to regenerate the OG card

Reproducible ImageMagick script that rebuilds static/og-preview.jpg from the
headshot + theme colours (with serif-font fallbacks so it runs on other
machines). Exposed as `npm run og:image`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 10:57:36 -07:00
parent 6ff99e0d51
commit 6f17cba9c5
2 changed files with 86 additions and 1 deletions
+2 -1
View File
@@ -14,7 +14,8 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"test": "vitest run",
"test:watch": "vitest"
"test:watch": "vitest",
"og:image": "./scripts/generate-og-image.sh"
},
"devDependencies": {
"@sveltejs/adapter-auto": "^7.0.0",
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
#
# Generate the Open Graph / Twitter link-preview card.
#
# Produces a 1200x630 branded card (the summary_large_image spec) from the
# headshot + site theme, written to static/og-preview.jpg. Re-run this whenever
# the headshot or branding changes, then redeploy and re-scrape the social
# crawlers (see the OG meta tags in src/routes/+layout.svelte).
#
# Requires ImageMagick 7 (`magick`). Run from anywhere; paths are repo-relative.
#
# Usage: ./scripts/generate-og-image.sh
set -euo pipefail
# --- locate repo + inputs ---------------------------------------------------
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
SRC_HEADSHOT="static/headshot.jpg"
OUT="static/og-preview.jpg"
if ! command -v magick >/dev/null 2>&1; then
echo "error: ImageMagick 7 (magick) not found on PATH" >&2
exit 1
fi
if [[ ! -f "$SRC_HEADSHOT" ]]; then
echo "error: $SRC_HEADSHOT not found" >&2
exit 1
fi
# --- pick a bold + regular serif font (with fallbacks) ----------------------
pick_font() {
for f in "$@"; do
[[ -f "$f" ]] && { echo "$f"; return 0; }
done
# Last resort: ask fontconfig for any serif.
fc-match -f '%{file}' serif 2>/dev/null || true
}
SERIF_B="$(pick_font \
/usr/share/fonts/TTF/DejaVuSerif-Bold.ttf \
/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf \
/usr/share/fonts/liberation/LiberationSerif-Bold.ttf)"
SERIF="$(pick_font \
/usr/share/fonts/TTF/DejaVuSerif.ttf \
/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf \
/usr/share/fonts/liberation/LiberationSerif-Regular.ttf)"
: "${SERIF_B:?no bold serif font found}"
: "${SERIF:?no serif font found}"
# --- theme colours (mirror src/lib/theme.ts) --------------------------------
GOLD='#ffd700'
GOLD_SOFT='#e8c95a'
MUTED='#a8a8b8'
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
# 1) Dark radial-gradient base (matches the #05050a space theme)
magick -size 1200x630 radial-gradient:'#16162c'-'#05050a' "$TMP/base.png"
# 2) Circular-crop the headshot to 300x300
magick "$SRC_HEADSHOT" -resize 300x300^ -gravity center -extent 300x300 \
\( -size 300x300 xc:none -fill white -draw "circle 150,150 150,0" \) \
-alpha set -compose CopyOpacity -composite "$TMP/headshot_circle.png"
# 3) Composite headshot (centre 330,315) + gold ring + faint outer ring
magick "$TMP/base.png" "$TMP/headshot_circle.png" -geometry +180+165 -compose over -composite \
-fill none -stroke "$GOLD" -strokewidth 5 -draw "circle 330,315 330,165" \
-stroke "$GOLD" -strokewidth 2 -draw "fill-opacity 0.4 circle 330,315 330,157" \
"$TMP/with_headshot.png"
# 4) Text block (right side) + divider rule
magick "$TMP/with_headshot.png" \
-font "$SERIF_B" -fill "$GOLD" -pointsize 92 -gravity NorthWest -annotate +540+205 'Kevin Riehl' \
-stroke "$GOLD" -strokewidth 2 -draw "line 545,330 980,330" -stroke none \
-font "$SERIF" -fill "$GOLD_SOFT" -pointsize 34 -kerning 6 -annotate +547+360 'THE ATLAS OF SKILLS' \
-font "$SERIF" -fill "$MUTED" -pointsize 26 -kerning 0 -annotate +547+420 'Interactive skill-tree portfolio' \
-fill "$MUTED" -pointsize 26 -annotate +547+455 'Experience · Projects · Skills' \
"$TMP/card.png"
# 5) Flatten to an optimised, progressive JPG
magick "$TMP/card.png" -strip -interlace Plane -quality 90 "$OUT"
echo "wrote $OUT ($(magick identify -format '%wx%h, %B bytes' "$OUT"))"