---
url: https://docs.nemo12.com/ops/illustrations.md
description: >-
  Ảnh minh họa SVG sinh bằng thuật toán (SRC-1201): mỗi ảnh là một dòng bảng giữ
  prompt và thông số để nâng cấp về sau.
---

# Ảnh minh họa trang

SRC-1201 (chủ dự án, 03.10.2026): mọi trang có ảnh minh họa chi tiết, giàu cảm xúc, dạng SVG
sinh bằng thuật toán; mỗi ảnh là một dòng trong một bảng, dòng ấy giữ cả prompt sinh ảnh và đủ
thông số để nâng cấp về sau. Cùng ngày chủ dự án thêm: tạo lại toàn bộ ảnh minh họa theo một tiêu
chuẩn mới, dùng skill mới. Skill: `.claude/skills/page-illustration/SKILL.md`.

## Ba phần

| Phần | Ở đâu | Việc |
| --- | --- | --- |
| Bộ vẽ | `packages/illustrations` (`@nemo12/illustrations`) | `renderIllustration(spec)` trả về một chuỗi SVG; cùng spec ra cùng từng byte. Dùng chung cho web và learn. |
| Bảng | `apps/web/src/site/illustrations/registry.json` | Nguồn chuẩn, một dòng một ảnh. Bản sao trong D1: bảng `illustrations` (migration 0326), nạp bằng `scripts/seed-illustrations.sql`. |
| Khe hiển thị | `apps/web/src/site/illustrations/PageIllustration.tsx` | `App.tsx` gọi `PageIllustration` ngay dưới phần đầu trang cho MỌI trang; `ContentPage` gọi nó cho trang nội dung; `ProgramTabPage` gọi `SectionIllustration` cho từng thẻ ở tab Kết quả. |

## Cột của bảng

`id`, `pages` (các đường dẫn), `placement` (`hero` hoặc `section:<key>`), `title`, `alt_vi`,
`prompt` (câu mô tả đủ để vẽ lại hay nâng cấp ảnh, bằng thuật toán này hoặc bằng một model sinh
ảnh), `scene`, `characters` (vai + cảm xúc), `props`, `palette`, `seed`, `label`,
`generator_version`, `width`, `height`, `created_at`, `updated_at`, `status`
(`auto` = phác từ metadata trang, `tuned` = chỉnh tay, `draft`), `notes`. D1 thêm `site` và
`file_path`.

## Quyết định

* **SVG sinh lúc build, không commit.** `npm run build` của web chạy `apps/web/scripts/illustrations.mjs`
  trước `vite build`, ghi `public/art/pages/*.svg` (đã gitignore). Cái được commit là BẢNG, vì bảng
  mới là thứ người ta sửa; 729 file SVG khoảng 10 MB trong git chỉ là bản sao của bảng.
* **Dòng đã có thì không phác lại.** `--add` chỉ thêm dòng cho trang chưa có. Đổi tiêu đề trang
  không làm đỏ cổng; nâng cấp một ảnh là sửa dòng của nó rồi chạy `--sync`.
* **Một khe ở tầng route** thay vì gắn tay từng trang, nên trang mới không thể thiếu ảnh; cổng
  `illustrations.test.ts` đỏ nếu một trang prerender chưa có dòng `hero`.
* **Trang động** (`/mentors/{slug}`...) không có dòng riêng thì dùng ảnh họ `family-<đoạn đầu>`.
* **Alt tiếng Việt nằm ở chunk lười** `alts.json` để bundle chính không chở 700 câu; trong lúc chờ,
  ảnh dùng câu hỏi của trang làm alt tạm.
* Kích thước: hero 960x400 (trên điện thoại cắt giữa về 16:9), thẻ 480x300. Trung bình 12 KB,
  trần 25 KB mỗi file do cổng giữ.

## Lệnh

```bash
cd apps/web
npm run illustrations:add    # thêm dòng cho trang mới, ghi lại alts.json + seed
npm run illustrations:sync   # sau khi sửa tay một dòng
npm run illustrations        # vẽ ra public/art/pages (build tự chạy)
gh workflow run seed-data.yml -f file=scripts/seed-illustrations.sql \
  -f verify="SELECT status, COUNT(*) FROM illustrations GROUP BY status"
```

## Learn (SRC-1203)

SRC-1203 (chủ dự án, 03.10.2026): "tạo lại toàn bộ các ảnh minh họa, theo một tiêu chuẩn mới, dùng
skill mới tạo". Mọi ảnh minh họa vẽ tay của learn đã chuyển sang bộ vẽ chung, giữ nguyên tên file và
URL nên không chỗ nào gãy:

| Ảnh | Trước | Nay |
| --- | --- | --- |
| 8 ảnh NEMO WALK `/art/walk/*.svg` (hero, phone-away, after, cathedral, ngoc-son, turtle-tower, ly-thai-to, dinh-le) | SVG vẽ tay 1 tới 7 KB | sinh từ dòng `learn--walk-*`, ghi ra CẢ `apps/learn/public` và `apps/web/public` (commit, vì là file tĩnh hai app cùng phục vụ) |
| Ảnh 22 bài Speak together + thẻ NEMO WALK + bài dự phòng (`PairScene`) | 25 cảnh vẽ tay trong JSX | `Illustration` render inline dòng `learn--speak-<topic>`, bản thẻ 2:1 và bản banner |
| Ảnh trang "How a session works" (`SessionScene`) | laptop vẽ tay | dòng `learn--speak-session` |

* **Bảng riêng của learn** ở `apps/learn/src/illustrations/registry.json`, cùng cột với bảng www
  cộng `site` (`learn`) và `file_path` (URL file tĩnh, hoặc `inline:<file>#<khoá>` cho cảnh render
  trong React). id luôn bắt đầu bằng `learn--` để không đụng khoá chính của dòng www trong D1.
  Seed riêng `scripts/seed-illustrations-learn.sql` chỉ xoá và ghi dòng `site = 'learn'`.
* **Inline thay vì file** cho cảnh Speak together, vì e2e và bố cục đang dựa vào chính phần tử
  `<svg>` (`data-variant=banner`, tỉ lệ 4:1). `src/illustrations/specs.json` là bản rút gọn (không
  prompt) mà bundle chở; id gradient trong SVG có tiền tố theo từng instance nên nhiều cảnh trên một
  trang không dùng chung id.
* **Bộ vẽ 1.1.0** thêm 13 bối cảnh hội thoại (`classroom`, `living-room`, `cafe`, `park`,
  `city-street`, `shop`, `rainy-day`, `beach`, `school-gate`, `library`, `tech-room`,
  `city-country`, `video-call`), 8 cảnh Hồ Gươm (`lake-tour`, `cathedral`, `ngoc-son`,
  `turtle-tower`, `ly-thai-to`, `book-street`, `street-food`, `phone-away`), 16 đồ vật mới (điện
  thoại, ô, vali, máy ảnh, biển chỉ đường, bát phở, bánh rán, trà chanh, đàn ghi ta, bóng, thùng tái
  chế, táo, ảnh gia đình, túi mua sắm, diều, tạ), bóng thoại co giãn theo nhãn, và `gender` trên
  nhân vật: bạn nam luôn áo xanh tóc ngắn, bạn nữ luôn áo cam đỏ tóc dài, khớp hai icon đọc to.
  Mọi cảnh 1.0.0 vẫn ra đúng từng byte (đã so 730 file www), nên dòng www giữ `1.0.0`.
* **Ngoài phạm vi**: mascot và icon của `N12Art.tsx` (ngôn ngữ icon DS-001, cỡ 40px, không phải
  cảnh), hình toán học (`figures/`, `explore/`), biểu đồ, bìa sách (`packages/books`).

```bash
cd apps/learn
npm run illustrations        # vẽ /art/walk cho learn + www, ghi specs.json + seed learn
npx vitest run src/illustrations
gh workflow run seed-data.yml -f file=scripts/seed-illustrations-learn.sql \
  -f verify="SELECT site, COUNT(*) FROM illustrations GROUP BY site"
```
