feat(art): replace all 72 posts' artwork with the v2 pipeline
Deploy / build (push) Successful in 17s

Every post now carries one unique local-ComfyUI/Ideogram 4 base frame, composited
into EN + ZH share cards, a banner and list thumbs.

- the shipped prompts_catalog held only 21 distinct metaphors for 72 posts (16
  shared one visual), so all 72 were rewritten as unique object+state metaphors
  and 10 frames re-rolled after OCR found pseudo-text rendered at legible size
- render_from_art.mjs emits JPEG by default (558 KB PNG -> 84 KB mozjpeg q85);
  the ZH 1200x630 card did not exist before this batch
- 288 frontmatter fields migrated; the 144 v1 PNGs are gone
- PostList's thumb helper keys off the base slug (a ZH card is zh-<slug>.jpg while
  the thumb is <slug>-800.webp, which 404'd the whole ZH listing)
- [...slug].astro's <picture> regex accepts .jpg
- the repo now carries the art inputs (metaphors.json, captions/<slug>.json,
  refreshed prompts_catalog.json) so a frame is reproducible from git alone
This commit is contained in:
hoelee committed 2026-10-19 04:18:59 +08:00
1 parent 72a9a2af6f
commit 327d516ed9
802 files changed
+1289 -519

No files matched your search

+25 -9
View File
@@ -94,7 +94,7 @@ Living list of what's done and what's next for blog.hoelee.com. Work through the
### Phase F — Artwork rollout: v2 "one art, four targets" (NEXT; user decision 2026-10-18)
**Step B6b — Replace all 72 posts' artwork with the consultant-approved v2 set.** 🔵 Ready to run; blocked on a GPU window + three answers
**Step B6b — Replace all 72 posts' artwork with the consultant-approved v2 set.** ✅ Rendered and staged 2026-10-19 (working tree; **not pushed** — awaiting the owner's read)
Everything below is measured, not planned. A fresh session needs no other context to continue.
@@ -103,13 +103,29 @@ Everything below is measured, not planned. A fresh session needs no other contex
| The renderer | `scripts/og-gen/render_from_art.mjs` (committed `a41448b`) — one zero-text base frame → banner 1600×900 (`banners/<slug>.webp` + `.jpg` fallback), social card 1200×630 (`og/<slug>.jpg` EN, `og/zh-<slug>.jpg` ZH), thumbs `og/thumb/<slug>-{800,400}.webp` |
| The layout | `scripts/og-gen/template-v2.2.html` — frosted 736×536 bento card, blueprint grid, full-width evidence bar, self-fitting headline that reports `FIT <n>px` through `<title>`; a card that never settles fails the run |
| Per-post inputs | `scripts/og-gen/art-meta.json` — `art` (default `<art-dir>/<slug>.png`), `evidence` (**required, hand-written**), optional `ogTitle_en/zh`, optional `tags` |
| Prompts | `scripts/og-gen/prompts_catalog.json` — **72** entries (one visual metaphor + prompt per post) |
| Base art on disk | **2 of 72** — `C:/AI/ideogram4/out/sample_prod_169_00001_.png`, `sample_oom_169_00001_.png` (the consultant samples). **70 still to generate, and this is the GPU half of the job** |
| Evidence lines written | **2 of 72** (`running-production-infrastructure-solo`, `docker-reported-exit-0-the-oom-killer-disagreed`) |
| Live site | still the **v1** set: 72 `public/banners/*.png` + 72 `public/og/*.png` from the terminal-panel generators (2026-10-06…10-16). Nothing a visitor sees has changed yet |
| Prompts | `scripts/og-gen/prompts_catalog.json` — **72** entries, regenerated 2026-10-19 after the metaphor rewrite (the shipped file had only **21 distinct metaphors for 72 posts**: 16 shared one visual, 15 another, 10 another) |
| Base art on disk | **72 of 72** — `C:/AI/ideogram4/out/<slug>.png`, 1360×768, produced 2026-10-19 (batch 4.5 h, ~220 s/frame, 20 steps) |
| Evidence lines written | **72 of 72** (`scripts/og-gen/build_art_meta.py` holds all 72, each quoting a number/log line/command from its own post) |
| Rendered targets | 72 EN cards + 72 ZH cards + 72 banner `.jpg` + 72 banner `.webp` + 144 thumbs, all in `public/`; the 72 v1 `public/og/*.png` and 72 v1 `public/banners/*.png` were deleted |
| Frontmatter | 288 fields migrated by `scripts/og-gen/migrate_to_v2_assets.py --write` (144 files: EN `ogImage: /og/<slug>.jpg`, ZH `ogImage: /og/zh-<slug>.jpg`, both `banner: /banners/<slug>.jpg`) |
| Component changes | `PostList.astro` thumb helper now derives **from the base slug** (a ZH card is `zh-<slug>.jpg` while the thumb is `<slug>-800.webp`); `[...slug].astro` `<picture>` regex accepts `.jpg` |
| Live site | unchanged — still serving v1 (nothing pushed) |
| Reference delivery | `C:/Users/hoelee/Desktop/blog-og-image-redesign-brief/delivery-for-consultant-round4/` (+ `00-第四轮-实施反馈与3个待定细节.md` for the three open questions) |
**What the GPU run has to produce.** 70 zero-text 16:9 frames (1360×768 or 1920×1080), one per post, from that post's entry in `prompts_catalog.json` — Ideogram 4 pipeline (`C:/AI/ideogram4/`) or ComfyUI on the Win11 VM's RTX 3060. Hard rules from the consultant: **no text, letters, numbers, HUD or watermark anywhere in the art** (put them in `art-meta.json` under `art` and drop them into `C:/AI/ideogram4/out/<slug>.png`, which is the renderer's default path).
**Rollout log — 2026-10-19 (every number measured this session).**
1. **The prompt set was not usable as shipped.** `prompts_catalog.json` carried **21 distinct metaphors for 72 posts** (16 posts shared "concentric acrylic rings", 15 shared "rackmount hardware", 10 shared "diagnostic probe"), so the batch would have produced visibly duplicated art across a third of the site. Rewrote one concrete object + state per post → **72/72 unique**, and generated the 72 structured JSON captions with `C:/AI/ideogram4/build_captions.py` (asserts the slug set matches `src/content/posts/*.md` and that no two metaphors are identical).
2. **GPU run.** 72 frames at 1360×768 / 20 steps / `--preset default`, ~220 s each, **4.5 h** total, resumable via `C:/AI/ideogram4/run_batch.py --all` (skips slugs whose frame exists). QC: 72/72 present, all exactly 1360×768, left-60 % mean luminance 10.2–23.1 (the space the card's text sits on).
3. **Caption defect found and fixed (3 of 3 pilot frames).** With the subject described twice — `high_level_description` repeating the metaphor — the model drew a labelled diagram with a pseudo-title in the corner (`Oypert`, `Oyristes tool`, `CONTRACT`). One short noun phrase in the HLD + the subject only in `elements[].desc` cleared it.
4. **The "no text anywhere" rule fails on the accepted samples too.** `sample_oom_169_00001_.png` OCRs as `Kenel L.aser` @0.874 plus six more hits. So the gate judges **legibility**, not hit count: a glyph box under ~14 px on the 1360×768 frame is invisible at banner (1600×900) and thumb (398 px) size.
5. **The model renders the prompt's own label nouns as text.** Measured: `access-denied barrier` → "ACCCSS-DENIED", `attachment clip` → "Attachment Clip", `eight labelled ports` → a 389 px glyph, `id-stamped cards` → "ATACHMENT CLIP", `233 fresh rivets` → a row of numbers, `SSD slab` → a 66 px "SSD", `PLUGIN cartridge` → "PLUGIN". Rewriting the metaphor to describe shape/material/state only fixed every one of them on re-render.
6. **Result after two correction rounds:** 45/72 frames fully clean, 18 with a single sub-threshold tick mark, and the 10 frames that exceeded the accepted reference all brought down to clean or reference level. Gate + tooling: `C:/AI/ideogram4/ocr_gate.py` (`--baseline` prints the accepted samples' numbers), `qc_frames.py` (per-frame stats + unlabelled contact sheets).
7. **Delivery staged, not shipped.** Rendered 72 EN + 72 ZH cards, 144 banner files, 144 thumbs into `public/`; deleted the 144 v1 PNGs; migrated 288 frontmatter fields; fixed the two components. `npm run build` → **251 pages**, 216 distinct image references, **0 missing, 0 stale `.png`**; the ZH listing's thumbs resolve from the base slug (the old helper would have 404'd every one of them).
8. **Art weight: 13.4 MB → 15.4 MB (+2.0 MB), and it buys 72 ZH cards that did not exist before.** Per file: EN card 67 KB JPEG (v1 was 71 KB PNG), banner 40 KB JPEG + 30 KB WebP, thumb 7 KB WebP.
**Two observations for the owner** (not defects, both open):
- The hero banner carries no text overlay, so the art's left 60 % negative space reads as dark band on a dark page; on `your-disk-full-alert-is-lying` the subject occupies ~29 % of the banner width. A banner-side crop toward the subject (the card already scales the art 116 %) would roughly double its presence. Not applied — it changes the framing the consultant accepted in round 4.
- Several frames are intentionally very dark; at full banner width the subject can read as small.
**Three decisions still open** (asked of the consultant in round 4 §二; the owner may simply pick the defaults):
1. English headline rag → **default: (C) shorten the card headline only**, via `ogTitle_en` in `art-meta.json` (article title untouched).
@@ -135,9 +151,9 @@ Everything below is measured, not planned. A fresh session needs no other contex
- Tags/banner: `python <skill>/scripts/measure_og_banner.py <slug> <repo>` → `rows==1`, `overflow==0`, banner `delta<=3` — it only covers the tag row and terminal panel, not the headline.
- After deploy: every `/og/<slug>.jpg`, `/og/zh-<slug>.jpg`, `/banners/<slug>.{webp,jpg}`, `-800.webp` returns 200 **with cache-busting** (Cloudflare caches a negative 404 for 16 days).
**Known gaps in the plan**
- The **OCR pseudo-text gate** promised to the consultant (auto-regenerate a frame that contains readable characters) is **not implemented** — it has to be written or the promise withdrawn.
- `art-meta.json` is committed with only the two sample entries; the 70 others are the work.
**Known gaps in the plan — updated 2026-10-19**
- ✅ The **OCR pseudo-text gate** promised to the consultant is **implemented**: `C:/AI/ideogram4/ocr_gate.py`, calibrated against the two accepted samples (`--baseline`). Judge legibility (glyph box ≥ 14 px on the 1360×768 frame), never raw hit count — the accepted reference has a readable phrase in it.
- ✅ `art-meta.json` now carries all 72 entries; `scripts/og-gen/build_art_meta.py` regenerates them and re-checks coverage against `src/content/posts/*.md` before the renderer runs.
- Extra thumbs: the renderer emits 400 px and 800 px; nothing else consumes them.
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Loaded 100 of 802 files, more files were not shown because too many files have changed in this diff. Show more