You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
121 lines
4.8 KiB
121 lines
4.8 KiB
#!/usr/bin/env bash |
|
# Regenerate assets/theme/local/og-image.jpg (1200×630, standard OG; JPEG for smaller file than PNG24). |
|
# Layout: ~45% left = painting full-bleed (no card frame); ~55% right = warm panel + type. |
|
# Soft gradient seam (no hard divider); headline + subheading + description from config/unfold.yaml. |
|
# Bottom-right: default theme mark (favicon) at reduced opacity. |
|
# Requires: ImageMagick (convert), fold. Run from repository root: |
|
# ./scripts/build-og-image.sh |
|
|
|
set -euo pipefail |
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)" |
|
cd "$ROOT" |
|
|
|
YAML="${ROOT}/config/unfold.yaml" |
|
LOGO="${ROOT}/assets/laeserin_logo.png" |
|
DEFAULT_MARK="${ROOT}/assets/theme/default/icons/favicon-96x96.png" |
|
OUT="${ROOT}/assets/theme/local/og-image.jpg" |
|
FONT_TITLE="/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" |
|
FONT_SUB="/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" |
|
FONT_BODY="/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" |
|
|
|
W=1200 |
|
H=630 |
|
# Safe inset for *type* and corner mark (previews crop edges); painting still bleeds left. |
|
PAD=48 |
|
# Left column width (45%). |
|
LW=$((W * 45 / 100)) |
|
# Text starts after seam blend region. |
|
TEXT_X=618 |
|
# Vertical start for headline (shifted down for balance; sub/body computed from headline lines). |
|
TITLE_Y=108 |
|
|
|
if [[ ! -f "$LOGO" ]]; then |
|
echo "Missing logo: $LOGO" >&2 |
|
exit 1 |
|
fi |
|
|
|
yaml_scalar() { |
|
local key="$1" |
|
grep -E "^[[:space:]]*${key}:" "$YAML" | head -1 | sed -E "s/^[[:space:]]*${key}:[[:space:]]*['\"](.*)['\"].*/\1/" |
|
} |
|
|
|
NAME="$(yaml_scalar name)" |
|
DESC="$(yaml_scalar description)" |
|
HEADLINE="$(yaml_scalar og_headline)" |
|
SUBHEAD="$(yaml_scalar og_subheading)" |
|
[[ -z "$HEADLINE" ]] && HEADLINE="$NAME" |
|
[[ -z "$SUBHEAD" ]] && SUBHEAD="$NAME" |
|
|
|
if [[ -z "$NAME" || -z "$DESC" ]]; then |
|
echo "Could not read name/description from $YAML" >&2 |
|
exit 1 |
|
fi |
|
|
|
mkdir -p "$(dirname "$OUT")" |
|
|
|
TMP="$(mktemp -d)" |
|
trap 'rm -rf "$TMP"' EXIT |
|
|
|
mapfile -t _OG_LINES < <(fold -s -w 44 <<<"$DESC") |
|
DESC_MULTILINE=$(printf '%s\n' "${_OG_LINES[@]}") |
|
|
|
mapfile -t _HL_LINES < <(fold -s -w 22 <<<"$HEADLINE") |
|
HEAD_MULTILINE=$(printf '%s\n' "${_HL_LINES[@]}") |
|
|
|
# Right panel: deeper warm linen (reads better on dark-mode surfaces than very pale beige). |
|
CANVAS='#d8cdc0' |
|
convert -size "${W}x${H}" xc:"$CANVAS" "$TMP/canvas.png" |
|
|
|
# Left: full-bleed cover crop; richer reds / saturation, slightly darker mids for depth. |
|
convert "$LOGO" -fuzz 8% -trim +repage \ |
|
-resize "${LW}x${H}^" -gravity center -extent "${LW}x${H}" \ |
|
-gamma 0.96 -modulate 93,138,104 -unsharp 0x0.6+0.52+0.014 \ |
|
"$TMP/left.png" |
|
|
|
convert "$TMP/canvas.png" "$TMP/left.png" -geometry +0+0 -compose over -composite "$TMP/base.png" |
|
|
|
# Full-width wash: transparent at the far left → gentle panel tone on the right. |
|
# Horizontal blur removes a visible “stripe” at the old painting/canvas boundary (LW). |
|
# Text is drawn after this step so it stays crisp on top. |
|
# Lighter wash than before so bodice reds survive; tint anchors toward the darker canvas. |
|
convert -size "${W}x${H}" 'gradient:rgba(216,205,192,0)-rgba(175,158,142,0.26)' "$TMP/seam_raw.png" |
|
# Blur mostly horizontally (rotate trick) so the ramp has no sharp column. |
|
convert "$TMP/seam_raw.png" -rotate 90 -blur 0x42 -rotate -90 "$TMP/seam_blur.png" |
|
convert "$TMP/base.png" "$TMP/seam_blur.png" -compose over -composite "$TMP/blended.png" |
|
|
|
# Stack subheading / body under multi-line headline. |
|
HL_COUNT=${#_HL_LINES[@]} |
|
LINE_H=58 |
|
SUB_Y=$((TITLE_Y + HL_COUNT * LINE_H + 20)) |
|
BODY_Y=$((SUB_Y + 42)) |
|
|
|
# Type colours tuned for the darker panel. |
|
convert "$TMP/blended.png" \ |
|
-font "$FONT_TITLE" -pointsize 52 -fill '#14100e' -annotate +${TEXT_X}+${TITLE_Y} "$HEAD_MULTILINE" \ |
|
-font "$FONT_SUB" -pointsize 27 -fill '#6e2218' -annotate +${TEXT_X}+${SUB_Y} "$SUBHEAD" \ |
|
-font "$FONT_BODY" -pointsize 23 -fill '#2a221c' -interline-spacing 6 \ |
|
-annotate +${TEXT_X}+${BODY_Y} "$DESC_MULTILINE" \ |
|
PNG24:"$TMP/with_type.png" |
|
|
|
# Default newsroom mark — tinted, low opacity so it belongs in the corner. |
|
if [[ -f "$DEFAULT_MARK" ]]; then |
|
# Circular mark (soft vs a square stamp on the OG card). |
|
convert "$DEFAULT_MARK" -resize 88x88 \ |
|
\( -size 88x88 xc:none -fill white -draw "circle 44,44 44,0" \) \ |
|
-compose DstIn -composite -alpha set \ |
|
-modulate 105,85,95 \ |
|
-channel A -evaluate multiply 0.48 +channel \ |
|
"$TMP/mark.png" |
|
convert "$TMP/with_type.png" \( "$TMP/mark.png" \) -gravity SouthEast -geometry +${PAD}+${PAD} -compose over -composite "$TMP/marked.png" |
|
else |
|
cp "$TMP/with_type.png" "$TMP/marked.png" |
|
fi |
|
|
|
# Global: a touch darker + more saturated so the whole card holds up on dark UI chrome. |
|
# JPEG (not PNG24): social previews are fine with lossy compression; much smaller on-disk. |
|
convert "$TMP/marked.png" -modulate 96,118,102 -unsharp 0x0.55+0.48+0.012 \ |
|
-quality 86 -sampling-factor 4:2:0 -strip "$OUT" |
|
|
|
rm -f "${ROOT}/assets/theme/local/og-image.png" |
|
|
|
echo "Wrote $OUT ($(wc -c <"$OUT") bytes)"
|
|
|