# Presentations Guide

Use this guide any time a `.pptx` file is involved — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any `.pptx` file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions "deck," "slides," "presentation," or references a `.pptx` filename, regardless of what they plan to do with the content afterward. If a `.pptx` file needs to be opened, created, or touched, follow this guide.

## Quick Reference

| Task | Guide |
|------|-------|
| Read/analyze content | `extract-text presentation.pptx` |
| Edit or create from template | Read [editing.md](editing.md) |
| Create from scratch | Read [pptxgenjs.md](pptxgenjs.md) |

---

## Reading Content

```bash
# Text extraction, one `## Slide N` section per slide
extract-text presentation.pptx

# Visual overview
python scripts/thumbnail.py presentation.pptx

# Raw XML
python scripts/office/unpack.py presentation.pptx unpacked/
```

---

## Editing Workflow

**Read [editing.md](editing.md) for full details.**

1. Analyze template with `thumbnail.py`
2. Unpack → manipulate slides → edit content → clean → pack

---

## Creating from Scratch

**Read [pptxgenjs.md](pptxgenjs.md) for full details.**

Use when no template or reference presentation is available.

---

## Design Ideas

**Don't create boring slides.** Plain bullets on a white background won't impress anyone. Consider ideas from this list for each slide.

### Before Starting

- **Pick a bold, content-informed color palette**: The palette should feel designed for THIS topic. If swapping your colors into a completely different presentation would still "work," you haven't made specific enough choices.
- **Dominance over equality**: One color should dominate (60-70% visual weight), with 1-2 supporting tones and one sharp accent. Never give all colors equal weight.
- **Dark/light contrast**: Dark backgrounds for title + conclusion slides, light for content ("sandwich" structure). Or commit to dark throughout for a premium feel.
- **Commit to a visual motif**: Pick ONE distinctive element and repeat it — rounded image frames, icons in colored circles, thick single-side borders. Carry it across every slide.

### Color

Pull color tokens from Fractal's color system — see [`../color/color-guide.md`](../color/color-guide.md) for the 10-stop scale rules and [`../color/colors.css`](../color/colors.css) for the available scales. The dominance-over-equality rule and dark/light contrast guidance from [`../philosophy.md`](../philosophy.md) still apply: pick one scale to dominate (60-70% visual weight), use one or two supporting scales, and reserve a sharp accent.

When emitting hex values into PPTX, drop the `#` prefix — pptxgenjs requires 6-char hex without it (see [pptxgenjs.md](pptxgenjs.md) common pitfalls).

### For Each Slide

**Every slide needs a visual element** — image, chart, icon, or shape. Text-only slides are forgettable.

**Layout options:**
- Two-column (text left, illustration on right)
- Icon + text rows (icon in colored circle, bold header, description below)
- 2x2 or 2x3 grid (image on one side, grid of content blocks on other)
- Half-bleed image (full left or right side) with content overlay

**Data display:**
- Large stat callouts (big numbers 60-72pt with small labels below)
- Comparison columns (before/after, pros/cons, side-by-side options)
- Timeline or process flow (numbered steps, arrows)

**Visual polish:**
- Icons in small colored circles next to section headers
- Italic accent text for key stats or taglines

### Typography

Decks **override** Fractal's app-UI default (Figtree, see [`../typography/typography-guide.md`](../typography/typography-guide.md)) and use **Roboto** for sans-serif and **Roboto Mono** for monospaced runs.

**Why the override.** Decks ship as `.pptx` files that get opened on machines we don't control. Figtree is not pre-installed on most viewer machines — Windows boxes, Office on enterprise hardware, projection laptops. Roboto is free, ships with Android, comes pre-installed with Microsoft Office (recent versions) and Keynote, Chrome on macOS, and many Adobe apps; it has by far the highest probability of rendering as authored across PowerPoint and Keynote. Roboto Mono is the matching monospaced family for code, filenames, and metadata.

In pptxgenjs:

```javascript
slide.addText('Hello', { fontFace: 'Roboto',      fontSize: 36, bold: true });
slide.addText('code',  { fontFace: 'Roboto Mono', fontSize: 14 });
```

PowerPoint will fall back to a system sans-serif if Roboto isn't installed; that fallback is acceptable but not beautiful, so don't depend on it visually.

The size guidance below applies regardless of font:

| Element | Size |
|---------|------|
| Slide title | 36-44pt bold |
| Section header | 20-24pt bold |
| Body text | 14-16pt |
| Captions | 10-12pt muted |

### Spacing

- 0.5" minimum margins
- 0.3-0.5" between content blocks
- Leave breathing room—don't fill every inch

#### Proximity — keep titles tight to their bodies

A header and the body text it explains belong together. The single most common deck-rendering bug is putting the header in an over-tall text box (`h: 0.5` for a 13pt header, when the rendered height is ~0.25") so the body that follows sits a quarter-inch below where it should be. The header reads as orphaned, the body reads as floating, and the slide loses its visual hierarchy.

**Rule:** the visible gap between a header and its body should be ≤ 0.1". Add space _between_ sections (≥ 0.3"), never _within_ them.

**How:** size text-box `h` to the actual rendered text height, not a padded approximation. See [pptxgenjs.md → Text Box Sizing](./pptxgenjs.md#text-box-sizing) for the height table and a wrong/right example. For the common header-then-body case, use the `titlePair` recipe in [Common Patterns](#common-patterns) below.

### Avoid (Common Mistakes)

- **Don't repeat the same layout** — vary columns, cards, and callouts across slides
- **Don't center body text** — left-align paragraphs and lists; center only titles
- **Don't skimp on size contrast** — titles need 36pt+ to stand out from 14-16pt body
- **Don't default to blue** — pick colors that reflect the specific topic
- **Don't mix spacing randomly** — choose 0.3" or 0.5" gaps and use consistently
- **Don't style one slide and leave the rest plain** — commit fully or keep it simple throughout
- **Don't create text-only slides** — add images, icons, charts, or visual elements; avoid plain title + bullets
- **Don't forget text box padding** — when aligning lines or shapes with text edges, set `margin: 0` on the text box or offset the shape to account for padding
- **Don't use low-contrast elements** — icons AND text need strong contrast against the background; avoid light text on light backgrounds or dark text on dark backgrounds
- **NEVER use accent lines under titles** — these are a hallmark of AI-generated slides; use whitespace or background color instead
- **Don't add decorative full-width colored bars/rectangles** — header/footer bars, side ribbons, or colored stripes read as AI slop unless the user explicitly requests them
- **Don't default to cream/beige backgrounds** — when no background is specified, use white (`FFFFFF`) or the user's brand palette; avoid warm-neutral defaults like `F5F5DC`, `FAF0E6`, `FAEBD7`, `FFF8E1`
- **Don't ship text that overflows its shape** — if text doesn't fit, reduce font size, split across slides, or enlarge the container; never leave content cut off or spilling past bounds

---

## Common Patterns

Reusable building blocks for deck scripts. Define these helpers near the top of your `.mjs` file (after the color/font constants, before slide bodies). They encapsulate the height math from [pptxgenjs.md → Text Box Sizing](./pptxgenjs.md#text-box-sizing) so a fresh deck stays tight.

### `lineH` and `titlePair` — header + body, tight

The default mistake: sizing a header's text box too tall (e.g. `h: 0.5` for a single-line 13pt header), which pushes the body 0.25" lower than it should be visually.

```javascript
// Approximate rendered text height in inches. ~1.4 line-height factor
// (0.0194 in/pt) plus a small descender pad, calibrated against
// pptxgenjs + LibreOffice rendering.
function lineH(fontSize, lines = 1) {
  return Math.max(0.22, fontSize * 0.022) * lines;
}

// Header + body, tightly proximate. Returns the y-coordinate where the
// next element should start (so callers can chain).
function titlePair(slide, opts) {
  const {
    x, y, w,
    title, body,
    titleSize = 14, bodySize = 11,
    titleLines = 1, bodyLines = 2,
    gap = 0.05,                  // inside-pair gap (header → body)
    sectionGap = 0.25,           // gap appended for the next section
    titleColor = c.fgPrimary,
    bodyColor = c.fgSecondary,
    fontFace = FONT,
  } = opts;

  const titleH = lineH(titleSize, titleLines);
  const bodyH  = lineH(bodySize, bodyLines);

  slide.addText(title, {
    x, y, w, h: titleH,
    fontSize: titleSize, fontFace, bold: true,
    color: titleColor, margin: 0,
  });
  slide.addText(body, {
    x, y: y + titleH + gap, w, h: bodyH,
    fontSize: bodySize, fontFace,
    color: bodyColor, margin: 0,
  });

  return y + titleH + gap + bodyH + sectionGap;
}
```

**Use it like this:**

```javascript
let y = 1.2;
y = titlePair(slide, {
  x: 0.6, y, w: 6,
  title: 'Mast Cell-Driven Diseases',
  body: 'CSU, atopic dermatitis, allergic asthma, and others.',
  titleSize: 13, bodySize: 11, bodyLines: 1,
});
y = titlePair(slide, {
  x: 0.6, y, w: 6,
  title: 'CSU: Significant Unmet Need',
  body: '~1.5M US patients; first-line antihistamines are 37% refractory.',
  titleSize: 13, bodySize: 11, bodyLines: 1,
});
```

The chained `y` updates carry the cumulative offset for you — the next section header lands cleanly under the previous body, with `sectionGap` (default 0.25") of breathing room.

### `bulletList` — bullets with tight per-line height

```javascript
function bulletList(slide, items, opts) {
  const { x, y, w, fontSize = 11, color = c.fgSecondary, fontFace = FONT } = opts;
  const runs = items.flatMap((it, i) => [
    { text: it, options: { bullet: { code: '2022' }, breakLine: i < items.length - 1 } },
  ]);
  slide.addText(runs, {
    x, y, w,
    h: lineH(fontSize, items.length),
    fontSize, fontFace, color, margin: 0,
    paraSpaceAfter: 4,
  });
  return y + lineH(fontSize, items.length) + 0.05;
}
```

Same idea: `h` is computed from the line count, so the next element starts where the bullets actually end — not where an arbitrarily padded box ends.

---

## QA

Your first render usually has a few real issues — overlaps, overflow, misalignment. Find and fix those, then stop. Don't keep iterating on minor coordinate nudges or chase a "perfect" render.

Work, don't narrate: minimize prose between tool calls. Run the check, apply the fix, move on.

**Visual QA is recommended but optional.** It depends on `soffice` and `pdftoppm` (see [Runtime Requirements](#runtime-requirements)). If those binaries aren't on `$PATH`, **don't block** — run text QA, ship the `.pptx`, and tell the user explicitly: *"Skipped visual QA — `soffice`/`pdftoppm` not installed. Open the deck in PowerPoint/Keynote/LibreOffice to verify visually."* Never claim a deck is verified when you didn't render it.

### Content QA

```bash
extract-text output.pptx
```

Check for missing content, typos, wrong order.

**When using templates, check for leftover placeholder text:**

```bash
extract-text output.pptx | grep -iE "\bx{3,}\b|lorem|ipsum|\bTODO|\[insert|this.*(page|slide).*layout"
```

If grep returns results, fix them before declaring success.

### Visual QA

**⚠️ USE SUBAGENTS** — even for 2-3 slides. You've been staring at the code and will see what you expect, not what's there. Subagents have fresh eyes.

Convert slides to images (see [Converting to Images](#converting-to-images)), then use this prompt:

```
Visually inspect these slides for user-visible defects.

Look for:
- Overlapping elements (text through shapes, lines through words, stacked elements)
- Text overflow or cut off at edges/box boundaries
- Decorative lines positioned for single-line text but title wrapped to two lines
- Source citations or footers colliding with content above
- Elements too close (< 0.3" gaps) or cards/sections nearly touching
- Uneven gaps (large empty area in one place, cramped in another)
- Insufficient margin from slide edges (< 0.5")
- Columns or similar elements not aligned consistently
- Low-contrast text (e.g., light gray text on cream-colored background)
- Low-contrast icons (e.g., dark icons on dark backgrounds without a contrasting circle)
- Text boxes too narrow causing excessive wrapping
- Leftover placeholder content

For each slide, list user-visible issues. Skip sub-pixel positioning and cosmetic nitpicks a viewer wouldn't notice.

Read and analyze these images — run `ls -1 "$PWD"/slide-*.jpg` and use the exact absolute paths it prints:
1. <absolute-path>/slide-N.jpg — (Expected: [brief description])
2. <absolute-path>/slide-N.jpg — (Expected: [brief description])
...
```

### Verification Loop

1. Generate slides → Convert to images → Inspect
2. **Check text bounds first** — for every text box, confirm the rendered text fits inside its shape. Overflow is the most common defect and is always user-visible.
3. List any other issues found
4. Fix issues
5. Re-verify only the affected slides
6. **Stop after one fix-and-verify cycle** unless a new *user-visible* defect appears (overlap, overflow, missing content). Do not loop on sub-pixel positioning, minor color tweaks, or issues a viewer wouldn't notice.

---

## Converting to Images

Convert presentations to individual slide images for visual inspection:

```bash
python scripts/office/soffice.py --headless --convert-to pdf output.pptx
rm -f slide-*.jpg
pdftoppm -jpeg -r 150 output.pdf slide
ls -1 "$PWD"/slide-*.jpg
```

**Pass the absolute paths printed above directly to the view tool.** The `rm` clears stale images from prior runs. `pdftoppm` zero-pads based on page count: `slide-1.jpg` for decks under 10 pages, `slide-01.jpg` for 10-99, `slide-001.jpg` for 100+.

**After fixes, rerun all four commands above** — the PDF must be regenerated from the edited `.pptx` before `pdftoppm` can reflect your changes.

---

## Runtime Requirements

The build path needs only Node + `pptxgenjs`. The visual QA loop adds LibreOffice + Poppler. If you only have the build deps, you can still produce a `.pptx` — visual QA gracefully degrades (see [QA](#qa)).

### Build (always required for `pptxgenjs.md` workflow)

| Tool | Install (macOS) | Install (Debian/Ubuntu) |
|------|-----------------|--------------------------|
| Node 20+ | `brew install node` | `apt install nodejs` |
| `pptxgenjs` | `npm install pptxgenjs` (project-local) | same |
| Icons (optional, see `pptxgenjs.md`) | `npm install react react-dom react-icons sharp` | same |

### Edit existing decks (`editing.md` workflow)

| Tool | Install (macOS) | Install (Debian/Ubuntu) |
|------|-----------------|--------------------------|
| Python 3.11+ | `brew install python` | `apt install python3` |
| `defusedxml`, `Pillow` | `pip install defusedxml Pillow` | same |

### Visual QA (recommended, optional)

| Tool | Install (macOS) | Install (Debian/Ubuntu) |
|------|-----------------|--------------------------|
| LibreOffice (`soffice`) | `brew install --cask libreoffice` | `apt install libreoffice` |
| Poppler (`pdftoppm`) | `brew install poppler` | `apt install poppler-utils` |

If either is missing, skip the visual QA loop and tell the user — don't fake it.

### Axon

When running inside Axon's presentations sandbox image, all of the above are pre-installed. Mount inputs/outputs through the `shared` volume (`/mnt/shared/...`) per Axon's convention.
