Menu Image Assets
Generate menu thumbnails by asking the image generation tool for 4x4 contact sheets, then split each sheet into per-menu-item images.
Outputs
- Final item images:
apps/betterzeriya/src/lib/assets/image/{code}.webp - UI references:
apps/betterzeriya/src/routes/sessions/[id]/+page.svelteloads images withimport.meta.glob('../../../lib/assets/image/*.webp', { eager: true, query: '?url', import: 'default' }) - Menu data: keep
apps/betterzeriya/src/lib/assets/data/menu.jsonfree of generated public URLs; useimageUrlonly for explicit external/official image URLs. - Temporary sheet files:
tmp/menu-image-sheets/sheet-001.webp, etc.
Do not reference images directly from $CODEX_HOME/generated_images. Copy or convert them into the workspace first.
Workflow
- Read
apps/betterzeriya/src/lib/assets/data/menu.json. - Chunk menu entries into groups of 16 in file order.
- For each chunk, call the built-in image generation tool once with the prompt template below.
- Find the newly generated files under
$CODEX_HOME/generated_images/<thread-id>/. - Copy or convert the generated sheets to
tmp/menu-image-sheets/sheet-001.webp,sheet-002.webp, and so on in generation order. - Split every sheet into a 4x4 grid in left-to-right, top-to-bottom order.
- Save each crop as
apps/betterzeriya/src/lib/assets/image/{code}.webp, preferably 512x512 WebP. - Ensure the UI maps each menu
codeto its generated asset throughimport.meta.glob. - Run
bun check:fixandbun run test.
Prompt Template
Use this prompt once per 16-item chunk. Normalize half-width kana in menu names with NFKC before inserting them.
Create one square contact-sheet image divided into an exact 4x4 grid of equal square cells.
Each cell is a realistic, appetizing food photo for one menu item from a generic Japanese casual Italian family restaurant.
Order is strict: fill the grid left-to-right across the top row, then the second row, third row, and bottom row.
Menu items:
1. row 1, column 1: <item 1 name>
2. row 1, column 2: <item 2 name>
3. row 1, column 3: <item 3 name>
4. row 1, column 4: <item 4 name>
5. row 2, column 1: <item 5 name>
6. row 2, column 2: <item 6 name>
7. row 2, column 3: <item 7 name>
8. row 2, column 4: <item 8 name>
9. row 3, column 1: <item 9 name>
10. row 3, column 2: <item 10 name>
11. row 3, column 3: <item 11 name>
12. row 3, column 4: <item 12 name>
13. row 4, column 1: <item 13 name>
14. row 4, column 2: <item 14 name>
15. row 4, column 3: <item 15 name>
16. row 4, column 4: <item 16 name>
Style: clean menu photography, centered dish, natural restaurant table lighting, consistent camera angle, realistic plating, no people.
Layout: 4 columns by 4 rows, equal cell sizes, each dish fully contained in its own cell with enough padding for later cropping.
Constraints: no text, no labels, no numbers, no logos, no watermarks, no brand marks, no trademarks, no restaurant signage, no mascots, no Saizeriya branding.
For any empty unused cell, leave the cell as a plain neutral tabletop with no dish and no text.
Local Split Snippet
Use a local image tool available in the repo/environment. If sharp is not installed, use bunx sharp-cli, ImageMagick, Pillow, or another available image processor. Avoid adding runtime dependencies just for a one-off split.
The split must:
- Center-crop each sheet to a square if needed.
- Divide the square into four equal columns and four equal rows.
- Map crop index
0..15to the matching menu chunk item. - Resize each output to
512x512. - Save as WebP.
After splitting, verify:
find apps/betterzeriya/src/lib/assets/image -type f -name '*.webp' | wc -l
node -e "const menu=require('./apps/betterzeriya/src/lib/assets/data/menu.json'); console.log(menu.length, menu.filter((item)=>item.imageUrl).length)"
Safety Notes
- These are generated approximations, not exact official product photos.
- Never include Saizeriya logos, signage, trademarks, or in-image menu text.
- Keep original generated files in
$CODEX_HOME/generated_images; do not delete them unless explicitly asked.