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

#!/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)"