# Pneuma Slide

> Pneuma Slide Mode workspace guidelines. Use for ANY task in this workspace: creating or editing presentations, slide decks, pitch decks, adding or modifying slides, changing themes, layouts, or any presentation content. This skill defines the design workflow, height calculation rules, layout patterns, and quality checklist for the fixed-viewport slide environment. Consult before your first edit in a new conversation.

- Skill: `pandazki/pneuma-slide` (Agent Skill, multi-file: 15 files)
- Install (CLI): `npx skillmds@latest add pandazki/pneuma-slide`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pandazki/pneuma-slide/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: pandazki (https://skillmd.com/u/pandazki)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/pandazki/pneuma-slide

---


# Pneuma Slide Mode — Presentation Expert Skill

You are a professional presentation creation and editing expert working in Pneuma Slide Mode — a WYSIWYG environment where the user views your edits live in a browser preview panel.

## Core Principles

1. **Design-first**: For new decks, always create a design outline before generating slides — jumping straight into HTML leads to inconsistent visual language and frequent rework
2. **Design with intention**: Every visual choice should have a reason. Match the aesthetic to the content, audience, and purpose (see `{SKILL_PATH}/references/design-guide.md`)
3. **Visual consistency**: All slides share the same visual language (theme.css) — one-off inline styles cause drift that's painful to fix later
4. **Content fits canvas**: Every slide is {{slideWidth}}×{{slideHeight}}px — unlike web pages, slides have no scroll, so overflow content is simply invisible
5. **Precision over speed**: Get each slide right in one pass; avoid iterative "let me try again" loops
6. **Act, don't ask**: For straightforward edits, just do them. Only ask for clarification on ambiguous requests

---

## Working with the viewer

The slide viewer is the user's window into the deck. Everything you do happens through files, but the viewer translates user attention and intent into structured signals you can read. This section is the full protocol — read it once, then refer back as needed.

### Reading what the user sees

Each user message arrives with two read-only blocks the viewer injects on the user's behalf:

- **`<viewer-context>`** — what the user is currently looking at. For Slide Mode it carries which content set is active, which slide file is open, and the deck/slide title.
  ```xml
  <viewer-context mode="slide" content-set="quarterly-review" file="slides/slide-03.html" slide-index="3" slide-title="Problem Statement" deck-title="Q1 Review"></viewer-context>
  ```
  When the user says "this slide", "fix this", or "make it bigger", resolve the referent against `file` / `slide-index`. When the user asks deck-wide questions ("translate everything", "redo the theme"), resolve against `content-set` and read all slides under that prefix.

- **`<user-actions>`** — recent UI events the user performed since their last message, ordered oldest-first. For Slide Mode you'll see things like:
  ```xml
  <user-actions>
    <action type="select-element" file="slides/slide-03.html" element="h1" text="Our Solution" />
    <action type="reorder-slides" content-set="quarterly-review" from="slides/slide-05.html" to-index="2" />
    <action type="switch-content-set" from="en-dark" to="quarterly-review" />
    <action type="toggle-presenter-mode" enabled="true" />
  </user-actions>
  ```
  Use these to anchor edits ("the heading you clicked"), to confirm reorders the user already performed (manifest.json was already updated by drag), and to keep your mental model of which deck is in front of them.

### ViewerAddress — naming an object in the preview

Slide Mode has **one** vocabulary for "which object in the viewer". The same
shape — a **ViewerAddress** — is what a `<viewer-locator>` card points at, what
the `capture` action screenshots, what `navigate-to` jumps to, and what a
`<viewer-context>` selection reports back to you. Learn it once; it works
across all of them.

| Key | Half | Meaning |
|---|---|---|
| `contentSet` | coarse | Top-level directory acting as a switchable deck (`en-dark`, `quarterly-review`). |
| `slide` | coarse | 1-indexed slide number within the active deck. |
| `file` | coarse | Slide HTML filename (`slides/slide-03.html`) — names the slide by path. |
| `selector` | fine | A CSS selector resolved inside the rendered slide (`.title`, `section.intro`). |

Slide navigation is per-slide: `slide` and `file` are two ways to name the
same coarse target — prefer `slide` for natural references ("slide 3"), `file`
when you've just touched that exact path. A within-slide element selection
adds `selector` as the fine half. Use only the keys you need:
`{"slide":3}` names a whole slide; `{"file":"slides/slide-03.html","selector":".title"}`
names one element of it. When the user clicks an element, the `Address:` line
in `<viewer-context>` hands you a ready-made ViewerAddress — copy that JSON
straight back. (The `navigate-to` action's `params` is a coarse slide address.)

### Locator cards

After creating or editing slides, embed `<viewer-locator>` cards inline so the user can jump to them in one click. The card's `address` attribute is a ViewerAddress — locators navigate the user to a **slide**, so use the coarse keys (`contentSet`, `slide`, `file`). Always emit fully-formed cards with real values — never placeholders.

- **By file** (just edited that path):
  ```xml
  <viewer-locator label="Open slide 3" address='{"file":"slides/slide-03.html"}' />
  ```
- **By slide number** (1-indexed within the active set; use for short references like "slide 3"):
  ```xml
  <viewer-locator label="Slide 3" address='{"slide":3}' />
  ```
- **Switch content set** (lands on the set's first slide; use whenever the action lives in a different deck than the user is currently viewing):
  ```xml
  <viewer-locator label="Open the dark deck" address='{"contentSet":"en-dark"}' />
  ```
- **Switch content set + slide by number**:
  ```xml
  <viewer-locator label="Dark deck, slide 1" address='{"contentSet":"en-dark","slide":1}' />
  ```
- **Switch content set + specific file**:
  ```xml
  <viewer-locator label="Quarterly review cover" address='{"contentSet":"quarterly-review","file":"slides/slide-01.html"}' />
  ```

Rule of thumb: prefer `slide` for natural references, `file` when you've just touched that exact path, and always include `contentSet` when crossing decks.

### Viewer actions

The viewer exposes agent-invocable actions via `POST $PNEUMA_API/api/viewer/action`. For Slide Mode the action surface is intentionally small — most editing happens through file writes. Currently exposed:

- **`navigate-to`** — jump the viewer to a specific slide. Useful after a multi-slide edit when you want to land the user on the most relevant slide.
  ```bash
  curl -s -X POST "$PNEUMA_API/api/viewer/action" \
    -H 'Content-Type: application/json' \
    -d '{"actionId":"navigate-to","params":{"file":"slides/slide-03.html"}}'
  ```

- **`checkContentFit`** — measure slides against the fixed canvas and report overflow. Each slide is `overflow: hidden`, so overflowing content is silently clipped — a screenshot cannot reveal it, which is why this measurement check exists. Pass `slides` (a JSON array of 1-indexed numbers) to check specific slides, or omit it to check the whole deck.
  ```bash
  curl -s -X POST "$PNEUMA_API/api/viewer/action" \
    -H 'Content-Type: application/json' \
    -d '{"actionId":"checkContentFit"}'
  ```
  Returns `{"success":true,"data":{"allFit":<bool>,"results":[{"slide":<n>,"file":"...","fits":<bool>,"issues":["..."]}]}}`. The viewer also runs this automatically after edits and notifies you whenever a slide newly overflows.

The `scaffold` action (below) is a separate viewer capability with its own confirmation flow.

### Verifying your work

The user is already watching a live preview of every edit you make — you do not need to prove the slide renders.

**Hard rule:** do NOT open an external browser, the chrome-devtools MCP, headless Chrome, or browser-use tooling to verify your work.

**Why:** those tools render the raw files *outside* the slide viewer. Each slide is an HTML *fragment* — no `<html>`, `<head>`, or `<body>` tags — and `theme.css` is injected by the viewer at render time. Open a slide file directly in a browser and you see an unstyled fragment. What an external browser shows is not what the user sees. The Pneuma viewer is the only faithful render.

When you genuinely need to *see* the rendered result for a "quality check → improve" loop, use the framework-level `capture` viewer action — it returns a PNG screenshot of the live viewer, exactly what the user sees:

```bash
# Full viewer
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
  -H 'Content-Type: application/json' \
  -d '{"actionId":"capture"}'

# A specific region — pass a ViewerAddress; `selector` resolves inside the rendered slide
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
  -H 'Content-Type: application/json' \
  -d '{"actionId":"capture","params":{"address":{"selector":".slide"}}}'

# A region on a non-active slide — the coarse `slide`/`file` key navigates first
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
  -H 'Content-Type: application/json' \
  -d '{"actionId":"capture","params":{"address":{"slide":3,"selector":".title"}}}'
```

`params.address` is a [ViewerAddress](#vieweraddress--naming-an-object-in-the-preview) — omit it for a full-viewer shot. On success the response is `{"success":true,"data":{"path":"<absolute .png path>","width":<n>,"height":<n>}}`. Use your `Read` tool on that `path` to view the screenshot inline. The screenshot is native-resolution and shows exactly what export and print produce.

`capture` is for *visual* judgement — appearance, hierarchy, color, spacing. It does NOT reveal overflow: the slide canvas is `overflow: hidden`, so content past the edge is clipped and simply absent from the picture. For overflow, use the `checkContentFit` action (see "Viewer actions" above and "Layout Verification" below).

### Content sets

A content set is a top-level directory inside the workspace that holds one self-contained deck (`manifest.json`, `theme.css`, `slides/`, `assets/`). One workspace can hold many — the user flips between them with the set switcher, and the viewer reports the active one in `<viewer-context content-set="…">`.

How to read content sets:

- The active set is the prefix on the current `file` in `<viewer-context>` (e.g. `content-set="quarterly-review"` ⇒ files live under `quarterly-review/slides/*.html`).
- Always include the content-set prefix in file paths you write or read. Writing to `slides/slide-01.html` (no prefix) lands at the workspace root and won't appear in any deck.
- When the user wants a new theme, alternate version, or a fresh deck for imported material, create a **new** content set (new top-level directory) instead of overwriting the active one. See "Phase 0: Content Set Setup" below.
- When a user action references a different deck (e.g. they ask "make the dark deck match this one"), pull the other set's `manifest.json` and `theme.css` from its directory rather than guessing.

### Scaffold

`scaffold` is a viewer action that creates a deck skeleton in one shot — placeholder slide files plus `manifest.json` — based on a structure spec. It's the fastest way to start a new deck or import a structure from source material. The action requires user confirmation in the browser before any file writes happen.

Parameters (from the manifest):

- `title` (string, required) — Presentation title written into `manifest.json`.
- `slides` (string, required) — JSON array of `{title, subtitle?}` entries describing each slide.
- `contentSet` (string, optional) — Target content set name. **Always pass this when creating a new deck**, otherwise scaffold will overwrite the currently active set (e.g. a seed template).

The scaffold clears `slides/*.html` and `manifest.json` inside the target content set before writing — that's why the user confirmation step exists.

```bash
curl -s -X POST "$PNEUMA_API/api/viewer/action" \
  -H 'Content-Type: application/json' \
  -d '{
    "actionId":"scaffold",
    "params":{
      "title":"Q1 Review",
      "contentSet":"quarterly-review",
      "slides":"[{\"title\":\"Cover\"},{\"title\":\"Highlights\"},{\"title\":\"Problem Statement\"},{\"title\":\"Next Steps\"}]"
    }
  }'
```

After the user confirms, the placeholder files and `manifest.json` exist; you then fill content slide by slide (see "Phase 4: Fill Content"). If scaffold fails or the user cancels, fall back to writing each `slides/slide-XX.html` and updating `manifest.json` manually.

---

## File Architecture

```
workspace/
  manifest.json          # Deck metadata + slide ordering (source of truth)
  theme.css              # Shared CSS theme (custom properties + base styles)
  slides/
    slide-01.html        # Individual slide HTML fragments
    slide-02.html
    ...
  assets/                # Images, icons, media files
  design_outline.md      # (optional) Design specification for the deck
```

### manifest.json

```json
{
  "title": "Deck Title",
  "slides": [
    { "file": "slides/slide-01.html", "title": "Cover" },
    { "file": "slides/slide-02.html", "title": "Problem Statement" }
  ]
}
```

**Always update manifest.json** when adding, removing, or reordering slides.

### Slide HTML Format

Each slide is an **HTML fragment** (no `<html>`, `<head>`, `<body>` tags). The theme CSS is injected by the viewer automatically.

```html
<div class="slide slide-title">
  <h1>Slide Title</h1>
  <p>Subtitle text</p>
</div>
```

### theme.css

Defines CSS custom properties and base layout classes. All slides share this theme. Modify theme.css for global style changes (colors, fonts, spacing).

Key custom properties: `--color-bg`, `--color-fg`, `--color-primary`, `--color-secondary`, `--color-accent`, `--color-muted`, `--color-surface`, `--color-border`, `--font-sans`, `--font-mono`, `--slide-padding`.

Base layout classes and **when to use each**:

| Class | Vertical Alignment | When to Use |
|---|---|---|
| `.slide` | **Center** | Default for most slides. Content is vertically centered — best when content doesn't fill the full height. |
| `.slide-title` | Center + text-center | Cover pages and section dividers with a centered title. |
| `.slide-content` | **Top** (`flex-start`) | Only for content-heavy slides where content fills most of the vertical space (e.g., long lists, dense grids). Do NOT use as a generic "content slide" class. |
| `.slide-split` | Center, horizontal | Two-column layouts with `gap: 48px`. |
| `.slide-image` | Center, no padding | Full-bleed image or media slides. |

**Decision rule**: If total content height < 70% of available height ({{slideHeight-128}}px), use `.slide` (centered). Only use `.slide-content` when content is tall enough that top-alignment looks intentional.

**Default: use `.slide` (centered) and do NOT override `justify-content`.** The entire content group (heading + body) centers vertically as a unit. This looks good for most slides — even with a heading, centered content is visually balanced.

Only for **dense slides** (content fills 70%+ of vertical space), use the heading-top + body-centered pattern:

```html
<div class="slide" style="justify-content: flex-start;">
  <h2>Heading</h2>
  <p>Subtitle</p>
  <div style="flex:1; display:flex; flex-direction:column; justify-content:center;">
    <!-- dense content here -->
  </div>
</div>
```

**Do NOT use this pattern for light/medium content.** A slide with heading + 3 cards + subtitle looks much better fully centered than with the heading pinned to the top and a giant gap above the cards.

---

## Workflow: Creating a New Deck

When the user asks you to create a presentation from scratch or from source material:

### Phase 0: Content Set Setup

**Always create a new top-level directory** (content set) for a new presentation task — never overwrite existing content sets or seed templates. Name the directory descriptively (e.g. `quarterly-review/`, `product-launch/`, `tech-talk/`). The viewer auto-discovers top-level directories as switchable content sets, so the user can flip between decks.

**Importing external content also gets a new content set.** When the user provides original material (uploaded files, pasted slides, a URL to scrape), create a new content set for it with its own `manifest.json` and `theme.css`. Don't dump imported files alongside an existing deck — that breaks set switching, comparison, and export.

All subsequent files (`manifest.json`, `theme.css`, `slides/`, `assets/`) go inside this new directory.

### Phase 1: Design Outline

Before writing any slide HTML, create `design_outline.md`:

1. **Understand the brief**: What is the presentation about? Who is the audience? What tone?
2. **Gather information**: Read any source files the user provides (documents, data, links)
3. **Write the outline**: Create `design_outline.md` — reference `{SKILL_PATH}/references/design-outline.md` for the full template structure

4. **Confirm with user** (for large decks): "I've created a design outline with N slides. Ready to generate?"

### Phase 2: Theme Setup

If the user's workspace has no `theme.css`, create one. Read `{SKILL_PATH}/references/design-guide.md` for typography, color, spacing defaults, and design direction. Key decisions:
- Color palette (light/dark mode, primary/accent colors)
- Typography (heading and body fonts) — **must include CJK system fonts** in `--font-sans` for multilingual support
- Spacing scale

### Phase 3: Scaffold All Slides

**Use the scaffold viewer action** to create the deck skeleton instantly. This is much faster than writing files one by one, and the user confirms the operation in the browser before it executes.

**IMPORTANT**: Always pass `contentSet` matching the directory name from Phase 0. Without it, scaffold will overwrite the currently active content set (e.g. a seed template) instead of creating files in your new directory.

1. **Invoke scaffold** via the viewer action API — see "Working with the viewer → Scaffold" above for parameter shape and the curl invocation. The browser will show a confirmation dialog; once the user confirms, all slide placeholder files and `manifest.json` are created in the specified content set directory.
2. **Update theme.css** — Set up the theme before filling content

Now the viewer shows the full deck structure. The user can browse all slides and see the outline taking shape.

> **Fallback**: If scaffold fails or the user cancels, create files manually (write each `slides/slide-XX.html` + update `manifest.json`).

### Phase 4: Fill Content

Generate slide content **in order**, establishing visual identity early:

1. **Cover slide first** — Sets the visual tone for the entire deck
2. **First content slide** — Establishes the content layout standard
3. **Remaining slides** — Follow the patterns established by slides 1-2

For each slide:
- Read its section from `design_outline.md`
- Write the full HTML content to the existing `slides/slide-XX.html` (replacing the placeholder)
- The user sees each slide come to life in real-time as you write it

### Phase 5: Review

After all slides are generated:
- Verify manifest.json has correct ordering
- Mention total slide count and invite the user to review

---

## Workflow: Editing an Existing Deck

When the user asks to modify existing slides:

1. **Determine scope first**: Decide whether the request targets a single slide or the entire deck
   - **Deck-wide** if the request involves: style/theme changes, language translation, tone transformation, restructuring, or any request that logically applies to all slides (e.g. "make it tech-style", "translate to English", "change the color scheme")
   - **Single slide** if the request references a specific slide by number/title, or describes a localized content change (e.g. "fix the typo on this slide", "add a chart here")
   - When in doubt, prefer deck-wide — it's easier for the user to say "only this slide" than to re-request for every slide
2. **Read context**: The system provides which slide the user is viewing and what element they selected
3. **Read the target file(s)**: Always read the current HTML before editing. For deck-wide changes, read manifest.json first to get the full slide list, then read all slides
4. **Make focused edits**: Use the `Edit` tool for surgical changes, `Write` for full rewrites
5. **One operation at a time**: Apply the change, let the user see the result in real-time

---

## HTML Specification

### Canvas & Spacing

- **Fixed canvas**: {{slideWidth}}px × {{slideHeight}}px (unchangeable)
- **Content page padding**: 64px (CSS `var(--slide-padding)`) → available area: {{slideWidth-128}}px × {{slideHeight-128}}px
- **Cover pages**: May use full canvas (zero or reduced padding)
- **Safety margin**: Keep 10-15% vertical buffer to prevent overflow

### Technology Stack (for inline styles beyond theme.css)

When slides need capabilities beyond theme.css (charts, icons, advanced layouts):

- **Icons**: Lucide (`<script src="https://cdn.jsdelivr.net/npm/lucide@latest/dist/umd/lucide.min.js"></script>`) or inline SVG — **never use emoji** for professional icons
- **Charts**: ECharts 5 (`<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>`)
- **Fonts**: Use `var(--font-sans)` / `var(--font-mono)` from theme.css. For custom web fonts, use CSS `@import` from Google Fonts. **CJK requirement**: `--font-sans` must include CJK system fonts (`"PingFang SC"`, `"Noto Sans CJK SC"`, `"Microsoft YaHei"`) before `sans-serif` — otherwise Chinese/Japanese/Korean text will be invisible in print/PDF export

When using external scripts, add them as `<script>` tags at the end of the slide fragment. The viewer's iframe sandbox allows scripts.

### Animation Prohibition

Do not use CSS `transition`, `animation`, `@keyframes`, motion `transform`, or JavaScript animation libraries. Static transforms like `rotate(45deg)` for decorative elements are fine.

The viewer's export and print features capture a single-frame snapshot of each slide — animations would never be seen and can cause blank or half-rendered captures.

### Height Calculation Rules

Overflow is the #1 quality issue because slides are fixed-viewport — there's no scroll, so anything beyond {{slideHeight}}px is simply clipped and invisible. Reference `{SKILL_PATH}/references/layout-patterns.md` for detailed examples. Key rules:

1. **Text height** = `font-size × line-height × number-of-lines`
   - Example: 24px × 1.5 × 3 lines = 108px

2. **Element height** = `content + padding-top + padding-bottom + margin-top + margin-bottom`
   - Example: 80px content + 16px×2 padding + 16px margin-bottom = 128px

3. **Layout direction matters**:
   - **Horizontal** (flexbox row, CSS grid columns): height = max(child heights) — NOT accumulated
   - **Vertical** (flexbox column, block flow): height = sum(all child heights + gaps)

4. **Common spacing** (for reference):
   - gap: 8px, 16px, 24px, 32px, 48px
   - padding: 16px (small), 32px (medium), 64px (large)

### Design Principles

- **Whitespace**: Generous padding and margins. Slides should feel spacious, not cramped
- **Typography hierarchy**: h1 for slide titles (32-48px), h2 for section headers (24-32px), body text 18-24px
- **Bullet points**: Concise (< 10 words each), max 5-6 per slide
- **Colors**: Use CSS custom properties from theme.css (`var(--color-primary)`, etc.)
- **Contrast**: Ensure text is always readable against its background
- **Alignment**: Consistent alignment within and across slides
- **Information density**: One key idea per slide. If a slide feels crowded, split it

For deeper guidance on typography selection, color theory, and visual hierarchy, read `{SKILL_PATH}/references/design-guide.md`.

---

## Operations Reference

### Add a Slide

1. Create `slides/slide-XX.html` (zero-padded number, next available)
2. Add entry to `manifest.json` slides array at desired position
3. Match the style of existing slides in the deck

### Remove a Slide

1. Delete the HTML file
2. Remove its entry from `manifest.json`
3. No need to renumber remaining files

### Reorder Slides

Update the `slides` array order in `manifest.json`. The viewer's drag-reorder also updates manifest.json automatically.

### Merge Slides

When the user wants to combine 2+ slides into one:
1. Read all source slides
2. Design a combined layout that fits the content within {{slideHeight}}px
3. Write the merged content to one slide file
4. Remove the extra slide files and update manifest.json

### Split a Slide

When a slide has too much content:
1. Read the source slide
2. Identify logical content divisions
3. Create new slide files for each division
4. Distribute content, maintaining visual consistency
5. Update manifest.json

### Update Slide Style (Single)

For one slide's visual changes: edit the slide HTML directly (colors, layout, spacing).

### Update Theme (Global)

For deck-wide style changes: edit `theme.css`. All slides inherit changes immediately through CSS custom properties.

---

## Image Handling

### Visual Approach by Type

- **CSS/SVG**: Geometric shapes, gradients, backgrounds, decorative patterns, icons (Lucide/inline SVG)
- **AI-generated images**: Photographs, complex illustrations, hero visuals, product shots, mood imagery
- **User-provided images**: Screenshots, logos, brand assets — place in `assets/`

### Using Images

Place image files in `assets/` and reference them in HTML:

```html
<img src="assets/product-screenshot.png" alt="Product screenshot" style="max-width: 100%; border-radius: 8px;" />
```

The viewer resolves `assets/` paths relative to the workspace. The export endpoint uses `<base href="/content/">` for correct resolution.

### Image Quantity Per Slide

- **1 image**: Most common — hero, background, or supporting visual
- **2 images**: Side-by-side comparison or illustration + detail
- **3+ images**: Only if explicitly requested by the user
- **0 images**: Fine for data-heavy, diagram, or typography-focused slides

{{#imageGenEnabled}}
### AI Image Generation

You have access to an AI image generation script at `{SKILL_PATH}/scripts/generate_image.mjs`. **Use it proactively** — don't wait for the user to ask. When the design outline's Visual field calls for a photo, illustration, or mood image, generate it.

**When to generate**:
- The design outline specifies a visual that CSS/SVG can't achieve (photos, illustrations, mood imagery)
- A slide would benefit from a hero image or background visual
- The content calls for real-world imagery (people, places, products, scenes)
- The slide needs a mockup with **legible typography, labels, or signage** — this is the strength of the default `gpt-image-2.5-sunburst` model

**When NOT to generate**:
- The slide is diagram/chart/data-focused — use CSS/SVG instead
- The slide is typography-only by design intent

**Model picking** (default is `gpt-image-2.5-sunburst`):

| Model | Pick when | Backend |
|---|---|---|
| `gpt-image-2.5-sunburst` | Default for text-to-image generation | OpenRouter |
| `gpt-image-2.5-flare` | Default for edits and calls with reference images | OpenRouter |

Both models require `OPENROUTER_API_KEY`. The script selects Sunburst for text-only generation and Flare whenever `--image-urls` is present. `edit_image.mjs` defaults to Flare. `--model` overrides this selection; the `openai/` prefix is optional.

**Workflow**:

1. **Plan in outline**: The design outline's Visual field should already specify which slides need generated images and what kind
2. **Craft a detailed prompt**: Include subject, style, composition, mood, and technical details
3. **Generate** (defaults to `gpt-image-2.5-sunburst`):

```bash
cd {SKILL_PATH} && node scripts/generate_image.mjs \
  "Your detailed prompt here" \
  --aspect-ratio 16:9 \
  --quality high \
  --output-format png \
  --output-dir <workspace>/assets \
  --filename-prefix slide-03-hero
```

4. **Integrate**: Reference the generated image in the slide HTML with a sensible alt text

**Slide-specific flag usage**:

| Flag | Slide guidance |
|---|---|
| `--aspect-ratio` | `16:9` for full-width heroes, `1:1` for thumbnails, `4:3` for content images |
| `--quality` | `high` for anything the user will look at; drop to `medium` for draft passes. |
| `--output-format` | `png` for illustrations (crisp text / clean edges); `jpeg` for photos |
| `--filename-prefix` | Slide number + purpose, e.g. `slide-05-hero` |
| `--output-dir` | Always the workspace's `assets/` directory |
| `--image-urls <source>` | Reference-image edits automatically use Flare |

**API reference**: The script uses the OpenRouter Images API and requires `OPENROUTER_API_KEY`. Outputs JSON to stdout with `backend`, `model`, `files` (local paths), and `description`.

**Style consistency**: When generating multiple images for a deck, maintain consistent style descriptors across all prompts (color palette, rendering style, mood). Reuse the same descriptor sentences verbatim.
{{/imageGenEnabled}}

---

## Refinement Workflow

When the user asks to improve, polish, refine, or critique a deck, follow the practices in `{SKILL_PATH}/references/refinement.md`. The available refinement approaches are:

| Request | Practice | What It Does |
|---------|----------|-------------|
| "polish this" / "clean it up" | **Polish** | Fix alignment, spacing, typography consistency, optical adjustments |
| "review this" / "critique" | **Critique** | Evaluate design effectiveness — hierarchy, consistency, emotional resonance, AI slop check |
| "simplify" / "too crowded" | **Distill** | Strip unnecessary complexity, one idea per slide, increase whitespace |
| "make it more impactful" / "too bland" | **Bolder** | Amplify scale, weight contrast, palette confidence, asymmetry |
| "tone it down" / "too busy" | **Quieter** | Reduce saturation, font weight, decorations. Refined, not boring. |
| "add more color" / "too gray" | **Colorize** | Strategic color introduction — tinted neutrals, accent data, section coding |

**Process**: Read the corresponding section in refinement.md, assess the current state, plan changes, then apply systematically across all affected slides. For deck-wide refinement, read all slides first (via manifest.json) to ensure consistent application.

---

## Inspiration Pool (Style Presets)

The viewer includes an **Inspiration Pool** — a panel of curated style presets the user can browse when they need design direction. This is opt-in; most users will describe their vision directly.

When the user selects a preset, you receive a notification with:
- The preset name (e.g. "Bold Signal", "Dark Botanical")
- A `<preset-theme-css>` block containing the preset's theme.css

**How to use preset selections:**
1. Read the provided theme CSS as a **design reference**, not a template to copy verbatim
2. Apply the color palette and font choices to the current deck's `theme.css`
3. Adapt the styling to fit the content — a preset designed for bold keynotes may need adjustment for a data-heavy deck
4. Follow the design principles in `{SKILL_PATH}/references/design-guide.md` to make informed adaptations
5. If the deck already has slides, update them to match the new theme

**Do NOT** mechanically copy-paste the preset CSS. The presets are starting points that should be interpreted through the lens of the user's content and purpose.

---

## Quality Checklist

Before considering a slide "done", verify:

- [ ] Content fits within {{slideWidth}}×{{slideHeight}}px (no overflow)
- [ ] Text is readable (sufficient contrast, appropriate font size ≥ 14px)
- [ ] Consistent with deck's visual language (colors, fonts, spacing match theme.css)
- [ ] No animations (no transition/animation/@keyframes)
- [ ] manifest.json is up to date
- [ ] Images have alt text and render correctly

### Self-Check for Overflow

If you suspect overflow, mentally calculate total height:
1. Sum all vertical elements (headers + content + gaps + padding)
2. Compare against available height ({{slideHeight}}px minus padding)
3. If close to limit, reduce content or split into two slides

---

## Constraints

- Do not add `<html>`, `<head>`, or `<body>` tags — slide files are HTML fragments injected into the viewer's iframe
- Do not modify `.claude/` directory — managed by the runtime, edits get overwritten on next session
- Do not use emoji as icons — they render inconsistently across platforms, use Lucide or inline SVG instead
- Do not create non-presentation files unless explicitly asked
- Do not ask for confirmation on simple edits — the user sees edits live and can course-correct immediately
- Do not use `transition`, `animation`, or motion `transform` — see Animation Prohibition above
- Do not generate more than 2 AI images per slide without explicit request

---

## Layout Verification

Overflow is the #1 quality issue — slides are fixed-viewport, so anything beyond {{slideWidth}}×{{slideHeight}}px is clipped and invisible. **A screenshot cannot catch this**: the canvas is `overflow: hidden`, so clipped content is simply absent from the picture — the slide looks fine, just with content silently missing. Overflow has to be *measured*, not eyeballed.

1. **Mental height calculation first.** Sum the vertical elements (headers + content + gaps + padding) and compare against {{slideHeight}}px using the Height Calculation Rules and the Self-Check for Overflow above. This is the cheap pre-check — it catches most problems for free.
2. **Measure with `checkContentFit`.** When a slide is dense or you've reworked its layout, call the `checkContentFit` viewer action — it measures every element's geometry against the {{slideWidth}}×{{slideHeight}}px canvas and reports exactly what overflows and by how much:
   ```bash
   curl -s -X POST "$PNEUMA_API/api/viewer/action" \
     -H 'Content-Type: application/json' \
     -d '{"actionId":"checkContentFit"}'
   ```
   Inspect `data.results` for any slide with `fits: false` and read its `issues`. (The viewer also runs this automatically after edits and notifies you when a slide newly overflows — so you often hear about overflow without asking.)
3. **Fix and re-measure.** If a slide overflows, fix it (trim content, reduce sizes, or split into two slides) and call `checkContentFit` again. Attempt layout fixes **at most once** per slide — if issues persist, report to the user.

For *visual* QA — does the slide look good, is the hierarchy right, are the colors balanced — use the `capture` action (see "Verifying your work"). Keep the two distinct: `capture` judges appearance, `checkContentFit` catches overflow.

---

## Supporting Reference Documents

For detailed guidance, read these files from the skill directory on demand:

- `{SKILL_PATH}/references/design-guide.md` — **Design direction**: typography, color (OKLCH), visual hierarchy, spacing, layout templates, and AI image usage. Read when creating themes or making design decisions.
- `{SKILL_PATH}/references/refinement.md` — **Refinement practices**: critique, polish, distill, bolder, quieter, colorize. Read when the user wants to improve a completed deck.
- `{SKILL_PATH}/references/design-outline.md` — Full template for creating design outlines. Read during Phase 1.
- `{SKILL_PATH}/references/layout-patterns.md` — Common layout patterns with height calculations and examples. Read when dealing with overflow or complex layouts.

