$37
Generate professional, on-brand presentation slides using python-pptx. This skill supports:
brands/IMPORTANT: All skill resources are in .agents/skills/pptx-generator/. Always use Glob patterns starting with .agents/skills/pptx-generator/ to find files.
NEVER generate more than 5 slides at once.
| Rule | Details |
|---|---|
| Max slides per batch | 5 (can be 1, 2, 3, 4, or 5) |
| After each batch | STOP and validate output |
| Validation required | Check: no duplicate titles, proper spacing, correct colors |
| Continue when | Validation passes |
| After ALL batches | COMBINE into single file and DELETE part files |
This prevents token limit errors and catches quality issues early.
CRITICAL: Always clean up part files after combining. The user should only see ONE final PPTX file, not multiple part files.
Before generating slides, check if any brands exist.
Glob: .claude/skills/pptx-generator/brands/*/brand.json
If NO brands found (only template/ exists):
STOP - Do not proceed with slide generation
Ask the user:
"No brands are configured yet. Would you like me to help you create a brand first? I'll need your brand colors, fonts, and style guidelines to set this up."
If user wants to create a brand, follow the Creating a New Brand section below.
If user declines, explain that slides require a brand configuration and offer to use generic styling as a fallback.
When no brands exist or user requests a new brand:
Read: .claude/skills/pptx-generator/brands/template/README.md
Read: .claude/skills/pptx-generator/brands/template/brand.json
Read: .claude/skills/pptx-generator/brands/template/config.json
Ask the user for (or extract from provided materials):
| Required | Description |
|---|---|
| Brand name | Folder name (lowercase, no spaces) |
| Colors | Background, text, accent colors (hex codes) |
| Fonts | Heading font, body font, code font |
| Optional | Description |
|---|---|
| Output directory | Where to save generated files (default: output/{brand}) |
| Logo | Path to logo file (PNG/SVG) |
| Brand guidelines | Existing style guide or website to reference |
| Tone of voice | Writing style, vocabulary preferences |
Create the brand folder:
mkdir -p .claude/skills/pptx-generator/brands/{brand-name}
Create brand.json with the gathered values:
{
"name": "Brand Name",
"description": "One-line description",
"colors": {
"background": "hex-without-hash",
"background_alt": "hex-without-hash",
"text": "hex-without-hash",
"text_secondary": "hex-without-hash",
"accent": "hex-without-hash",
"accent_secondary": "hex-without-hash",
"accent_tertiary": "hex-without-hash",
"code_bg": "hex-without-hash",
"card_bg": "hex-without-hash",
"card_bg_alt": "hex-without-hash"
},
"fonts": {
"heading": "Font Name",
"body": "Font Name",
"code": "Monospace Font"
},
"assets": {
"logo": "assets/logo.png",
"logo_dark": null,
"icon": null
}
}
Create config.json with output settings:
{
"output": {
"directory": "output/{brand}",
"naming": "{name}-{date}",
"keep_parts": false
},
"generation": {
"slides_per_batch": 5,
"auto_combine": true,
"open_after_generate": false
},
"defaults": {
"slide_width_inches": 13.333,
"slide_height_inches": 7.5
}
}
Create brand-system.md - Copy from template and fill in brand guidelines
Create tone-of-voice.md - Copy from template and fill in voice guidelines
Add assets - Copy logo/images to brands/{brand-name}/assets/
After creating the brand, verify with:
Glob: .claude/skills/pptx-generator/brands/{brand-name}/*
Then proceed to slide generation.
This skill operates in four modes:
User wants presentation slides (16:9) created using a brand's styling.
→ Follow: Brand Discovery → Layout Selection → Content Adaptation → Execute
→ Layouts in: cookbook/*.py
User wants a LinkedIn carousel (square 1:1 format) for social media.
→ Follow: Brand Discovery → Carousel Planning → Generate → Export PDF
→ Layouts in: cookbook/carousels/*.py
User wants to create, edit, or improve layout templates. → Follow: Layout CRUD Operations section
User wants Ericsson LTE/NR RAN feature documentation slides.
→ Follow: Feature Discovery → Load Ericsson Brand → Select Mode → Generate
→ Layouts in: cookbook/ran/*.py
→ Data from: elex-ran-features skill (features.json)
List available brands:
Glob: .claude/skills/pptx-generator/brands/*/brand.json
Extract unique brand names from paths (e.g., brands/rasmus/... → "rasmus")
Read the brand configuration files:
Read: .claude/skills/pptx-generator/brands/{brand-name}/brand.json
Read: .claude/skills/pptx-generator/brands/{brand-name}/config.json
brand.json - Colors, fonts, assetsconfig.json - Output directory, generation settingsRead supporting markdown files for context:
Glob: .claude/skills/pptx-generator/brands/{brand-name}/*.md
These provide voice, tone, and design philosophy.
Extract from brand files:
If brand not found, list available brands and ask user to choose.
⚠️ MANDATORY: Read ALL layout frontmatters before selecting any layout.
This step is critical for making informed layout decisions. You must understand what ALL layouts offer before choosing.
Step 2a: Discover all layouts:
Glob: .claude/skills/pptx-generator/cookbook/*.py
Step 2b: Read EVERY layout file (not just one or two):
For each .py file found, read the first 40 lines to extract the # /// layout frontmatter block. Build a mental map of:
purpose, best_for)avoid_when)max_*, min_*, *_max_chars)The frontmatter block looks like this:
# /// layout
# name = "floating-cards-slide"
# purpose = "Feature highlights, process steps, multiple equal items with depth"
# best_for = [
# "Exactly 3 related features or concepts",
# "Process with 3 steps",
# ]
# avoid_when = [
# "More than 3 items - use multi-card-slide instead",
# "Long card titles (over 15 characters)",
# ]
# max_cards = 3
# card_title_max_chars = 15
# instructions = [
# "EXACTLY 3 cards required - no more, no less",
# "Card titles must be SHORT: 1-2 words, max 15 characters",
# ]
# ///
Key frontmatter fields:
| Field | Description |
|---|---|
name | Layout identifier |
purpose | What this layout is for |
best_for | Ideal use cases (array) |
avoid_when | When NOT to use this layout (array) |
max_* / min_* | Item limits (cards, bullets, stats) |
instructions | Specific tips for using this layout |
Step 2c: Select layouts (only AFTER reading all frontmatters):
Now that you know all available layouts and their constraints:
best_for criteriaavoid_when → Don't use a layout in situations it warns againstmax_*, use a different layoutExample selection process:
floating-cards-slide: max_cards = 3 → Won't workmulti-card-slide: max_cards = 5 → Perfect fitmulti-card-slideWhy read ALL frontmatters?
avoid_when (e.g., "use multi-card-slide instead")🎨 DEFAULT TO VISUAL LAYOUTS. Content-slide is the LAST RESORT, not the default.
The biggest mistake in presentation generation is defaulting to content-slide (title + bullets) whenever you have information to convey. This creates repetitive, boring presentations.
Common failure pattern:
HARD LIMITS:
Ask these questions IN ORDER before defaulting to content-slide:
Do I have 3-5 equal items?
YES → Use multi-card-slide (not content-slide)
Do I have 2-4 big numbers/metrics?
YES → Use stats-slide (not content-slide)
Am I comparing two things?
YES → Use two-column-slide (not content-slide)
Do I have a central concept with surrounding items?
YES → Use circular-hero-slide (not content-slide)
Do I have exactly 3 related items?
YES → Use floating-cards-slide (not content-slide)
Do I have 1-3 words I want to emphasize dramatically?
YES → Use giant-focus-slide or bold-diagonal-slide (not content-slide)
Do I have a powerful quote or principle?
YES → Use quote-slide (not content-slide)
Is this the ONLY way to present this information?
YES → NOW you can use content-slide
NO → Go back through the decision tree
Example 1: "Validation Patterns"
❌ BAD (content-slide):
Title: Validation Patterns
Bullets:
- Run comprehensive test suites
- Type checking and linting
- Code review by humans and AI
- Deployment previews
✅ GOOD (multi-card-slide):
Title: Validation Patterns
Cards:
1. Testing | Run comprehensive test suites after every change
2. Linting | Type checking and formatting as guardrails
3. Review | Human and AI code review process
4. Preview | Deployment previews for visual regression
Example 2: "Why PIV Works"
❌ BAD (content-slide):
Title: Why PIV Works
Bullets:
- Forces planning before implementation
- Validation catches issues immediately
- Iterative improvements compound
- System gets smarter with every bug
✅ GOOD (floating-cards-slide with 3 cards):
Title: Why PIV Works
Cards:
1. Plan First | Forces architectural thinking before coding
2. Fast Feedback | Validation catches issues immediately
3. Compounds | System improves with every bug
(Note: Reduced from 4 to 3 items to fit floating-cards-slide max_cards limit)
Example 3: "Human-in-the-Loop Strategy"
❌ BAD (content-slide):
Title: Human-in-the-Loop Strategy
Bullets:
- In-the-loop: Human approves before execution
- On-the-loop: Human reviews after completion
- Code review remains critical
- AI generates, humans validate
✅ GOOD (two-column-slide):
Title: Human-in-the-Loop Strategy
Left: In-the-Loop
- Human approves before execution
- Critical for production changes
- Quality gateway
Right: On-the-Loop
- Human reviews after completion
- Faster iteration cycles
- AI generates, human validates
Before planning any slide, ask yourself:
Use content-slide ONLY when:
Never use content-slide as your default thinking.
| Content Type | Best Layout | Why |
|---|---|---|
| 3-5 equal features/steps | multi-card-slide | Cards create visual hierarchy |
| Exactly 3 featured items | floating-cards-slide | Elevated cards add depth |
| 2-4 metrics/KPIs | stats-slide | Big numbers grab attention |
| Before/after comparison | two-column-slide | Side-by-side shows contrast |
| Hub concept with types | circular-hero-slide | Radiating pattern shows relationships |
| Dramatic emphasis (1-3 words) | giant-focus-slide | Scale creates impact |
| High-energy warning | bold-diagonal-slide | Dynamic shapes convey urgency |
| Powerful quote/principle | quote-slide | Attribution adds authority |
| List of related items | multi-card-slide | Better than bullets |
| Process with steps | floating-cards-slide | Visual flow beats text |
| Technical comparison | two-column-slide | Structured comparison |
Only use content-slide when:
Before generating ANY slides, create a written plan.
This applies to single slides, batches, and full presentations. Planning prevents:
Create a slide plan table:
| # | Layout | Title | Key Content | Notes |
|---|--------|-------|-------------|-------|
| 1 | title-slide | [Title] | [Subtitle, author] | Opening slide |
| 2 | content-slide | [Title] | [3-4 bullet points] | Main concepts |
| 3 | stats-slide | [Title] | [2-3 metrics] | Impact data |
| ... | ... | ... | ... | ... |
For each slide, specify:
Planning checklist:
After planning, briefly present the plan before generating.
Example of good variety distribution for 30-slide presentation:
For each slide in your plan:
IMPORTANT: Follow these rules for ALL slide text.
| Element | Rule | Example |
|---|---|---|
| Titles | No trailing periods or commas | "Why AI Matters" not "Why AI Matters." |
| Subtitles | No trailing punctuation | "The future of coding" not "The future of coding." |
| Bullet points | No trailing periods (unless full sentences) | "Faster development" not "Faster development." |
| Headlines | Minimal punctuation, no ellipsis | "What's Next" not "What's Next..." |
| Stats/Numbers | Clean format, no trailing punctuation | "50%" not "50%." |
| CTAs | No trailing punctuation | "Get Started" not "Get Started." |
| Labels | Short, no punctuation | "Step 1" not "Step 1:" |
Avoid:
Exception: Full sentence descriptions or quotes may use appropriate punctuation.
Map brand.json values to layout placeholders:
| Layout Placeholder | brand.json Path |
|---|---|
BRAND_BG | colors.background |
BRAND_BG_ALT | colors.background_alt |
BRAND_TEXT | colors.text |
BRAND_TEXT_SECONDARY | colors.text_secondary |
BRAND_ACCENT | colors.accent |
BRAND_ACCENT_SECONDARY | colors.accent_secondary |
BRAND_ACCENT_TERTIARY | colors.accent_tertiary |
BRAND_CODE_BG | colors.code_bg |
BRAND_CARD_BG | colors.card_bg |
BRAND_CARD_BG_ALT | colors.card_bg_alt |
BRAND_HEADING_FONT | fonts.heading |
BRAND_BODY_FONT | fonts.body |
BRAND_CODE_FONT | fonts.code |
Note: All color values in brand.json are hex WITHOUT the # prefix.
Write content in brand's voice (from tone-of-voice.md)
Preserve layout structure (decorative elements, spacing, hierarchy)
MAXIMUM 5 SLIDES PER BATCH. This is a hard limit.
When generating multiple slides:
Why batching matters:
⚠️ CRITICAL BACKGROUND BUG FIX:
EVERY slide MUST have its background explicitly set. If you don't set slide.background.fill.solid() and slide.background.fill.fore_color.rgb, the slide will use PowerPoint's default WHITE background, making text unreadable on dark-themed brands.
Mandatory for every slide:
slide = prs.slides.add_slide(prs.slide_layouts[6])
slide.background.fill.solid() # ← REQUIRED
slide.background.fill.fore_color.rgb = hex_to_rgb(BRAND_BG) # ← REQUIRED
This is especially critical when:
Execution:
PREFERRED: Use heredoc (no files created):
uv run --with python-pptx==1.0.2 python << 'EOF'
# [Adapted code with brand values and content]
EOF
IF heredoc fails (Windows issues): Use temp directory:
# Create temp directory if needed
mkdir -p .claude/skills/pptx-generator/.tmp
# Write script to temp directory
# (create file at .claude/skills/pptx-generator/.tmp/gen.py)
# Execute
uv run --with python-pptx==1.0.2 python .claude/skills/pptx-generator/.tmp/gen.py
# MANDATORY: Clean up immediately after execution
rm .claude/skills/pptx-generator/.tmp/gen.py
CRITICAL: Never create Python files in the repository root. Always use heredoc or temp directory within the skill folder.
After EVERY batch, validate before continuing:
| Issue | What to Look For | Fix |
|---|---|---|
| White background | Slide has white background instead of brand color | Add slide.background.fill.solid() and set fore_color.rgb |
| Duplicate titles | Same title text appearing twice on a slide | Remove duplicate text boxes |
| Spacing problems | Title too close to subtitle/content | Increase Y position of lower elements |
| Text overflow | Content extending beyond slide bounds | Reduce font size or split content |
| Missing elements | Decorative elements not rendering | Check shape positions and colors |
| Wrong colors | Colors not matching brand | Verify hex values (no # prefix in code) |
| Bad punctuation | Trailing periods/commas on titles/bullets | Remove unnecessary punctuation |
If issues found:
If validation passes:
Use the output settings from config.json:
| Config Setting | Default | Description |
|---|---|---|
output.directory | output/{brand} | Where to save files |
output.naming | {name}-{date} | File naming pattern |
output.keep_parts | false | Keep part files after combining |
Resolve placeholders:
{brand} → Brand folder name{name} → Presentation name from user request{date} → Current date (YYYY-MM-DD)# Create output directory from config
mkdir -p {resolved-output-directory}
Batched generation workflow:
{name}-part1.pptx, {name}-part2.pptx, etc.auto_combine is true)keep_parts is false)🚨 CRITICAL BUG WARNING: BACKGROUND MUST BE SET WHEN COMBINING 🚨
When combining presentations, add_slide() creates slides with DEFAULT WHITE BACKGROUNDS. Shape copying does NOT copy the slide background property. You MUST explicitly set the background immediately after creating each new slide.
This is the most common source of white slides in combined presentations.
After all batches are validated, combine them into a single PPTX:
uv run --with python-pptx==1.0.2 python << 'SCRIPT'
from pptx import Presentation
from pptx.dml.color import RGBColor
from pathlib import Path
import shutil
def hex_to_rgb(hex_color: str) -> RGBColor:
h = hex_color.lstrip("#")
return RGBColor(int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16))
# Brand background color (get from brand.json)
BRAND_BG = "REPLACE_WITH_BRAND_BACKGROUND" # e.g., "07090F"
# List all part files in order
output_dir = Path("output/{brand-name}")
part_files = sorted(output_dir.glob("{name}-part*.pptx"))
if len(part_files) > 1:
# Start with first part as base
combined = Presentation(part_files[0])
# Add slides from remaining parts
for part_file in part_files[1:]:
part_prs = Presentation(part_file)
for slide in part_prs.slides:
# Copy slide layout and add to combined
blank_layout = combined.slide_layouts[6]
new_slide = combined.slides.add_slide(blank_layout)
# 🚨 CRITICAL: Set background IMMEDIATELY after creating slide
# PowerPoint defaults to WHITE background - this MUST be set before copying shapes
new_slide.background.fill.solid()
new_slide.background.fill.fore_color.rgb = hex_to_rgb(BRAND_BG)
# Copy all shapes from source slide
for shape in slide.shapes:
# Clone shape to new slide
el = shape.element
new_slide.shapes._spTree.insert_element_before(
el, 'p:extLst'
)
# Save combined file
combined.save(output_dir / "{name}-final.pptx")
print(f"Combined {len(part_files)} parts into {name}-final.pptx")
# MANDATORY: Clean up part files - user should only see final file
for part_file in part_files:
part_file.unlink()
print(f"Deleted {part_file.name}")