Files
hoelee-blog/scripts/og-gen
hoelee 0dae8b0daa
Deploy / build (push) Successful in 34s
fix(art): give both 2026-10-20 posts their artwork, and gate the build on it
The YuE2 and subtitles posts shipped text-only: render_from_art.mjs only READS
ogImage/banner, and after the 2026-10-19 bulk migration no script had to write
them, so both pages advertised /og-default.jpg and rendered no hero banner.

- new metaphors (in build_captions.py, frozen here via sync_prompts_catalog.py),
  captions, art-meta evidence, banner plan and the five derived files per slug
- frontmatter: ogImage + banner in EN and ZH for both posts
- scripts/check-assets.mjs is now step 1 of npm run build, so CI fails when a
  published post lacks a language-correct og card, banner, banner .webp or
  400/800 list thumb. It reported 16 problems on the unfixed tree, all here.
2026-10-20 23:37:52 +08:00
..

og-gen — per-post OG image generator

Generates a branded 1200×630 social-share image for a blog post, matching the site's cobalt-blue #295cff branding, the logo-square.png mark, and the "terminal / debugging" visual style.

Usage

node scripts/og-gen/generate.mjs <post-slug>

Reads frontmatter (title, category, tags) from src/content/posts/<slug>.md, fills template.html, renders with headless Chrome, and writes public/og/<slug>.png.

The post's frontmatter needs ogImage: /og/<slug>.png so the blog serves it (layout already renders og:image + width/height; PostList shows it as a card thumbnail).

Customizing the terminal box

Each post shows a "terminal" panel (the red error / green fix lines). Add an entry keyed by slug in TERMINALS inside generate.mjs, using these tokens:

<div class="line"><span class="prompt">$</span><span class="cmd">command</span></div>
<div class="line"><span class="prompt">&nbsp;</span><span class="err">error line</span></div>
<div class="line"><span class="prompt">$</span><span class="cmd">fix</span><span class="fix">→ done ✓</span></div>

No entry → a generic default terminal.

Requirements

  • Google Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe (override with CHROME_PATH env var).
  • Node 20+.

Notes

  • PNG (not JPG) is intentional: for flat UI/terminal art, PNG is smaller and sharper than JPG (see commit history).
  • The logo-square.png referenced lives at public/logo-square.png.

v2 pipeline ("one art, four targets") — render_from_art.mjs

The consultant-approved redesign replaces the generated terminal panels with one zero-text base-art frame per post, composited into four assets:

Target Path Size
hero banner public/banners/<slug>.webp (+ .jpg fallback from frontmatter) 1600×900
social card public/og/<slug>.jpg (EN), public/og/zh-<slug>.jpg (ZH) 1200×630
bento/list thumb public/og/thumb/<slug>-800.webp 800×420
small thumb public/og/thumb/<slug>-400.webp 400×210

The card is template-v2.2.html (frosted bento + blueprint grid + full-width evidence bar) filled from the post's own frontmatter plus art-meta.json; the self-fitting headline script reports FIT <n>px through <title>, and a render that does not settle fails instead of shipping a silently-shrunken card.

# metadata first: art path (default <art-dir>/<slug>.png) + a HAND-WRITTEN evidence line
$EDITOR scripts/og-gen/art-meta.json

node scripts/og-gen/render_from_art.mjs --only <slug>[,<slug>] --out /tmp/og-check
node scripts/og-gen/render_from_art.mjs --all            # writes into public/
  • --format jpg (default) or png. JPEG is the default because it is measured: a composited card renders 558 KB as PNG and lands at 84 KB at mozjpeg q85 (256-colour PNG = 240 KB). The default site card already ships as JPG for the same reason.
  • evidence is required and never derived — it is editorial text, and inventing one from the description is the "invented specificity" failure the blog avoids. A missing entry aborts with the slug list.
  • ogTitle_en / ogTitle_zh shorten only the card's headline (the article keeps its real title) — the consultant's answer to the English headline-rag problem.
  • Reproducibility check: re-rendering the two sample posts produced banners and 800px thumbs byte-identical to the consultant's accepted delivery (sha256 16e6a7b0…, f3b29f38…, c4e28291…, 8095ec6c…).
  • ⚠ Changing ogImage from .png to .jpg also needs PostList.astro's thumb helper updated (/\.(png|jpe?g)$/), or the listing derives …-800.webp from slug.jpg and every thumb 404s.