# Deck Image Generation Pipeline

Pattern for generating AI images for HTML pitch decks via local ComfyUI, then integrating them with visual QA.

## When to use

- User asks for images to fill a premium HTML deck/pitch/presentation
- User wants ComfyUI-generated visuals integrated into an existing HTML deck
- User requests a specific DA (art direction) or model type for deck images

## Pipeline stages

### 1. Start ComfyUI and verify

```bash
env -u VIRTUAL_ENV comfy --workspace <ComfyUI> launch -- --listen 127.0.0.1 --port 8188
# Wait for /system_stats to respond
curl -s http://127.0.0.1:8188/system_stats | python3 -m json.tool
```

### 2. Choose model based on quality needs

| Model | Quality | Speed | Best for |
|-------|---------|-------|----------|
| SD 1.5 | Low | Fast | Abstract/mood, NOT portraits |
| Z-Image Turbo | Medium | Fast (9 steps) | Abstract UI, timelines, dashboards |
| Nano Banana 2 | High | Slow (20-30s/img) | Portraits, people, editorial, specific ethnicity/styling |

For decks with people/portraits: use Nano Banana 2. For abstract/UI/mood: Z-Image Turbo or SD1.5 acceptable.

### 3. Generate test image first

Always generate ONE image and QA it before batch generation. This catches:
- Wrong model/Python mismatch (Z-Image DF11)
- API key issues (Nano Banana 2 partner key)
- Prompt quality / art direction mismatch
- Resolution/aspect ratio problems

### 4. Batch generation

Generate all images sequentially. Use `terminal(background=true, notify_on_complete=true)` for the batch script. Each image should have:
- A descriptive `filename_prefix` matching its deck slot
- Proper `aspect_ratio` matching the deck layout (4:5 for tall, 4:3 for wide)
- `resolution: 2K` for Nano Banana 2 quality

### 5. Visual QA before integration (MANDATORY)

**Never skip this step. Never deliver images without visual analysis.**

1. Create a contact sheet (PIL thumbnail grid) of all generated images
2. Use `vision_analyze` to classify each: KEEP / REGENERATE / USE BLURRED
3. Check for: fake text/glyphs, bad anatomy, distorted faces, off-brand look, explicit content, generic stock-photo feeling
4. Regenerate rejected images with stricter negative prompts
5. For UI/dashboard images with fake text: blur with `PIL GaussianBlur` rather than rejecting

### 6. Prepare web assets

```python
from PIL import Image, ImageEnhance, ImageFilter
# Convert PNG to optimized JPG
# Apply blur to UI-type images with fake text
# Apply slight color/contrast enhancement
im.save(out_dir / f'{name}.jpg', quality=91, optimize=True)
```

### 7. Integrate into HTML deck

- Create a new HTML file (don't overwrite the previous version)
- Use `object-fit: cover` for output images, `object-fit: contain` for input/reference images
- Remove image labels/captions if user requests clean look
- Add `filled` class to image-slots with real images

### 8. Final deck QA

1. Render all slides as screenshots via headless Chrome
2. Create a contact sheet of all slides
3. Visually verify: no cutoffs, no overlaps, images loading, layout consistent
4. Check both desktop (1280x720) and mobile (390x844)
5. Verify public URL serves correct assets

## Common issues

- **Image cropped badly**: use `object-fit: contain` instead of `cover` for portrait images in landscape containers
- **Footer overlapping images**: reduce `min-height` on image containers or hide footer on that slide
- **Title too large**: use `clamp()` with smaller max values and `max-width` in `ch` units
- **Fake text in generated UI**: blur the image or regenerate with "no text, no letters, no numbers" in negative prompt
- **Identity drift across outputs**: Nano Banana 2 keeps general vibe but not pixel-perfect identity; acceptable for demo decks

## HTML deck slide management

When adding or removing slides from the deck HTML, update ALL of these:

1. **Slide counters** — each slide footer shows `NN / TOTAL`. Use
   `sed -i 's|0X / OLD|0X / NEW|g'` for each slide, or a global replace.
2. **Nav jump attributes** — `data-jump="N"` on prev/next buttons must point
   to the correct 0-indexed slide position. Adding a slide between slides 8
   and 9 means: new slide gets index 9, old slide 9 becomes 10, all jumps
   pointing to the old 9 must now point to 9 (new) or 10 (old moved down).
3. **Progress bar formula** — the CSS `width: calc(var(--i) / N * 100%)`
   uses N = total slides minus 1. If total goes from 10 to 11, change `/ 9`
   to `/ 10`.
4. **Serving assets** — if the deck references images in `deck_assets/` but
   the FastAPI server mounts `static/` from a different directory, add:
   - A `/deck_assets/{filepath:path}` route serving from the assets dir
   - A catch-all `/{filename:path}` route at the END (after all other routes)
     serving from the directory where the deck HTML lives
   - Or create symlinks: `ln -sf /path/to/deck.html static/` and
     `ln -sf /path/to/deck_assets static/deck_assets`

## Serving decks publicly via Tailscale

If ComfyUI and the deck server run on a machine with Tailscale:

```bash
# Enable Tailscale Funnel for public HTTPS access
sudo tailscale funnel --bg --https=443 http://127.0.0.1:7870
# Public URL: https://<machine-name>.<tailnet>.ts.net/
```

Verify both the deck HTML and all asset paths return 200:
```bash
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:7870/deck.html
curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:7870/deck_assets/path/to/image.png
```

## Portrait enhancement from real photos (Nano Banana 2)

To "pimp" a real team/person photo into a premium studio portrait:

1. Upload the reference photo to ComfyUI:
   ```python
   # POST /upload/image with multipart form data
   # Use a UNIQUE filename to avoid the overwrite pitfall (pitfall #19)
   ```

2. Build the workflow with LoadImage → GeminiNanoBanana2:
   ```python
   wf = {
     '0': {'class_type': 'LoadImage', 'inputs': {'image': 'unique_name.jpg'}},
     '1': {'class_type': 'GeminiNanoBanana2', 'inputs': {
       'prompt': 'Using the reference person as identity, create a premium '
                 'professional studio portrait. Black background, dramatic '
                 'cinematic lighting with warm gold rim light, dark elegant '
                 'outfit, confident pose, magazine quality.',
       'model': 'Nano Banana 2 (Gemini 3.1 Flash Image)',
       'seed': random.randint(1, 2**31-1),
       'aspect_ratio': '4:5',
       'resolution': '2K',
       'response_modalities': 'IMAGE',
       'thinking_level': 'MINIMAL',
       'images': ['0', 0]
     }},
     '2': {'class_type': 'SaveImage', 'inputs': {'images': ['1', 0], 'filename_prefix': 'team_pro/name'}}
   }
   ```

3. Submit with `extra_data.api_key_comfy_org` (partner key).

4. QA each output — Nano Banana 2 may drift identity details. Generate 2-3
   variants per person if the first isn't close enough.

5. If uploading multiple people, upload all reference photos first, then
   generate sequentially. The upload step is fast; generation is the bottleneck.

**Portrait/editorial:**
```
premium editorial photograph of a [specific ethnicity] creator/model,
[warm olive/tan skin, dark eyes, long dark wavy hair],
[wardrobe: elegant black silk tailored outfit],
[luxury studio, charcoal background, gold and violet cinematic rim light],
[confident, premium, tasteful, non-explicit],
[vertical upper-body fashion campaign, natural skin texture]
Avoid: text, logos, watermark, fake UI, bad hands, distorted anatomy,
nudity, explicit pose, plastic skin, generic stock-photo feeling
```

**Abstract/mood:**
```
abstract premium visualization of [concept],
glowing [elements], [color palette] lights,
luxury dark interface, cinematic, no readable text, no numbers
```

**Identity reference (Nano Banana 2):**
```
Using the reference woman as identity inspiration, [describe new scene].
Preserve the same woman's identity cues: [ethnicity, skin, hair, eyes].
[Scene description, wardrobe, setting, mood].
Avoid: [standard negative prompt]
```
