Designing a deck (PPTX)
You build decks by writing a python-pptx script and running it with
$DSH_OFFICE_PYTHON. There is no template: invent a new design for every
deck — colors chosen for this topic, a motif, varied layouts. Two decks on
different subjects should never look like siblings.
Helper primitives (styled charts, bullets, shadows — they take your colors,
no layout opinions): scripts/pptx_helpers.py. Visual QA renderer:
scripts/render_slides.py. Resolve both against this skill's base directory.
1. Design before you code
Start from the topic, never from a previous deck. Before writing any
code, write a 3-line design brief: two or three candidate directions —
each a palette (dominant / support / accent hexes), a motif, a font pairing,
and a one-word persona from the list in references/design.md — then pick
the least obvious one and say in one sentence why it fits this audience
and this moment. Name the clichéd choice for the topic explicitly so you
can avoid it. Check the workspace first: if a previous build script or deck
exists there, its design is TAKEN — a new deck for a different topic must
not reuse its palette, motif, or script (start a fresh script file; do not
edit the old one). Same topic, new engagement (this quarter's review after
last quarter's) should also shift — evolve the palette and layout plan
rather than reskinning.
Write the chosen design as constants at the top of the build script:
- Palette for THIS topic's concrete world — not its category. "AI" does
not mean dark + cyan glow; "finance" does not mean default navy;
"sustainability" does not have to be green. Derive colors from something
tangible about the subject — its industry's documents, its product, its
season, its geography — and sanity-check: would this deck be mistakable
for a generic tech template? If yes, redo it. Pick one dominant color
(
60-70% of the visual weight), 1-2 supporting tones, ONE sharp accent
(10%, reserved for the numbers and phrases that matter). Derive tints
with blend()/tints() instead of inventing extra hues. The palettes in
references/design.md are raw material for synthesis — never pick a row
verbatim, and never pick the same row twice in one workspace.
- Font pairing from the safe list (these ship with Office AND render
true-to-width in the QA preview): headers Cambria (serif, editorial)
or Arial (bold, modern); body Calibri. Contrast serif headers over
sans body for a distinctive, zero-risk pairing. Never Aptos; Georgia or
Trebuchet only if the user asks (their QA preview is unreliable — leave
~10% extra width slack there).
- One motif, carried everywhere — e.g. numbers in colored circles,
oversized ghost numerals, soft translucent circles on dark slides, thick
serif quote marks. Pick ONE and repeat it. Never an accent bar or stripe
(see Never).
- Sandwich structure: dark cover and closing (optionally dark section
dividers and a dark pull-quote breather), white content slides. Or commit
to dark throughout for a premium feel — but then text contrast must be
flawless.
- Backgrounds are white (
FFFFFF) unless the user's brand says otherwise.
Cream/beige defaults read as template filler.
2. Write the story before the slides
Outline first: cover → one context slide → 2-4 supporting beats with
evidence (charts, stats, tables) → what's next → closing. 6-12 content
slides. One idea per slide. Then map each slide to a DIFFERENT layout —
see the layout patterns in references/design.md.
- Titles are claims, not topics: "Payback fell from 14 to 9 months", not
"Unit economics". ≤ 9 words, ≥ 30pt bold, left-aligned.
- Body 14-15pt, left-aligned (never center body text), ≤ 5 bullets of
≤ 2 lines, parallel grammar, bold lead-in phrase + normal rest.
- Speaker notes go in the notes pane (
slide.notes_slide...), never in
a text box on the slide.
3. Every slide needs a visual
Text-only slides are forgettable. Each content slide gets at least one of:
a native chart, big stat callouts, a table, numbered cards with a tinted
fill, a timeline, a half-bleed image (crop with crop_to(), never
squash), or a bold pull-quote. Icons: draw simple geometric marks with
shapes (circles, rings, bars) in palette colors — or skip them; a clean
number chip beats a bad icon.
4. Charts stay native and quiet
add_chart for anything PowerPoint can chart (column, bar, line, area,
pie, doughnut, scatter via XyChartData). Style every chart with
style_chart(chart, colors) — it kills the auto-title, labels values
directly where possible, drops the value axis when labels replace it, and
quiets gridlines to hairlines. Then:
- One chart per slide; the slide title is its claim; source caption below
in 9.5pt italic muted.
- Single series → no legend; 2+ series → bottom legend. Put a tinted
takeaway panel beside the chart instead of making the chart bigger.
- Series colors come from your palette (or a
tints() ramp of it).
- No chart images — only PowerPoint-impossible visuals (Sankey, network)
may be rendered images.
5. Workflow
- Design constants + outline (steps above).
- Write
build_deck.py, run with bash under $DSH_OFFICE_PYTHON, fix
errors, re-run until clean.
- Verify structure in code: reopen the saved file, assert slide count
and chart count; print two lines.
- Visual QA — required, every build:
$DSH_OFFICE_PYTHON <skill-dir>/scripts/render_slides.py deliverables/x.pptx --out qa
View qa/sheet.png, then any suspect slide full-size. Fix in the build
script and re-render — never hand-edit the pptx. Look for: text
overflowing its box (the #1 defect), overlaps, elements < 0.3" apart,
uneven gaps, low-contrast text or chart labels, orphaned/widow words,
captions colliding with content.
- Save to
deliverables/<slug>.pptx, call deliver with the path, then
paste the one-line-per-slide outline so the user can request swaps.
python-pptx foot-guns (each of these bites)
RGBColor.from_string("4361EE") — hex without #. An # corrupts nothing here but from_string raises; never paste CSS strings elsewhere.
tf.text = "..." destroys formatting — it collapses to one unstyled run. Set text on runs, style run.font.
- New textboxes:
word_wrap = True, auto_size = None, and zero the margins when text must align with a shape edge — default margins are ~0.1" and misalign everything.
- New autoshapes: run
strip_style(shape) — python-pptx attaches a theme style ref that PowerPoint/LibreOffice resolve differently (phantom shadows, wrong lines).
- Shadows: don't rely on
shape.shadow toggles alone — declare soft_shadow(shape) so both renderers agree; negative/implicit offsets differ between them.
- Rounded rectangle radius:
shape.adjustments[0] = 0.05 (fraction of the smaller side).
- Chart auto-title: python-pptx writes
autoTitleDeleted=0, so PowerPoint invents a title from a single series' name. chart.has_title = False, always.
- Data labels: set
plot.has_data_labels = True BEFORE touching plot.data_labels (it raises otherwise). OUTSIDE_END is valid on column/bar/pie; on stacked use CENTER only.
- Pie slices need per-point fills (
series[0].points[i].format.fill) — series-level fill does nothing. Doughnut labels sit INSIDE slices: darken slice colors (white bold labels) or they vanish.
- Tables: kill the theme banding (
table.first_row = False, table.horz_banding = False) and fill every cell explicitly; style with fills + cell_border_bottom hairlines only.
- Bullets: plain textboxes have none, and a literal "• " character double-indents wrapped lines. Use
bullet(paragraph, accent_hex).
- Coordinates past the slide edge are written, not clamped — off-slide shapes silently vanish from view. 13.333 × 7.5 in; keep ≥ 0.5" margins.
1---2name: powerpoint-deck3description: Design and build a polished PowerPoint deck by writing and running a python-pptx script: a fresh, topic-informed visual design every time — palette, typography, motif, varied layouts, styled native charts, speaker notes, and a mandatory visual QA render. Use for any slide deliverable: business reviews, pitch decks, reports, training decks, announcements.4---56# Designing a deck (PPTX)78You build decks by **writing a python-pptx script and running it** with9`$DSH_OFFICE_PYTHON`. There is no template: **invent a new design for every10deck** — colors chosen for this topic, a motif, varied layouts. Two decks on11different subjects should never look like siblings.1213Helper primitives (styled charts, bullets, shadows — they take your colors,14no layout opinions): `scripts/pptx_helpers.py`. Visual QA renderer:15`scripts/render_slides.py`. Resolve both against this skill's base directory.1617## 1. Design before you code1819**Start from the topic, never from a previous deck.** Before writing any20code, write a 3-line **design brief**: two or three candidate directions —21each a palette (dominant / support / accent hexes), a motif, a font pairing,22and a one-word persona from the list in `references/design.md` — then pick23the least obvious one and say in one sentence why it fits *this* audience24and *this* moment. Name the clichéd choice for the topic explicitly so you25can avoid it. Check the workspace first: if a previous build script or deck26exists there, its design is TAKEN — a new deck for a different topic must27not reuse its palette, motif, or script (start a fresh script file; do not28edit the old one). Same topic, new engagement (this quarter's review after29last quarter's) should also shift — evolve the palette and layout plan30rather than reskinning.3132Write the chosen design as constants at the top of the build script:3334- **Palette for THIS topic's concrete world** — not its category. "AI" does35 not mean dark + cyan glow; "finance" does not mean default navy;36 "sustainability" does not have to be green. Derive colors from something37 tangible about the subject — its industry's documents, its product, its38 season, its geography — and sanity-check: would this deck be mistakable39 for a generic tech template? If yes, redo it. Pick one dominant color40 (~60-70% of the visual weight), 1-2 supporting tones, ONE sharp accent41 (~10%, reserved for the numbers and phrases that matter). Derive tints42 with `blend()`/`tints()` instead of inventing extra hues. The palettes in43 `references/design.md` are raw material for synthesis — never pick a row44 verbatim, and never pick the same row twice in one workspace.45- **Font pairing from the safe list** (these ship with Office AND render46 true-to-width in the QA preview): headers **Cambria** (serif, editorial)47 or **Arial** (bold, modern); body **Calibri**. Contrast serif headers over48 sans body for a distinctive, zero-risk pairing. Never Aptos; Georgia or49 Trebuchet only if the user asks (their QA preview is unreliable — leave50 ~10% extra width slack there).51- **One motif, carried everywhere** — e.g. numbers in colored circles,52 oversized ghost numerals, soft translucent circles on dark slides, thick53 serif quote marks. Pick ONE and repeat it. Never an accent bar or stripe54 (see Never).55- **Sandwich structure**: dark cover and closing (optionally dark section56 dividers and a dark pull-quote breather), white content slides. Or commit57 to dark throughout for a premium feel — but then text contrast must be58 flawless.59- **Backgrounds are white** (`FFFFFF`) unless the user's brand says otherwise.60 Cream/beige defaults read as template filler.6162## 2. Write the story before the slides6364Outline first: cover → one context slide → 2-4 supporting beats with65evidence (charts, stats, tables) → what's next → closing. 6-12 content66slides. One idea per slide. Then map each slide to a DIFFERENT layout —67see the layout patterns in `references/design.md`.6869- **Titles are claims**, not topics: "Payback fell from 14 to 9 months", not70 "Unit economics". ≤ 9 words, ≥ 30pt bold, left-aligned.71- **Body 14-15pt, left-aligned** (never center body text), ≤ 5 bullets of72 ≤ 2 lines, parallel grammar, bold lead-in phrase + normal rest.73- **Speaker notes** go in the notes pane (`slide.notes_slide...`), never in74 a text box on the slide.7576## 3. Every slide needs a visual7778Text-only slides are forgettable. Each content slide gets at least one of:79a native chart, big stat callouts, a table, numbered cards with a tinted80fill, a timeline, a half-bleed image (crop with `crop_to()`, never81squash), or a bold pull-quote. Icons: draw simple geometric marks with82shapes (circles, rings, bars) in palette colors — or skip them; a clean83number chip beats a bad icon.8485## 4. Charts stay native and quiet8687`add_chart` for anything PowerPoint can chart (column, bar, line, area,88pie, doughnut, scatter via `XyChartData`). Style every chart with89`style_chart(chart, colors)` — it kills the auto-title, labels values90directly where possible, drops the value axis when labels replace it, and91quiets gridlines to hairlines. Then:9293- One chart per slide; the slide title is its claim; source caption below94 in 9.5pt italic muted.95- Single series → no legend; 2+ series → bottom legend. Put a tinted96 takeaway panel beside the chart instead of making the chart bigger.97- Series colors come from your palette (or a `tints()` ramp of it).98- No chart images — only PowerPoint-impossible visuals (Sankey, network)99 may be rendered images.100101## 5. Workflow1021031. Design constants + outline (steps above).1042. Write `build_deck.py`, run with bash under `$DSH_OFFICE_PYTHON`, fix105 errors, re-run until clean.1063. **Verify structure in code**: reopen the saved file, assert slide count107 and chart count; print two lines.1084. **Visual QA — required, every build**:109 ```bash110 $DSH_OFFICE_PYTHON <skill-dir>/scripts/render_slides.py deliverables/x.pptx --out qa111 ```112 View `qa/sheet.png`, then any suspect slide full-size. Fix in the build113 script and re-render — never hand-edit the pptx. Look for: text114 overflowing its box (the #1 defect), overlaps, elements < 0.3" apart,115 uneven gaps, low-contrast text or chart labels, orphaned/widow words,116 captions colliding with content.1175. Save to `deliverables/<slug>.pptx`, call **deliver** with the path, then118 paste the one-line-per-slide outline so the user can request swaps.119120## python-pptx foot-guns (each of these bites)121122- `RGBColor.from_string("4361EE")` — hex **without** `#`. An `#` corrupts nothing here but `from_string` raises; never paste CSS strings elsewhere.123- **`tf.text = "..."` destroys formatting** — it collapses to one unstyled run. Set text on `run`s, style `run.font`.124- New textboxes: `word_wrap = True`, `auto_size = None`, and zero the margins when text must align with a shape edge — default margins are ~0.1" and misalign everything.125- New autoshapes: run `strip_style(shape)` — python-pptx attaches a theme style ref that PowerPoint/LibreOffice resolve differently (phantom shadows, wrong lines).126- Shadows: don't rely on `shape.shadow` toggles alone — declare `soft_shadow(shape)` so both renderers agree; negative/implicit offsets differ between them.127- Rounded rectangle radius: `shape.adjustments[0] = 0.05` (fraction of the smaller side).128- Chart auto-title: python-pptx writes `autoTitleDeleted=0`, so PowerPoint invents a title from a single series' name. `chart.has_title = False`, always.129- Data labels: set `plot.has_data_labels = True` BEFORE touching `plot.data_labels` (it raises otherwise). `OUTSIDE_END` is valid on column/bar/pie; on stacked use `CENTER` only.130- Pie slices need per-point fills (`series[0].points[i].format.fill`) — series-level fill does nothing. Doughnut labels sit INSIDE slices: darken slice colors (white bold labels) or they vanish.131- Tables: kill the theme banding (`table.first_row = False`, `table.horz_banding = False`) and fill every cell explicitly; style with fills + `cell_border_bottom` hairlines only.132- Bullets: plain textboxes have none, and a literal "• " character double-indents wrapped lines. Use `bullet(paragraph, accent_hex)`.133- Coordinates past the slide edge are written, not clamped — off-slide shapes silently vanish from view. 13.333 × 7.5 in; keep ≥ 0.5" margins.