
Morph Ppt
- 29 installs
- 25.5k repo stars
- Updated August 4, 2026
- iofficeai/officecli
Generate PowerPoint .pptx decks with smooth Morph transition animations using officecli.
About
Generates PowerPoint .pptx decks with smooth Morph transition animations using officecli. A developer uses it to produce visually animated presentations where shapes with identical names morph across slides.
- Generates .pptx decks with smooth PowerPoint Morph transitions
- Morph matches shapes by identical names across adjacent slides
Morph Ppt by the numbers
- 29 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #413 of 688 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/iofficeai/officecli --skill morph-pptAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 29 |
|---|---|
| repo stars | ★ 25.5k |
| Last updated | August 4, 2026 |
| Repository | iofficeai/officecli ↗ |
What it does
Generate PowerPoint .pptx decks with smooth Morph transition animations using officecli.
Files
Morph
Generate visually compelling PPTs with smooth Morph animations.
Philosophy: Trust yourself to learn through practice. This skill provides workflow and references — you bring creativity and judgment.
---
Use when
- User wants to generate a
.pptx
---
What is Morph?
PowerPoint's Morph transition creates smooth animations by matching shapes with identical names across adjacent slides.
Slide 1: shape name="!!circle" x=5cm width=8cm
Slide 2: shape name="!!circle" x=20cm width=12cm
↓
Result: Circle smoothly moves and growsThree core concepts:
- Scene Actors: Persistent shapes with
!!prefix that evolve across slides - Ghosting: Move shapes to
x=36cm(off-screen) instead of deleting - Content: Text/data added fresh per slide, previous content ghosted first
For details: reference/pptx-design.md
---
Workflow
Phase 1: Understand the Topic
Ask only when topic is unclear, otherwise proceed directly.
---
⚠️ CRITICAL KNOWN ISSUE: Name-based path selectors break after `transition=morph` is set
After callingofficecli set '/slide[N]' --prop transition=morph, paths like/slide[N]/!!my-shapereturn 'Element not found'. The CLI auto-prepends!!to shape names when morph is applied, which invalidates name-based lookups.
>
Workaround: Always use shape INDEX paths instead of name paths when accessing shapes on morph slides:
```bash
# WRONG (after transition=morph set):
officecli get deck.pptx '/slide[3]/!!my-circle' --depth 1
>
# CORRECT:
officecli get deck.pptx '/slide[3]' --depth 1 # first list all shapes to find index
officecli get deck.pptx '/slide[3]/shape[2]' --depth 1
```
The build.py template should use inspect() + index-based access throughout.---
Phase 2: Plan the Story
FIRST: Read the thinking framework
→ Open and read reference/decision-rules.md — it provides the structured approach for planning compelling presentations (Pyramid Principle, SCQA, page types).
Then create `brief.md` with:
- Context: Topic, audience, purpose, narrative structure (SCQA or Problem-Solution)
- Outline: Conclusion first + slide-by-slide summary
- Page briefs: For each slide:
- Objective (what should this slide achieve?)
- Content (specific text/data to include)
- Page type (title | evidence | transition | conclusion)
- Design notes (visual emphasis, scene actor behavior)
Morph Pair Scene Planning (REQUIRED before building)
For every morph transition, plan the slide pair BEFORE writing any code. Use a table like this in brief.md:
| Pair | Slide A (start) | Slide B (end) | Visual narrative purpose |
|---|---|---|---|
| 1→2 | Ring centered, title appears | Ring shifts right, subtitle revealed | Attention → context |
| 2→3 | Feature box large | Feature box small, metric card grows | Zoom out → detail |
| 3→4 | Metric card exits (ghost), new actor enters | Actor repositions | Section transition |
Rules for the planning table:
- Determine ALL
!!shape names during planning — the same name must be used identically across the slide pair - For each
!!shape, decide its role:!!scene-{desc}(background/decoration) or!!actor-{desc}(content/foreground) - Mark which shapes need to be ghosted at each section transition
- Do NOT start building until the naming table is complete — renaming shapes mid-build causes ghost accumulation bugs
---
Phase 3: Design and Generate
Before generation starts, always remind the user:
- The PPT file may be rewritten multiple times during build.
- Once the PPT file appears in the workspace, the user can preview the live generation progress directly in AionUi.
- Do not click "Open with system app" during generation, to avoid file lock / write conflicts.
- Use clear, direct language and make this a concrete warning, not an optional suggestion.
FIRST: Install `officecli` if needed
Follow the install section in reference/officecli-pptx-min.md section 0.
IMPORTANT: Use morph-helpers for reliable workflow
Generate a Python script that uses reference/morph-helpers.py — this provides helper functions with built-in verification. Python works cross-platform (Mac / Windows / Linux).
Shape naming rules (for best results):
Use these naming patterns for clear code and reliable verification:
Namespace prefixes for `!!` shapes — prevent scene collision:
All persistent !! shapes MUST use one of these two prefixes to avoid morph engine confusion when multiple morph pairs share similar shape names:
!!scene-{desc}— Background / decoration shapes (e.g.,!!scene-ring,!!scene-bg-gradient,!!scene-grid-line)- These persist across the entire deck; move them for motion but rarely ghost them
!!actor-{desc}— Content / foreground shapes (e.g.,!!actor-feature-box,!!actor-metric,!!actor-label)- These carry slide-specific content; ghost them at section boundaries
*Rule: `!!scene- and !!actor-` names must NEVER be identical.* Bad: !!scene-card and !!actor-card in the same deck — morph engine will confuse them. Good: !!scene-card-bg and !!actor-card-content — unambiguous.
1. Scene actors (persistent across slides):
- Format:
name=!!scene-{desc}orname=!!actor-{desc} - Examples:
name=!!scene-ring,name=!!scene-dot,name=!!actor-feature-box - Behavior: Modify position/size/color across slides — do NOT delete
- Exit strategy — two trigger scenarios:
1. Permanent exit (shape no longer needed): Move it off-screen to x=36cm. Morph will smoothly slide it out of view. Example: officecli set deck.pptx '/slide[N]/!!FeatureBox' --prop x=36cm --prop y=14cm To bring it back on a later slide, simply move it back to a visible position. 2. Scene transition exit (entering a new topic section): When the presentation moves into a new thematic section, ALL !! content shapes from the previous section must also be ghosted to x=36cm. Only decoration actors that persist throughout the entire deck (e.g., a background ring) should remain visible.
# Entering new section: ghost all previous section's !! content shapes
# First, check what !! shapes are on the current slide
officecli get deck.pptx '/slide[N]' --depth 1
# Then ghost each one
officecli set deck.pptx '/slide[N]/!!FeatureBox' --prop x=36cm
officecli set deck.pptx '/slide[N]/!!MetricCard' --prop x=36cm
officecli set deck.pptx '/slide[N]/!!ChannelLabel' --prop x=36cmRule: Each new section's first slide should be clean — only current-section actors visible; no leftover shapes from the previous section.
2. Content shapes (unique per slide):
- Format:
name=#sN-description - Pattern:
#+s+ slide_number +-+ description - Examples:
name=#s1-title,name=#s2-card1,name=#s3-stats - Behavior: Ghost (x=36cm) when moving to next slide
Ghost accumulation — critical behavior to understand:
Once a !!-prefixed shape appears on any slide, it persists and remains visible on every subsequent morph slide unless explicitly moved off-screen.This means:
- A
!!actor-feature-boxintroduced on slide 3 will still be visible on slides 4, 5, 6, 7 ... unless you ghost it - Ghost accumulation builds silently — visual clutter compounds across the deck
- The
morph_final_checktool does NOT catch!!shapes that linger in the visible area; only screenshot verification can detect this
Ghost cleanup pattern — when a !!actor-* shape is no longer needed, exit it explicitly:
# Pattern: after the last slide where !!actor-feature-box is needed,
# on the NEXT slide's setup, move it off-screen BEFORE adding new content
officecli set deck.pptx '/slide[N]/shape[X]' --prop x=36cm --prop y=10cm
# If the shape served a 2-slide story arc (slides 3→4), ghost it on slide 5:
helper("ghost", OUTPUT, 5, <shape_index_of_actor_feature_box>)Rule: For every !!actor-* shape, its "ghost slide" (where it exits) must be planned in the Phase 2 morph pair table. Do not leave any !!actor-* shape without a planned exit.
Why this naming matters:
- ✅ Better detection: Primary method (
#sN-pattern matching) is fastest and most accurate - ✅ Readable code: Anyone can tell
#s1-titleis slide 1's title - ✅ Easy debugging:
grep "#s1-"finds all slide 1 content quickly - ⚠️ Backup detection exists: Even without
#prefix, duplicate text detection will catch most issues (but has edge cases)
Bottom line: Follow these patterns in your code examples, and verification will work smoothly.
Then proceed with pattern:
#!/usr/bin/env python3
import subprocess, sys, os
def run(*args):
result = subprocess.run(list(args))
if result.returncode != 0:
sys.exit(result.returncode)
# Load helper functions (provides morph_clone_slide, morph_ghost_content, morph_verify_slide)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
def helper(*args):
run(sys.executable, os.path.join(SCRIPT_DIR, "reference", "morph-helpers.py"), *[str(a) for a in args])
OUTPUT = "deck.pptx"
run("officecli", "create", OUTPUT)
run("officecli", "open", OUTPUT) # Resident mode — all commands run in memory
# ============ SLIDE 1 ============
print("Building Slide 1...")
run("officecli", "add", OUTPUT, "/", "--type", "slide")
run("officecli", "set", OUTPUT, "/slide[1]", "--prop", "background=1A1A2E")
# Scene actors (!!scene-* prefix = decoration, persists entire deck)
run("officecli", "add", OUTPUT, "/slide[1]", "--type", "shape",
"--prop", "name=!!scene-ring", "--prop", "preset=ellipse", "--prop", "fill=E94560",
"--prop", "opacity=0.3", "--prop", "x=5cm", "--prop", "y=3cm", "--prop", "width=8cm", "--prop", "height=8cm")
run("officecli", "add", OUTPUT, "/slide[1]", "--type", "shape",
"--prop", "name=!!scene-dot", "--prop", "preset=ellipse", "--prop", "fill=0F3460",
"--prop", "x=28cm", "--prop", "y=15cm", "--prop", "width=1cm", "--prop", "height=1cm")
# Content shapes (#s1- prefix, will be ghosted on next slide)
# Use generous width (25-30cm for titles) to avoid text wrapping!
run("officecli", "add", OUTPUT, "/slide[1]", "--type", "shape",
"--prop", "name=#s1-title", "--prop", "text=Main Title",
"--prop", "font=Arial Black", "--prop", "size=64", "--prop", "bold=true",
"--prop", "color=FFFFFF", "--prop", "x=10cm", "--prop", "y=8cm",
"--prop", "width=28cm", "--prop", "height=3cm", "--prop", "fill=none")
# ============ SLIDE 2 ============
print("Building Slide 2...")
# Use helper: automatically clone + set transition + list shapes + verify
helper("clone", OUTPUT, 1, 2)
# Use helper: ghost all content from slide 1 (shape index 3 = #s1-title)
helper("ghost", OUTPUT, 2, 3)
# Add new content for slide 2
run("officecli", "add", OUTPUT, "/slide[2]", "--type", "shape",
"--prop", "name=#s2-title", "--prop", "text=Second Slide",
"--prop", "font=Arial Black", "--prop", "size=64", "--prop", "bold=true",
"--prop", "color=FFFFFF", "--prop", "x=10cm", "--prop", "y=8cm",
"--prop", "width=28cm", "--prop", "height=3cm", "--prop", "fill=none")
# Adjust scene actors to create motion
# SPATIAL RULE: scene actors must stay in safe zones (see Shape naming rules above)
run("officecli", "set", OUTPUT, "/slide[2]/shape[1]", "--prop", "x=15cm", "--prop", "y=5cm") # !!scene-ring moves
run("officecli", "set", OUTPUT, "/slide[2]/shape[2]", "--prop", "x=5cm", "--prop", "y=10cm") # !!scene-dot moves
# Use helper: verify slide is correct (transition + ghosting)
helper("verify", OUTPUT, 2)
# ============ SLIDE 3 ============
print("Building Slide 3...")
# ============ SECTION TRANSITION: Ghost ALL !! content shapes from previous section ============
# Before adding new section content, ghost every !! shape that belongs to the previous section.
# Run: officecli get deck.pptx '/slide[N]' --depth 1 to list all shapes and confirm indices.
# Then ghost each previous-section actor:
# helper("ghost", OUTPUT, N, shape_index_1)
# helper("ghost", OUTPUT, N, shape_index_2)
# ... repeat for ALL !! shapes that were part of the previous section
# VERIFY: After building, open screenshot of this slide and confirm zero overlap with previous section content.
helper("clone", OUTPUT, 2, 3)
helper("ghost", OUTPUT, 3, 4) # Ghost #s2-title (now at index 4)
run("officecli", "add", OUTPUT, "/slide[3]", "--type", "shape",
"--prop", "name=#s3-title", "--prop", "text=Third Slide",
"--prop", "font=Arial Black", "--prop", "size=64", "--prop", "bold=true",
"--prop", "color=FFFFFF", "--prop", "x=10cm", "--prop", "y=8cm",
"--prop", "width=28cm", "--prop", "height=3cm", "--prop", "fill=none")
run("officecli", "set", OUTPUT, "/slide[3]/shape[1]", "--prop", "x=25cm", "--prop", "y=8cm")
run("officecli", "set", OUTPUT, "/slide[3]/shape[2]", "--prop", "x=10cm", "--prop", "y=5cm")
helper("verify", OUTPUT, 3)
# ============ FINAL VERIFICATION ============
run("officecli", "close", OUTPUT) # Save from memory to disk
print()
print("=========================================")
helper("final-check", OUTPUT)
print()
print("Build complete! Open", OUTPUT, "in PowerPoint to see morph animations.")Key advantages of using helpers:
- ✅ Fewer steps:
morph_clone_slide= clone + transition + list + verify (4 steps → 1 function) - ✅ Instant feedback: Each helper shows ✅ or ❌ immediately
- ✅ Can't forget: Transition and verification are automatic
- ✅ Clear errors: If something is wrong, you'll know exactly what and where
- ✅ Dual detection: Catches unghosted content by both naming pattern AND duplicate text detection
- Even if you forget
#prefix, duplicate detection will still catch the problem!
Scene Actor Spatial Rule (CRITICAL):
Scene actors must stay in safe zones at all times — corners and edges only. DO NOT let scene actors pass through or rest in the content area (x=2~28cm, y=3~16cm).
Safe zones:
Top-right corner: x ≥ 24cm, y ≤ 6cm
Bottom-right: x ≥ 24cm, y ≥ 12cm
Bottom-left: x ≤ 2cm, y ≥ 12cm
Off-screen (right): x ≥ 32cm (fully out of view — use for ghost position)Before planning any scene actor path, inspect existing shape coordinates:
# List all shapes on a slide (check for coordinate conflicts before placing actors)
officecli get deck.pptx '/slide[N]' --depth 1 --jsonConfirm the actor's target position does not overlap any content shape's bounding box (x to x+width, y to y+height).
Essential rules:
- Naming: Scene actors use
!!prefix, content uses#sN-prefix (best practice for verification and readability) - Transition: Every slide after the first MUST have
transition=morph(without this, no animation!) - Ghosting: Before adding new slide content, ghost ALL previous content shapes to
x=36cm(don't delete) - Motion: Adjust scene actor (
!!-*) positions between slides for animation - Variety: Create spatial variety between adjacent slides
- Text Width: Use generous widths to prevent text wrapping:
- Centered titles (64-72pt): 28-30cm width
- Centered subtitles (28-40pt): 25-28cm width
- Left-aligned titles: 20-25cm width
- Body text: 8-12cm (single-column), 16-18cm (double-column)
- When in doubt, make it wider! See
reference/pptx-design.mdfor details - Text size rule — 16pt minimum scope:
The 16pt minimum applies to ALL text that conveys primary content. Exceptions allowed for: chart axis labels (≤12pt OK), section eyebrow/kicker labels (≤14pt OK if ≤5 words), decoration shapes with no narrative content. Each exception must be intentional — descriptive body text at 13pt is NOT exempt.
Choreography — timing and motion principles:
Understanding how morph animates multiple shapes helps you plan intentional motion:
| Animation type | How to achieve it |
|---|---|
| Simple move | Same shape on slide A and B, same size, different x/y — morph interpolates position |
| Scale transform | Same shape on slide A and B, different width/height — morph interpolates size and position |
| Move + scale | Different x, y, width, height simultaneously — morph handles all dimensions at once |
| Color shift | Same shape, different fill color — morph cross-fades the fill |
| Enter (fade in) | Shape exists only on slide B (no counterpart on slide A) — morph fades it in |
| Exit (fade out) | Shape only on slide A (no counterpart on slide B) — morph fades it out |
Multi-shape timing rule:
- All
!!shapes in the same morph pair animate simultaneously — there is no way to stagger their start times within a single pair - If you need shape A to move before shape B, you MUST split the transition into two morph pairs (i.e., add an intermediate slide between them)
Staggered timing pattern (two shapes, offset timing):
Slide 2 → Slide 3: !!actor-A moves (!!actor-B stays put)
Slide 3 → Slide 4: !!actor-B moves (!!actor-A stays put or has already exited)This requires slide 3 as an explicit intermediate keyframe — never try to fake staggering within a single morph pair.
Known CLI behaviors:
- `!!` prefix auto-added after `transition=morph`: After running
set --prop transition=morph
on a slide, the CLI automatically prepends !! to all shape names on that slide (e.g., #s1-title → !!#s1-title). This is expected behavior. morph-helpers.py handles this correctly — its verification logic uses substring matching and is not affected.
⚠️ CRITICAL: Name-based path selectors break after `transition=morph` is set
After callingofficecli set '/slide[N]' --prop transition=morph, paths like/slide[N]/!!my-shapereturn 'Element not found'. The CLI auto-prepends!!to shape names when morph is applied, which invalidates name-based lookups.
>
Workaround: Always use shape INDEX paths instead of name paths when accessing shapes on morph slides:
>
```bash
# WRONG (after transition=morph set):
officecli get deck.pptx '/slide[3]/!!my-circle' --depth 1
>
# CORRECT:
officecli get deck.pptx '/slide[3]' --depth 1 # first list all shapes to find index
officecli get deck.pptx '/slide[3]/shape[2]' --depth 1
```
>
The build.py template should use inspect() + index-based access throughout.>
Pattern recommendation: Pre-plan all shape indices in a comment block at the top of your build script before setting morph. This prevents index tracking errors as the slide's shape count grows.
- Shape index tracking: After each batch of shape additions, run
officecli get deck.pptx '/slide[N]' --depth 1 to confirm the current slide's shape list and indices. This prevents off-by-one errors when manually computing index values for subsequent ghost/set operations.
Design resources:
reference/pptx-design.md— Design principles (Canvas, Fonts, Colors, Scene Actors, Page Types, Style References)reference/officecli-pptx-min.md— Command syntaxreference/styles/<name>/— Visual style examples (optional inspiration, browse by use case instyles/INDEX.md)
---
Phase 4: Visual Verification + Deliver
Phase 4 视觉验证(REQUIRED — final-check 通过后不可跳过)
4A. morph_final_check.py(CLI 数量验证)
If you used morph-helpers.py, the build script calls helper("verify", ...) and helper("final-check", ...) automatically. Also validate the final structure:
officecli validate <file>.pptx
officecli view <file>.pptx outline4B. 截图目视验证(必须执行)
final-check 通过不等于视觉正确。 morph_final_check 只验证 #sN- 前缀 shapes 的 ghost 状态(x=36cm 检查),它无法检测:
!!shapes 在场景切换后仍停留在可视区域(x < 33.87cm)——这类问题会通过 final-check 但产生视觉叠加- 相邻幻灯片间 scene actor 位置/尺寸未发生变化(动画静止)
必须对每张 slide 截图验证:
# 方案1: officecli view(pptx 有 SVG 预览)
officecli view deck.pptx svg --output-dir screenshots/
# 方案2: LibreOffice PDF → Chrome PNG(更准确)
libreoffice --headless --convert-to pdf deck.pptx
# 然后用 Chrome DevTools MCP 截图每页逐 slide 检查清单:
- [ ] 每张 slide 中,前一节的
!!content shapes 均不可见(x >= 33.87cm 已移出视野) - [ ] 每个场景切换的第一张 slide(新章节起始):前一节所有
!!shapes 已 ghost - [ ] 最后一个场景的收尾 slide:整洁,无残留前场景内容
- [ ] 装饰性
!!shapes(背景圆、角标等)在正确位置
If verification fails, see Troubleshooting section below.
---
Outputs (3 files):
1. <topic>.pptx 2. Build script (complete, re-runnable — bash/python/powershell/etc.) 3. brief.md — MUST be a standalone file (not embedded inside test-report.md or any other file). Content: slide-by-slide plan, content per slide, morph design decisions, ghost strategy per transition.
Final delivery message requirements:
- Tell the user the deck with polished Morph animations is ready.
- Explicitly recommend opening the generated PPT now to preview the motion effects.
- Use affirmative wording (e.g., "ready now", "open it now to preview the animation quality").
---
Troubleshooting
If `morph_verify_slide` or `morph_final_check` reports issues:
1. Missing transition:
# Check which slides are missing transition
officecli get <file>.pptx '/slide[2]' --json | grep transition
officecli get <file>.pptx '/slide[3]' --json | grep transition
# Expected: "transition": "morph"
# Fix:
officecli set <file>.pptx '/slide[2]' --prop transition=morph2. Unghosted content:
# Find unghosted shapes manually
import subprocess
for slide in range(2, 7):
print(f"Slide {slide}:")
subprocess.run(["officecli", "get", "<file>.pptx", f"/slide[{slide}]", "--depth", "1"])
# If you see shapes like "#s1-title" on slide 2 (not at x=36cm), they should be ghosted
# Fix (run in terminal):
# officecli set <file>.pptx /slide[N]/shape[X] --prop x=36cm3. Visual issues:
# Open HTML preview to debug layout
officecli view <file>.pptx htmlNote: !!scene-* shapes (decoration/background actors) should appear on all slides — that's normal and expected. However, !!actor-* shapes (content actors) MUST be ghosted at section boundaries to prevent ghost accumulation. Only #sN- prefix shapes are checked by morph_final_check; !!actor-* shapes require screenshot verification to confirm they are off-screen after their section ends.
---
Phase 5: Iterate
Ask user for feedback, support quick adjustments.
---
References
reference/decision-rules.md— Planning logic, Pyramid Principlereference/pptx-design.md— Design principles (Canvas, Fonts, Colors, Scene Actors, Page Types)reference/officecli-pptx-min.md— Tool syntaxreference/styles/INDEX.md— Visual style examples organized by use case
---
Adjustments After Creation
When the user requests changes after the deck is built:
| Request | Command |
|---|---|
| Swap two slides | officecli swap deck.pptx '/slide[2]' '/slide[4]' |
| Move a slide after another | officecli move deck.pptx '/slide[5]' --after '/slide[2]' |
| Edit shape text | officecli set deck.pptx '/slide[N]/shape[@name=!! ShapeName]' --prop text="..." |
| Change color / style | officecli set deck.pptx '/slide[N]/shape[@name=!! ShapeName]' --prop fill=FF0000 |
| Remove an element | officecli remove deck.pptx '/slide[N]/shape[@name=!! ShapeName]' |
| Find & replace text | officecli set deck.pptx / --prop find=OldText --prop replace=NewText |
Morph caution: Morph transitions rely on matching!!-prefixed shape names across consecutive slides. After swapping or moving slides, verify that morph pairs (same!!name on adjacent slides) are still correctly aligned. Useofficecli get deck.pptx '/slide[N]' --depth 1to check shape names.
---
First time? Read "Understanding Morph" above, skim one style reference for inspiration, then generate. Always use morph-helpers.py workflow. You'll learn by doing.
Trust yourself. You have vision, design sense, and the ability to iterate. These tools enable you — your creativity makes it excellent.
PPT Planner
Role: Think deeply about the user's topic and produce a high-quality PPT plan.
Output: A single brief.md containing extraction summary, outline, and detailed page briefs.
---
Infer Audience
Thinking Method: Based on topic keywords and usage context, ask "Who will view this PPT? What do they care about most?"
Common Patterns (examples, not exhaustive):
- Fundraising / Roadshow → Investors
- Teaching / Training → Students
- Product Introduction → Clients
- Analysis / Report → Executives
- Internal Sharing → Colleagues
- Cannot determine → General Business
---
Infer Purpose
Thinking Method: Based on topic keywords, ask "What outcome does the user want to achieve with this PPT?"
Common Patterns (examples, not exhaustive):
- Fundraising / Roadshow → Persuade Investment
- Product Introduction → Demonstrate Value
- Analysis / Report → Deliver Insights
- Training / Teaching → Impart Knowledge
- Cannot determine → Present Information
---
Infer Narrative Structure
Thinking Method: Choose an appropriate narrative thread based on the purpose.
Common Structures (examples, not exhaustive):
| Applicable Scenario | Narrative Structure | Page Sequence Example |
|---|---|---|
| Fundraising / Sales / Bidding | problem_solution | hero → statement → pillars → evidence → cta |
| Reporting / Analysis | insight_driven | hero → statement → evidence → pillars → cta |
| Promotion / Speech | vision_driven | hero → quote → pillars → evidence → cta |
| Teaching / Training | educational | hero → statement → pillars → pillars → showcase → cta |
Free Combination: Feel free to adapt based on the specific content.
---
Outline Construction
Thinking Method: Pyramid Principle
1. Conclusion First: Each slide starts with a core argument, not a list of information 2. Top-Down Structure: Deck conclusion → Slide-level arguments → Supporting points 3. Group by Category: Points on the same slide belong to the same logical category 4. Logical Progression: Organize by time / importance / causality / parallelism
6-Step Thinking Process
1. What is the one-sentence conclusion of this deck? 2. How many supporting arguments are needed? 3. What is the core argument of each slide? 4. What evidence / data / case studies support each slide? 5. Which slides are essential? Which are "nice to have"? 6. Where is the audience most likely to push back?
Page Count Guidelines (reference only)
- Quick intro / single topic: 3–5 slides
- Standard presentation: 5–8 slides
- Deep analysis / annual report: 10–15 slides
---
brief.md Output Format
Write everything into a single brief.md with three sections:
Section 1: Summary
Topic: ...
Audience: ... [provided / inferred]
Purpose: ... [provided / inferred]
Narrative: ...
Style direction: ... [provided / inferred based on topic + mood, not habit]Style selection principles:
1. Match topic mood → Corporate ≠ playful, tech ≠ organic (unless intentionally contrasting) 2. Vary by project → Browse reference/styles/ directory, avoid repeating recent styles 3. Consider 6 categories → dark (16), light (10), warm (11), bw (5), vivid (6), mixed (7) 4. Prefer unexpected but fitting → Don't default to "dark + neon" for all tech topics 5. Name specific style → "warm--earth-organic palette" not "warm tones"
Section 2: Outline
Overall conclusion: AI Agent Platform lets every enterprise have its own AI workforce
---
S1: [hero] "AI Agent Platform — Let agents work for you"
S2: [statement] "From automation to autonomy: why agents are needed now"
S3: [pillars] "Three core capabilities: Perceive / Reason / Execute" ★key slide
S4: [evidence] "10M+ API Calls / 99.95% Uptime / 50ms P95"
S5: [cta] "Start building your agent"Section 3: Page Briefs
For each slide, answer 6 questions:
S3 [pillars] ★key slide
├── Objective: Help the audience understand the three differentiated capabilities
├── Core information (detailed):
│ ① Perception: Supports text, image, voice, video multimodal input, 95%+ accuracy
│ ② Reasoning: Chain-of-Thought technology, 40% improvement on complex tasks
│ ③ Execution: Auto-calls 20+ tools and APIs, end-to-end task completion
├── Evidence: Specific metrics for each capability
├── Page type: pillars (multi-column)
├── Hierarchy: Number ① largest → capability name next → description smallest
└── Transition: S2 asks "why needed" → S3 answers "how it works"Critical: Core information must be detailed and complete (titles, descriptions, data, cases). Do NOT write abbreviated bullet points like "multimodal understanding". The Design Expert will use this content directly.
---
Fallback Strategy
| Failure Scenario | Fallback Strategy |
|---|---|
| Cannot infer audience | General Business |
| Cannot infer purpose | Present Information |
| Cannot determine page count | Decide based on content volume; avoid <3 or >20 |
---
#!/usr/bin/env python3
"""
Morph PPT Helper Functions
Cross-platform replacement for morph-helpers.sh (Mac / Windows / Linux)
Usage (CLI):
python morph-helpers.py clone <deck> <from_slide> <to_slide>
python morph-helpers.py ghost <deck> <slide> <idx> [idx ...]
python morph-helpers.py verify <deck> <slide>
python morph-helpers.py final-check <deck>
Usage (import):
from morph_helpers import morph_clone_slide, morph_ghost_content, morph_verify_slide, morph_final_check
"""
import sys
import json
import subprocess
import argparse
import re
# Cross-platform color support (colorama optional)
try:
from colorama import init, Fore, Style
init(autoreset=True)
GREEN = Fore.GREEN
RED = Fore.RED
YELLOW = Fore.YELLOW
BLUE = Fore.CYAN
NC = Style.RESET_ALL
except ImportError:
GREEN = RED = YELLOW = BLUE = NC = ""
# ---------------------------------------------------------------------------
# Internal helpers
# ---------------------------------------------------------------------------
def _run(*args):
"""Run a command, return (returncode, stdout, stderr)."""
result = subprocess.run(list(args), capture_output=True, text=True)
return result.returncode, result.stdout, result.stderr
def _find_nested(data, key):
"""Recursively search a nested dict for a key, return its value or None."""
if isinstance(data, dict):
if key in data:
return data[key]
for v in data.values():
found = _find_nested(v, key)
if found is not None:
return found
return None
def _has_morph_transition(json_str):
"""Check whether JSON output from officecli contains transition=morph."""
if '"transition": "morph"' in json_str:
return True
try:
data = json.loads(json_str)
return _find_nested(data, "transition") == "morph"
except Exception:
return False
def _collect_shapes(children, callback):
"""Walk a shape tree depth-first, calling callback(child) for each node."""
for child in children:
callback(child)
if "Children" in child:
_collect_shapes(child["Children"], callback)
# ---------------------------------------------------------------------------
# morph_clone_slide
# ---------------------------------------------------------------------------
def morph_clone_slide(deck, from_slide, to_slide):
"""Clone slide and automatically set transition=morph, then verify.
Args:
deck: path to .pptx file
from_slide: source slide number (1-based)
to_slide: destination slide number (1-based)
"""
from_slide, to_slide = int(from_slide), int(to_slide)
print(f"{BLUE}Cloning slide {from_slide} -> {to_slide}...{NC}")
_run("officecli", "add", deck, "/", "--from", f"/slide[{from_slide}]")
print(f"{BLUE}Setting morph transition...{NC}")
_run("officecli", "set", deck, f"/slide[{to_slide}]", "--prop", "transition=morph")
print(f"{BLUE}Listing shapes for ghosting reference:{NC}")
rc, out, _ = _run("officecli", "get", deck, f"/slide[{to_slide}]", "--depth", "1")
print(out)
# Verify
print(f"{BLUE}Verifying transition...{NC}")
rc, out, _ = _run("officecli", "get", deck, f"/slide[{to_slide}]", "--json")
if not _has_morph_transition(out):
print(f"{RED}ERROR: Transition not set on slide {to_slide}!{NC}")
print(f"{RED} This slide will not have morph animation.{NC}")
sys.exit(1)
print(f"{GREEN}Transition verified on slide {to_slide}{NC}")
print()
# ---------------------------------------------------------------------------
# morph_ghost_content
# ---------------------------------------------------------------------------
def morph_ghost_content(deck, slide, *shapes):
"""Move shapes off-screen (x=36cm) to ghost them for morph animation.
Args:
deck: path to .pptx file
slide: slide number (1-based)
*shapes: one or more shape indices to ghost
"""
slide = int(slide)
shapes = [int(s) for s in shapes]
if not shapes:
print(f"{YELLOW}No shapes to ghost{NC}")
return
print(f"{BLUE}Ghosting {len(shapes)} content shape(s) on slide {slide}...{NC}")
for idx in shapes:
rc, _, _ = _run("officecli", "set", deck, f"/slide[{slide}]/shape[{idx}]", "--prop", "x=36cm")
if rc == 0:
print(f"{GREEN} Ghosted shape[{idx}]{NC}")
else:
print(f"{RED} Failed to ghost shape[{idx}]{NC}")
print(f"{GREEN}Ghosting complete{NC}")
print()
# ---------------------------------------------------------------------------
# morph_verify_slide
# ---------------------------------------------------------------------------
def _check_unghosted(data, prev_slide):
"""Return list of shapes with #s{prev_slide}- prefix not yet ghosted."""
unghosted = []
def visit(child):
name = child.get("Format", {}).get("name", "")
x = child.get("Format", {}).get("x", "")
path = child.get("Path", "")
if f"#s{prev_slide}-" in name and x != "36cm":
unghosted.append(f"{path}: name={name}, x={x}")
if "Children" in data:
_collect_shapes(data["Children"], visit)
return unghosted
def _check_duplicates(prev_data, curr_data):
"""Return list of shapes with identical text+position on adjacent slides (excluding ghost zone)."""
SCENE_KEYWORDS = ["ring", "dot", "line", "circle", "rect", "slash",
"accent", "actor", "star", "triangle", "diamond"]
def extract(data):
boxes = []
def visit(child):
if child.get("Type") != "textbox":
return
name = child.get("Format", {}).get("name", "")
text = child.get("Text", "").strip()
x = child.get("Format", {}).get("x", "")
y = child.get("Format", {}).get("y", "")
path = child.get("Path", "")
if not text or len(text) < 6:
return
clean = name.replace("!!", "")
is_scene = any(kw in clean.lower() for kw in SCENE_KEYWORDS)
has_slide_pattern = any(f"s{i}-" in clean for i in range(1, 20))
if has_slide_pattern or not is_scene:
boxes.append({"path": path, "text": text[:50], "x": x, "y": y})
if "Children" in data:
_collect_shapes(data["Children"], visit)
return boxes
prev_boxes = extract(prev_data)
curr_boxes = extract(curr_data)
duplicates = []
for curr in curr_boxes:
for prev in prev_boxes:
if (curr["text"] == prev["text"]
and curr["x"] == prev["x"]
and curr["y"] == prev["y"]
and curr["x"] != "36cm"):
duplicates.append(
f"{curr['path']}: text='{curr['text']}...', pos=({curr['x']},{curr['y']})"
)
break
return duplicates
def morph_verify_slide(deck, slide):
"""Verify a slide has correct morph setup (transition + ghosting).
Uses two detection methods:
1. Name-based: shapes with #s{prev}- prefix must be at x=36cm
2. Duplicate text: same text+position on adjacent slides (catches missing # prefix)
Args:
deck: path to .pptx file
slide: slide number (1-based)
Returns:
True if all checks pass, False otherwise.
"""
slide = int(slide)
print(f"{BLUE}Verifying slide {slide}...{NC}")
has_error = False
# --- Check transition ---
rc, out, _ = _run("officecli", "get", deck, f"/slide[{slide}]", "--json")
curr_json_str = out
if not _has_morph_transition(curr_json_str):
print(f"{RED} Missing transition=morph{NC}")
print(f"{RED} Without this, slide will not animate!{NC}")
has_error = True
else:
print(f"{GREEN} Transition OK{NC}")
# --- Checks against previous slide ---
prev_slide = slide - 1
if prev_slide >= 1:
try:
curr_data = json.loads(curr_json_str).get("data", {})
# Method 1: name-based unghosted detection
unghosted = _check_unghosted(curr_data, prev_slide)
if unghosted:
print(f"{YELLOW} Warning: Found unghosted content from slide {prev_slide}:{NC}")
for item in unghosted:
print(f" {item}")
print(f"{YELLOW} These shapes should be ghosted to x=36cm{NC}")
has_error = True
else:
print(f"{GREEN} No unghosted content detected{NC}")
except Exception:
print(f"{GREEN} No unghosted content detected{NC}")
# Method 2: duplicate text/position detection (backup for missing # prefix)
try:
rc2, out2, _ = _run("officecli", "get", deck, f"/slide[{prev_slide}]", "--json")
prev_data = json.loads(out2).get("data", {})
curr_data = json.loads(curr_json_str).get("data", {})
duplicates = _check_duplicates(prev_data, curr_data)
if duplicates:
print(f"{YELLOW} Warning: Found duplicate content from slide {prev_slide} (same text at same position):{NC}")
for dup in duplicates:
print(f" {dup}")
print(f"{YELLOW} This might indicate:{NC}")
print(f"{YELLOW} 1. Content shapes missing '#sN-' prefix (can't detect for ghosting){NC}")
print(f"{YELLOW} 2. Forgot to ghost previous slide's content{NC}")
print(f"{YELLOW} 3. Forgot to add new content for this slide{NC}")
has_error = True
except Exception:
pass
if not has_error:
print(f"{GREEN}Slide {slide} verification passed{NC}")
else:
print(f"{RED}Slide {slide} has issues - see above{NC}")
print()
return not has_error
# ---------------------------------------------------------------------------
# morph_final_check
# ---------------------------------------------------------------------------
def morph_final_check(deck):
"""Verify the entire deck: all slides (2+) must pass morph_verify_slide.
Args:
deck: path to .pptx file
Returns:
True if all slides pass, False otherwise.
"""
print(f"{BLUE}Final deck verification...{NC}")
print()
rc, out, _ = _run("officecli", "view", deck, "outline")
total_slides = 0
first_line = out.split("\n")[0] if out else ""
match = re.search(r"(\d+)\s+slides", first_line)
if match:
total_slides = int(match.group(1))
if total_slides == 0:
print(f"{RED}No slides found in deck{NC}")
return False
print(f"Total slides: {total_slides}")
print()
error_count = 0
for i in range(2, total_slides + 1):
if not morph_verify_slide(deck, i):
error_count += 1
print("=========================================")
if error_count == 0:
print(f"{GREEN}All slides verified successfully!{NC}")
print(f"{GREEN} Your morph animations should work correctly.{NC}")
return True
else:
print(f"{RED}Found issues in {error_count} slide(s){NC}")
print(f"{RED} Please fix the issues above before delivering.{NC}")
return False
# ---------------------------------------------------------------------------
# CLI entry point
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
prog="morph-helpers.py",
description="Morph PPT Helper Functions — cross-platform (Mac / Windows / Linux)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
commands:
clone <deck> <from_slide> <to_slide> Clone slide and set morph transition
ghost <deck> <slide> <idx> [idx ...] Ghost multiple shapes off-screen (x=36cm)
verify <deck> <slide> Verify slide setup (transition + ghosting)
final-check <deck> Verify entire deck
example:
python morph-helpers.py clone deck.pptx 1 2
python morph-helpers.py ghost deck.pptx 2 7 8 9
python morph-helpers.py verify deck.pptx 2
python morph-helpers.py final-check deck.pptx
""",
)
sub = parser.add_subparsers(dest="command")
p = sub.add_parser("clone")
p.add_argument("deck")
p.add_argument("from_slide", type=int)
p.add_argument("to_slide", type=int)
p = sub.add_parser("ghost")
p.add_argument("deck")
p.add_argument("slide", type=int)
p.add_argument("shapes", nargs="+", type=int)
p = sub.add_parser("verify")
p.add_argument("deck")
p.add_argument("slide", type=int)
p = sub.add_parser("final-check")
p.add_argument("deck")
args = parser.parse_args()
if args.command == "clone":
morph_clone_slide(args.deck, args.from_slide, args.to_slide)
elif args.command == "ghost":
morph_ghost_content(args.deck, args.slide, *args.shapes)
elif args.command == "verify":
if not morph_verify_slide(args.deck, args.slide):
sys.exit(1)
elif args.command == "final-check":
if not morph_final_check(args.deck):
sys.exit(1)
else:
parser.print_help()
if __name__ == "__main__":
main()
#!/bin/bash
# Morph PPT Helper Functions
# Purpose: Simplify morph workflow by bundling common operations with built-in verification
# Colors for output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# ============================================
# morph_clone_slide: Clone slide and set transition
# ============================================
# Usage: morph_clone_slide <deck.pptx> <from_slide_num> <to_slide_num>
# Example: morph_clone_slide deck.pptx 1 2
#
# What it does:
# 1. Clone the source slide
# 2. Automatically set transition=morph
# 3. List all shapes for ghosting reference
# 4. Verify transition was set correctly
morph_clone_slide() {
local deck=$1
local from_slide=$2
local to_slide=$3
echo -e "${BLUE}📋 Cloning slide $from_slide → $to_slide...${NC}"
officecli add "$deck" '/' --from "/slide[$from_slide]"
echo -e "${BLUE}⚡ Setting morph transition...${NC}"
officecli set "$deck" "/slide[$to_slide]" --prop transition=morph
echo -e "${BLUE}📊 Listing shapes for ghosting reference:${NC}"
officecli get "$deck" "/slide[$to_slide]" --depth 1
# Verify transition was set
echo -e "${BLUE}🔍 Verifying transition...${NC}"
local trans=$(officecli get "$deck" "/slide[$to_slide]" --json 2>/dev/null | grep '"transition": "morph"')
if [ -z "$trans" ]; then
echo -e "${RED}❌ ERROR: Transition not set on slide $to_slide!${NC}"
echo -e "${RED} This slide will not have morph animation.${NC}"
exit 1
else
echo -e "${GREEN}✅ Transition verified on slide $to_slide${NC}"
fi
echo ""
}
# ============================================
# morph_ghost_content: Ghost multiple shapes at once
# ============================================
# Usage: morph_ghost_content <deck.pptx> <slide_num> <shape_idx1> [shape_idx2] [shape_idx3] ...
# Example: morph_ghost_content deck.pptx 2 7 8 9
#
# What it does:
# 1. Move specified shapes to x=36cm (off-screen)
# 2. Show progress for each shape
# 3. Verify all shapes were ghosted
morph_ghost_content() {
local deck=$1
local slide=$2
shift 2
local shapes=("$@")
if [ ${#shapes[@]} -eq 0 ]; then
echo -e "${YELLOW}⚠️ No shapes to ghost${NC}"
return 0
fi
echo -e "${BLUE}👻 Ghosting ${#shapes[@]} content shape(s) on slide $slide...${NC}"
for shape_idx in "${shapes[@]}"; do
officecli set "$deck" "/slide[$slide]/shape[$shape_idx]" --prop x=36cm 2>/dev/null
if [ $? -eq 0 ]; then
echo -e "${GREEN} ✓ Ghosted shape[$shape_idx]${NC}"
else
echo -e "${RED} ✗ Failed to ghost shape[$shape_idx]${NC}"
fi
done
echo -e "${GREEN}✅ Ghosting complete${NC}"
echo ""
}
# ============================================
# morph_verify_slide: Verify slide has correct setup
# ============================================
# Usage: morph_verify_slide <deck.pptx> <slide_num>
# Example: morph_verify_slide deck.pptx 2
#
# What it does:
# 1. Check if transition=morph is set
# 2. Check for unghosted content from previous slide (by '#sN-' prefix)
# 3. Check for duplicate content (same text at same position) - BACKUP DETECTION
# 4. Report any issues found
#
# TWO DETECTION METHODS:
#
# Method 1: Name-based detection (Primary)
# - Checks if shapes with '#sN-' prefix are ghosted
# - REQUIRES correct naming: '#s1-title', '#s2-card', etc.
# - Fast and accurate when naming is correct
#
# Method 2: Duplicate detection (Backup insurance)
# - Checks if adjacent slides have identical text at identical positions
# - Works even if naming is wrong (e.g., 's1-title' instead of '#s1-title')
# - Catches cases where content wasn't ghosted OR naming is incorrect
# - Ignores ghost zone (x=36cm) duplicates (those are expected)
#
# WHY TWO METHODS?
# If agents forget '#' prefix, Method 1 fails but Method 2 still catches the problem!
morph_verify_slide() {
local deck=$1
local slide=$2
echo -e "${BLUE}🔍 Verifying slide $slide...${NC}"
local has_error=0
# Check transition
local trans=$(officecli get "$deck" "/slide[$slide]" --json 2>/dev/null | grep '"transition": "morph"')
if [ -z "$trans" ]; then
echo -e "${RED} ❌ Missing transition=morph${NC}"
echo -e "${RED} Without this, slide will not animate!${NC}"
has_error=1
else
echo -e "${GREEN} ✅ Transition OK${NC}"
fi
# Check for unghosted content from previous slide
local prev_slide=$((slide - 1))
if [ $prev_slide -ge 1 ]; then
# Use JSON output for reliable parsing
local shapes_json=$(officecli get "$deck" "/slide[$slide]" --json 2>/dev/null)
# Use python to parse JSON and find unghosted content
local unghosted_check
unghosted_check=$(printf '%s' "$shapes_json" | python3 -c "
import sys, json
try:
data = json.load(sys.stdin)
def check_children(children, prev_slide):
unghosted = []
for child in children:
name = child.get('Format', {}).get('name', '')
x = child.get('Format', {}).get('x', '')
path = child.get('Path', '')
# Check if this shape has previous slide's content prefix
if f'#s{prev_slide}-' in name:
# Check if it's NOT ghosted (x != 36cm)
if x != '36cm':
unghosted.append(f\"{path}: name={name}, x={x}\")
# Recursively check children
if 'Children' in child:
unghosted.extend(check_children(child['Children'], prev_slide))
return unghosted
if 'Children' in data.get('data', {}):
unghosted = check_children(data['data']['Children'], $prev_slide)
if unghosted:
for item in unghosted:
print(item)
sys.exit(1)
sys.exit(0)
except Exception:
sys.exit(0)
" 2>/dev/null)
local python_exit=$?
if [ $python_exit -eq 1 ] && [ -n "$unghosted_check" ]; then
echo -e "${YELLOW} ⚠️ Warning: Found unghosted content from slide $prev_slide:${NC}"
echo "$unghosted_check" | sed 's/^/ /'
echo -e "${YELLOW} These shapes should be ghosted to x=36cm${NC}"
has_error=1
else
echo -e "${GREEN} ✅ No unghosted content detected${NC}"
fi
fi
# Additional check: Detect duplicate content between adjacent slides
# (Catches cases where content shapes are missing #sN- prefix)
if [ $prev_slide -ge 1 ]; then
local prev_json=$(officecli get "$deck" "/slide[$prev_slide]" --json 2>/dev/null)
local curr_json="$shapes_json"
local duplicates
duplicates=$(python3 -c "
import sys, json
try:
prev_data = json.loads('''$prev_json''')
curr_data = json.loads('''$curr_json''')
def extract_textboxes(data, slide_num):
boxes = []
def walk(children):
for child in children:
if child.get('Type') == 'textbox':
name = child.get('Format', {}).get('name', '')
text = child.get('Text', '').strip()
x = child.get('Format', {}).get('x', '')
y = child.get('Format', {}).get('y', '')
path = child.get('Path', '')
# Skip empty text and very short text
if not text or len(text) < 6:
continue
# Clean name (remove !! prefix if present)
clean_name = name.replace('!!', '') if name else ''
# Skip pure scene actors (common keywords)
scene_keywords = ['ring', 'dot', 'line', 'circle', 'rect', 'slash',
'accent', 'actor', 'star', 'triangle', 'diamond']
is_scene = any(kw in clean_name.lower() for kw in scene_keywords)
# Include if:
# 1. Name contains 'sN-' pattern (likely content even if missing #)
# 2. Not a pure scene actor
has_slide_pattern = any(f's{i}-' in clean_name for i in range(1, 20))
if has_slide_pattern or not is_scene:
boxes.append({
'path': path,
'name': name,
'text': text[:50], # First 50 chars
'x': x,
'y': y
})
if 'Children' in child:
walk(child['Children'])
if 'Children' in data.get('data', {}):
walk(data['data']['Children'])
return boxes
prev_boxes = extract_textboxes(prev_data, $prev_slide)
curr_boxes = extract_textboxes(curr_data, $slide)
duplicates = []
for curr in curr_boxes:
for prev in prev_boxes:
# Check if text and position are identical
if (curr['text'] == prev['text'] and
curr['x'] == prev['x'] and
curr['y'] == prev['y']):
# Skip if both are already in ghost position (x=36cm)
# (It's normal for ghosted content to be at same position)
if curr['x'] != '36cm':
duplicates.append(f\"{curr['path']}: text='{curr['text']}...', pos=({curr['x']},{curr['y']})\")
break
if duplicates:
for dup in duplicates:
print(dup)
sys.exit(1)
sys.exit(0)
except Exception:
sys.exit(0)
" 2>/dev/null)
local dup_exit=$?
if [ $dup_exit -eq 1 ] && [ -n "$duplicates" ]; then
echo -e "${YELLOW} ⚠️ Warning: Found duplicate content from slide $prev_slide (same text at same position):${NC}"
echo "$duplicates" | sed 's/^/ /'
echo -e "${YELLOW} This might indicate:${NC}"
echo -e "${YELLOW} 1. Content shapes missing '#sN-' prefix (can't detect for ghosting)${NC}"
echo -e "${YELLOW} 2. Forgot to ghost previous slide's content${NC}"
echo -e "${YELLOW} 3. Forgot to add new content for this slide${NC}"
has_error=1
fi
fi
if [ $has_error -eq 0 ]; then
echo -e "${GREEN}✅ Slide $slide verification passed${NC}"
else
echo -e "${RED}❌ Slide $slide has issues - see above${NC}"
return 1
fi
echo ""
}
# ============================================
# morph_final_check: Verify entire deck
# ============================================
# Usage: morph_final_check <deck.pptx>
# Example: morph_final_check deck.pptx
#
# What it does:
# 1. Check all slides (2+) have transition=morph
# 2. Summary report of any issues
morph_final_check() {
local deck=$1
echo -e "${BLUE}🎯 Final deck verification...${NC}"
echo ""
# Get total slides
local total_slides=$(officecli view "$deck" outline 2>/dev/null | head -1 | grep -oE '[0-9]+' | head -1 || echo "0")
if [ "$total_slides" -eq 0 ]; then
echo -e "${RED}❌ No slides found in deck${NC}"
return 1
fi
echo "Total slides: $total_slides"
echo ""
local error_count=0
# Check each slide starting from slide 2
for ((i=2; i<=total_slides; i++)); do
if ! morph_verify_slide "$deck" "$i"; then
((error_count++))
fi
done
echo ""
echo "========================================="
if [ $error_count -eq 0 ]; then
echo -e "${GREEN}✅ All slides verified successfully!${NC}"
echo -e "${GREEN} Your morph animations should work correctly.${NC}"
return 0
else
echo -e "${RED}❌ Found issues in $error_count slide(s)${NC}"
echo -e "${RED} Please fix the issues above before delivering.${NC}"
return 1
fi
}
# Show usage if called directly
if [ "${BASH_SOURCE[0]}" == "${0}" ]; then
echo "Morph PPT Helper Functions"
echo ""
echo "Usage: source morph-helpers.sh"
echo ""
echo "Available functions:"
echo " morph_clone_slide <deck> <from> <to> - Clone slide and set transition"
echo " morph_ghost_content <deck> <slide> <idx...> - Ghost multiple shapes"
echo " morph_verify_slide <deck> <slide> - Verify slide setup"
echo " morph_final_check <deck> - Verify entire deck"
echo ""
echo "Example:"
echo " source morph-helpers.sh"
echo " morph_clone_slide deck.pptx 1 2"
echo " morph_ghost_content deck.pptx 2 7 8"
echo " morph_verify_slide deck.pptx 2"
fi
OfficeCLI PPT Command Reference
0) BEFORE YOU START (CRITICAL)
If `officecli` is not installed:
macOS / Linux
if ! command -v officecli >/dev/null 2>&1; then
curl -fsSL https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/main/install.sh | bash
fiWindows (PowerShell)
if (-not (Get-Command officecli -ErrorAction SilentlyContinue)) {
irm https://raw.githubusercontent.com/iOfficeAI/OfficeCLI/main/install.ps1 | iex
}Verify: officecli --version
If officecli is still not found after first install, open a new terminal and run the verify command again.
---
1) Learn Commands from CLI (NOT from this doc)
DO NOT memorize property lists from this document. They will become outdated. Instead, query the CLI for real-time syntax:
Three-Layer Help System
# Layer 1: See all settable elements
officecli pptx set
# Layer 2: See all properties for ONE element
officecli pptx set shape # Shows: text, fill, x, y, width, height, font, size, bold...
officecli pptx set slide # Shows: background, transition, advanceTime...
officecli pptx set chart # Shows: chartType, categories, data, style...
# Layer 3: See format details for ONE property
officecli pptx set shape.fill # Color format examples
officecli pptx set shape.animation # Animation syntax details
officecli pptx set shape.gradient # Gradient syntax
officecli pptx set shape.shadow # Shadow formatWorkflow Example: Discover → Apply → Validate
# Step 1: What can I modify on shapes?
$ officecli pptx set shape
# Output: text, fill, x, y, width, height, font, size, bold, italic, gradient, shadow...
# Step 2: How do I use gradient?
$ officecli pptx set shape.gradient
# Output: gradient="linear:90:FF0000,0000FF" or "radial:FF0000,0000FF"
# Step 3: Apply it
officecli set demo.pptx '/slide[1]/shape[1]' --prop gradient="linear:90:FF0000,0000FF"
# Step 4: Validate
officecli validate demo.pptxFor comprehensive reference: https://github.com/iOfficeAI/OfficeCLI/wiki/agent-guide
---
2) Quick Start (10 Most Common Commands)
Only memorize these essentials. For anything else, use officecli pptx set/add:
# 1. Create document
officecli create deck.pptx
# 2. Add slide with background and transition
officecli add deck.pptx '/' --type slide --prop background=1A1A2E --prop transition=morph
# 3. Add shape/textbox
officecli add deck.pptx '/slide[1]' --type shape --prop text="Hello World" \
--prop x=5cm --prop y=8cm --prop width=10cm --prop height=3cm --prop fill=FF0000
# 4. Modify existing shape
officecli set deck.pptx '/slide[1]/shape[1]' --prop fill=0000FF --prop text="New text"
# 5. Clone slide (with all shapes)
officecli add deck.pptx '/' --from '/slide[1]'
# 6. View structure
officecli view deck.pptx outline
# 7. HTML preview (NEW in 1.0.14) - auto-opens in browser
officecli view deck.pptx html
# 8. Get element details (check indices before modifying)
officecli get deck.pptx '/slide[1]' --depth 1
# 9. Validate document
officecli validate deck.pptx
# 10. Performance mode (for 3+ commands on same file)
officecli open deck.pptx
officecli set deck.pptx '/slide[1]/shape[1]' --prop fill=FF0000
officecli set deck.pptx '/slide[1]/shape[2]' --prop text="Fast"
officecli close deck.pptxMore Element Types (when needed, check syntax with officecli pptx add)
officecli add deck.pptx '/slide[1]' --type picture --prop src=photo.jpg --prop width=12cm
officecli add deck.pptx '/slide[1]' --type chart --prop chartType=column --prop categories="Q1,Q2" --prop data="Sales:100,200"
officecli add deck.pptx '/slide[1]' --type table --prop rows=3 --prop cols=4
officecli add deck.pptx '/slide[1]' --type connector --prop preset=straight --prop line=FF0000
officecli add deck.pptx '/slide[1]' --type video --prop src=demo.mp4 --prop autoplay=true---
3) Critical Rules (Read Before Any Command)
Syntax Pitfalls (Top 4 Mistakes)
| ❌ Wrong | ✅ Correct | Why |
|---|---|---|
--name "foo" | --prop name="foo" | All attributes go through --prop |
'/slide[@name="foo"]' | '/slide[1]/shape[3]' | Numeric indexing only (1-based) |
x=-3cm | x=0cm or x=36cm | No negative coordinates. Use 36cm for off-screen |
| Add shapes before slide | Create → add slide → add shapes | Must add slide first or "Slide not found" |
Shape Index Management
Indices change dynamically. Always verify before modifying:
# Check current indices
officecli get deck.pptx '/slide[1]' --depth 1
# Output: shape[1] name="title", shape[2] name="subtitle", shape[3] name="logo"...Index behavior:
- After clone: New slide inherits all shapes with same indices as source
- After add: New shape gets next index (if slide has 8 shapes, new =
shape[9]) - After remove: Indices shift down (remove
shape[3]→ oldshape[4]becomesshape[3])
Morph Animation
transition=morph creates smooth animations by matching shapes by name across adjacent slides.
How it works:
1. Adjacent slides must have shapes with identical names for morphing 2. PowerPoint matches by name and animates position/size/color changes 3. If names don't match → only fade in/out (no morph effect)
Morph variants:
--prop transition=morph # Match by object (default)
--prop transition=morph-byWord # Word-by-word text animation
--prop transition=morph-byChar # Character-by-character text animationFor complete Morph workflow (naming conventions, ghosting, helpers), see SKILL.md Phase 3.
Script Best Practices
- Multi-line text: Use
\\n(double-escaped in bash) or split into multiple textboxes - Line continuation (bash): No trailing spaces after backslash
\ - Filenames: Use English names to avoid encoding issues
- Language choice: Use bash/python/powershell — whatever executes
officeclicommands clearly
Batch JSON Mode (NOT RECOMMENDED for Morph)
Do NOT use officecli batch --input commands.json for Morph presentations. Reason: Morph requires careful step-by-step control that's hard to debug in JSON.
If you must use batch mode for non-Morph tasks:
- Booleans as strings:
{"props":{"bold":"true"}}not{"bold":true} - Escape quotes:
{"props":{"text":"It\\'s working"}}
---
4) Troubleshooting
When a command fails:
1. Read the error message — officecli provides descriptive errors
- "Unrecognized argument" → check
--propformat - "Slide not found" → add slide first
- "Could not find path" → verify file exists
2. Inspect current state:
officecli get <file> '/slide[N]' --depth 1 # List all shapes + indices
officecli view <file> outline # Document structure
officecli view <file> issues # Validation issues3. Check command syntax:
officecli pptx set <element> # See available properties
officecli pptx add # See available element types4. Common fixes:
| Error | Solution |
|---|---|
| "Slide not found" | Run officecli add <file> '/' --type slide first |
| "Unrecognized argument" | Use --prop key=value format, not --key value |
| Shape not where expected | Run get to verify current indices after add/remove |
| File locked | Close PPT in PowerPoint/WPS before running commands |
5. Still stuck? Generate HTML preview to debug visually:
officecli view deck.pptx html # Opens in browser with live preview---
Remember: This doc is a quick reference. For the latest syntax, always query the CLI first with officecli pptx set/add.
Design Essentials
Philosophy: Create dynamic, beautiful presentations by designing layout and motion together from the start.
---
1) Canvas & Coordinates
- Canvas: 16:9 (33.87cm × 19.05cm)
- Safe Margins: left/right 1.2cm, top/bottom 0.8cm
- Spacing Tokens: 0.2, 0.4, 0.6, 0.8, 1.2, 1.6 cm (use these for consistency)
- Ghost Position:
x=36cm(off the right edge)
---
2) Fonts & Typography
Recommended Combinations
| Content Type | Primary Font | Fallback |
|---|---|---|
| English | Montserrat (title) + Inter (body) | Segoe UI / Helvetica Neue |
| Chinese | Source Han Sans (思源黑体) | PingFang SC / Microsoft YaHei |
| Mixed | Montserrat + Source Han Sans | Segoe UI + System Font |
Size Scale
- Title: 54-72pt, bold/black
- Heading: 28-40pt
- Body: 18-24pt
- Caption: 13-16pt (minimum 13pt)
Text Width Guidelines
CRITICAL: Always make text boxes wider than you think necessary. Wrapping breaks visual hierarchy.
| Content Type | Minimum Width | Best Practice |
|---|---|---|
| Centered titles (64-72pt) | 28cm | Use 28-30cm for 10-15 char titles, 25cm for hero statements |
| Centered subtitles (28-40pt) | 25cm | Always use 25-28cm to avoid mid-word breaks |
| Left-aligned titles | 20cm | Use 20-25cm depending on content length |
| Body text / cards | 8cm (single) | Single-column 8-12cm, double-column 16-18cm |
Common mistakes to avoid:
- ❌ Using 10-15cm for long centered subtitles → causes awkward line breaks
- ❌ Tight text boxes that "just fit" the text → one extra character breaks layout
- ✅ Always add 3-5cm extra width for centered text
- ✅ Test with slightly longer text in your mind
Rule of thumb: When in doubt, make text boxes wider. Extra whitespace is better than wrapped text overlapping other elements.
---
3) Color Principles
Contrast is King
Text must be readable:
- Dark background (brightness < 128) → white or light text (#FFFFFF, #EEEEEE)
- Light background (brightness ≥ 128) → dark text (#000000, #333333)
Brightness formula:
Brightness = (R × 299 + G × 587 + B × 114) / 1000Examples:
#000000(black) = 0 → dark → use white text#2C3E50(dark blue) = 62 → dark → use white text#E74C3C(red) = 115 → dark → use white text#F39C12(orange) = 160 → light → use dark text#FFFFFF(white) = 255 → light → use dark text
Safe combinations:
- White text (#FFFFFF) on dark backgrounds (#000000–#555555)
- Black/dark gray text (#000000–#333333) on light backgrounds (#EEEEEE–#FFFFFF)
- For mixed backgrounds: add a semi-transparent backing block behind the text
Tip: When in doubt, choose high contrast — it's always more readable.
Color Hierarchy
Maintain three-layer visual hierarchy:
Background → Decorative Shapes → Content (text/data)
(weakest) (medium) (strongest)Decorative shape opacity:
- ≤ 0.12 for background decoration (let content shine)
- 0.3-0.6 for content backgrounds (evidence slides, data cards)
Palette Selection
Create unique palettes based on topic mood — there are no universal formulas.
Need inspiration? Browse reference/styles/ for color combinations across different moods (dark, light, warm, vivid, bw, mixed).
---
4) Scene Actors (Animation Engine)
Purpose: Create smooth Morph animations through persistent shapes that change properties.
Setup
Define 6-8 actors on Slide 1:
- Large (5-8cm): Main visual anchors
- Medium (2-4cm): Supporting elements
- Small (1-2cm): Accents and details
Shape types: ellipse, rect, roundRect, triangle, diamond, star5, hexagon
Naming conventions (recommended for best results):
1. Scene actors (persistent shapes):
--prop 'name=!!dot-main' # Single quotes prevent shell ! escaping
--prop 'name=!!line-top'
--prop 'name=!!slash-accent'- Pattern:
!!prefix (double exclamation) - These shapes persist and morph across all slides
2. Content shapes (unique per slide):
--prop 'name=#s1-title' # Format: # + s + slide_number + - + description
--prop 'name=#s2-card1'
--prop 'name=#s3-stats'- Pattern:
#sN-prefix (where N = slide number) - These shapes are ghosted when moving to next slide
Benefits of following these patterns:
- Primary verification (pattern matching) is fastest and catches all cases
- Code is self-documenting:
#s2-card1clearly means slide 2's first card - Easy to debug: search for
#s1-to find all slide 1 content - Backup verification (duplicate detection) exists but has edge cases
Note: In Python build scripts, # and !! require no special quoting — pass them as plain strings (e.g. "--prop", "name=#s1-title").
How Morph Pairing Works
- PowerPoint matches shapes by name across adjacent slides
- Same name + different properties = smooth animated transition
- To hide a scene actor on a slide, move it off-screen:
x=36cm(ghost position, right of canvas) - To bring it back, move it to a visible position on the next slide
Example — 3 scene actors across 3 slides:
Slide 1: dot-main (x=2cm, y=3cm), line-top (x=5cm, y=1cm), slash-accent (x=10cm, rotation=30)
Slide 2: dot-main (x=8cm, y=10cm), line-top (x=15cm, y=5cm), slash-accent (x=20cm, rotation=60)
Slide 3: dot-main (x=36cm) [hidden], line-top (x=10cm, y=2cm), slash-accent (x=25cm, rotation=0)Evolution
On subsequent slides:
- Position: Move actors to different locations (create motion)
- Size: Grow or shrink (create emphasis)
- Rotation: Rotate for dynamic feel
- Color/Opacity: Subtle shifts (mood changes)
- Hide when not needed: Move to
x=36cm(ghost position)
Key: Adjacent slides should have noticeably different spatial compositions.
Content (added fresh per slide)
Content (titles, body text, numbers, cards) is added fresh on each slide with officecli add. Since text changes every slide, Morph just cross-fades it — no benefit from same-name pairing.
Critical workflow:
1. Clone previous slide → inherited content has old slide's prefix (e.g., #s1-title) 2. Ghost inherited content → move all #s(N-1)-* shapes to x=36cm 3. Add new content → with current slide's prefix (e.g., #s2-title)
Why ghosting matters: Without ghosting old content, slides accumulate shapes, causing visual overlap and confusion.
Coordinate Notes
- Ghost position:
x=36cm(off the right edge of the 33.87cm canvas) - Spread y-coordinates for ghosted shapes:
y=0cm,y=5cm,y=10cm,y=15cm - Coordinates start at
x=0cm— negative values are not supported
---
5) Page Types
Mix these to create rhythm. Each serves a different narrative purpose:
| Type | When to Use | Visual Structure |
|---|---|---|
| hero | Opening, closing | Large centered title + scattered scene actors |
| statement | Key message, transition | One impactful sentence + dramatic actor shifts (8cm+ moves) |
| pillars | Multi-point structure | 2-4 equal columns, actors become card backgrounds (opacity 0.12) |
| evidence | Data, statistics | 1-2 large asymmetric blocks + supporting details (opacity 0.3-0.6) |
| timeline | Process, sequence | Horizontal or vertical flow with step backgrounds |
| comparison | A vs B | Left-right split (50/50 or 60/40) with contrasting colors |
| grid | Multiple items | Scattered or grid layout, lighter feel |
| quote | Breathing moment | Centered text, minimal decoration |
| cta | Call to action | Return to bold, centered design |
| showcase | Featured display | Large central area for product/screenshot |
Variety matters: Avoid repeating the same type consecutively.
Design notes:
- pillars: Multi-column layout with even distribution, scene actors morph into card backgrounds (roundRect, opacity=0.12)
- evidence: Asymmetric data layout, 1 large actor (30-40% canvas) + 1 medium (20-30%), opacity 0.3-0.6 allowed for data backgrounds
- grid: Must differ from pillars and evidence — light, scattered vs. structured
---
6) Style References
Browse reference/styles/ for design inspiration. See reference/styles/INDEX.md for a complete catalog organized by use case.
---
7) Shape Index Mechanics
Shapes are numbered sequentially on each slide: shape[1], shape[2], shape[3]...
Index Behavior
On Slide 1: Shapes added in order
# Scene actors: shape[1-6]
# Content: shape[7+]After cloning: New slide inherits all shapes with identical indices
officecli add deck.pptx '/' --from '/slide[1]' # S2 now has shape[1-N]After adding: New shapes get the next available index
# If slide has 9 shapes, next add becomes shape[10]After modifying: Index stays the same
officecli set deck.pptx '/slide[2]/shape[3]' --prop x=20cm # Still shape[3]Pattern for Build Scripts
# Slide 1: 6 actors + 2 content = 8 shapes total
# Slide 2: Clone (8) → Ghost content (shape[7-8]) → Add new (shape[9+])
# Slide 3: Clone (10 shapes) → Ghost content (shape[9-10]) → Add new (shape[11+])Formula: Next slide's first new shape index = Previous slide's total shape count + 1
Debugging: Use officecli get <file> '/slide[N]' --depth 1 to inspect actual indices.
---
8) Morph Animation Essentials
Minimum Requirements
1. Slides 2+ must have `transition=morph` 2. Scene actors must have identical names across slides (!! prefix) 3. Previous content must be ghosted (x=36cm) before adding new content 4. Adjacent slides should have different spatial layouts
Creating Motion
Change at least 3 scene actors between adjacent slides:
- Move positions (x, y)
- Resize (width, height)
- Rotate (rotation)
- Shift colors (fill, opacity)
Goal: Create a sense of movement and transformation, not just fade in/out.
Entrance Effects
- Morph handles shape transitions automatically — entrance animations are usually unnecessary
- If an entrance is needed, use the
withtrigger so it plays simultaneously with morph:animation=fade-entrance-300-with
Animation Format (if needed)
Format: EFFECT[-DIRECTION][-DURATION][-TRIGGER][-delay=N][-easein=N][-easeout=N]
---
## Design Freedom
**This document provides principles, not prescriptions.**
- Trust your design judgment
- Learn from style references
- Experiment with color and layout
- Iterate based on visual results
- Let the content guide the design
**The best presentations come from understanding principles, then applying them creatively to your specific topic.**
---
Good design! 🎨Quality Reviewer
Role: Evaluate the quality of the generated PPT, identify issues, and guide fixes.
Goal: Ensure the delivered PPT has clear content, comfortable layout, and smooth animations.
---
Content Gate
Check Criteria
- ✅ 1 headline per slide
- ✅ Title <= 2 lines
- ✅ 3–5 bullet points
- ✅ No long paragraphs
- ✅ Conclusion First (title is an argument, not a topic)
Common Issues & Fixes
| Issue | Fix |
|---|---|
| Title exceeds 2 lines | Shorten the text or reduce font size (64pt → 56pt) |
| Too many bullet points (>5) | Merge similar points or split into two slides |
| Title is a topic instead of an argument | Rewrite as a conclusion: "Cost reduced by 40%" instead of "Cost Analysis" |
| Long paragraph present | Break into 3–5 bullet points, 1–2 lines each |
---
Layout Gate
Check Criteria
- ✅ Text boxes <= 14 per slide
- ✅ No overlapping text boxes
- ✅ x-coordinates aligned to grid lines
- ✅ scene actors opacity <= 0.12 (background decoration transparency)
- ✅ Text color has sufficient contrast with background (readability) ← mandatory check
Text Readability Check (critical)
Check Flow:
1. Get the color attribute of each text box 2. Get the background color at the text box's position (slide background or scene actor fill) 3. Determine whether the text color and background color provide sufficient contrast
Criteria (using brightness formula):
Brightness = (R × 299 + G × 587 + B × 114) / 1000
- Brightness < 128 → Dark background → Text must be light (#FFFFFF)
- Brightness >= 128 → Light background → Text must be dark (#000000 or #333333)Examples:
#2C3E50(dark blue) = 62 → Dark → Use white text#E74C3C(red) = 115 → Dark → Use white text#F39C12(orange) = 160 → Light → Use black text#FFFFFF(white) = 255 → Light → Use black text
Prohibited Errors:
- ❌ Dark blue text on dark blue background (similar or identical color values)
- ❌ White text on light background (insufficient contrast)
- ❌ Any case where text color = background color
Common Issues & Fixes
| Issue | How to Identify | Fix |
|---|---|---|
| Text color = background color | Text color and background fill are identical | Dark background → change text to FFFFFF; Light background → change text to 000000 |
| Insufficient contrast | Text color and background color are both dark or both light | Invert one of them: dark background → white text; light background → black text |
| Text wrapping overflow | Text box too narrow, text forced to wrap and overflows | Increase text box width, or reduce text content |
| Previous slide text residue | Previous slide's title has no ghost on the current slide | Move the unneeded headline/content actor to x=36cm |
| Text box overlap | Two text boxes with overlapping y-coordinates | Adjust with officecli set '/shape[N]' --prop y=XXcm |
| x-coordinate not aligned | x is not a grid multiple | Align to grid: 1.2cm, 2.4cm, 3.6cm... |
| scene actors obscuring text | Opacity too high (>0.12) | Lower transparency: --prop opacity=0.08 |
| Too many text boxes (>14) | Count shapes with type=textbox | Merge similar content or simplify descriptions |
---
Morph Gate
Check Criteria
- ✅ All slides 2+ have
transition=morphset - ✅ All slides have identically named scene actors (6–8 fixed actors present on every slide)
- ✅ Adjacent slides have noticeably different spatial layouts (shape position, size, and rotation vary)
- ✅ Each slide has enough scene shapes (6+) to create a sense of motion
- ✅ Actors that should not be visible are placed off-screen (ghost position:
x=36cmorx=36cm)
Check Method
Use officecli get to verify that scene actor names are consistent across adjacent slides:
# Check shape names on slide 1 and slide 2
officecli get <filename>.pptx '/slide[1]' --depth 1 | grep name
officecli get <filename>.pptx '/slide[2]' --depth 1 | grep name
# You should see the same list of actor names (e.g., !!dot-main, !!line-top)Common Issues & Fixes
| Issue | Fix |
|---|---|
| Morph has no transform effect, only fade | Critical issue: Scene actor names differ between adjacent slides. Fix: Use identically named actors across all slides (e.g., !!dot-main); place unneeded ones at x=36cm off-screen |
| Adjacent slides look too similar | Adjust scene actor position/size/rotation to create visual difference (displacement >= 5cm or rotation >= 15°) |
| Not enough scene shapes | Add decorative geometric shapes (ellipses, rectangles, triangles) with consistent names |
| Transition not smooth | Verify transition=morph is set; verify actors use identical names |
| An actor disappears on a slide | Do not delete the actor — move it off-screen (x=36cm or x=36cm) instead |
---
Delivery Gate
- ✅ Must pass
officecli validate <filename>.pptx - ✅ Must pass
officecli view outline <filename>.pptx(structure is reasonable) - ✅ Exactly 3 deliverables:
<topic-name>.pptx+build.sh+brief.md(no other files) - ✅
build.shcan be re-run to produce the same result
---
Check Flow
Per-Slide Check (during Phase 3 — mandatory)
Self-check immediately after generating each slide. Fix issues before moving on.
1. Content: Headline clear? Bullet points <= 5? 2. Layout: No overlaps? Text color contrasts with background? 3. Morph: Scene actors have same names as previous slide? Scene actors not needed are ghosted to x=36cm? 4. ⚠️ TEXT OVERLAP CHECK: Previous slide's content actors are ALL ghosted (x=36cm)? This slide's new content is added fresh?
- Since content is added per slide (not pre-defined on slide 1), you only need to check the previous slide's content — not all slide types
- This is the #1 most common defect — failure causes visible text overlap
This is the primary quality gate. If every slide passes, the PPT is already high quality.
Pre-Delivery Check (Phase 4 — two commands)
After all slides are generated:
officecli validate <filename>.pptx # must pass
officecli view outline <filename>.pptx # verify structureIf issues found → fix and re-validate (max 2 rounds). If still failing → report to user.
---
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT="$SCRIPT_DIR/bw__brutalist_raw.pptx"
echo "Building: bw--brutalist-raw (Brutalist Design)"
rm -f "$OUTPUT"
officecli create "$OUTPUT"
# Colors
WHITE=FFFFFF
BLACK=000000
RED=FF0000
# ============================================
# SLIDE 1 - HERO (反叛 / REVOLT)
# ============================================
echo "Building Slide 1: Hero..."
officecli add "$OUTPUT" '/' --type slide --prop layout=blank --prop background=$WHITE
# Scene actors: geometric shapes with thick borders and violent positioning
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!border-box' \
--prop preset=rect \
--prop fill=$WHITE \
--prop line=$BLACK \
--prop lineWidth=3pt \
--prop x=20cm --prop y=2cm --prop width=10cm --prop height=8cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!block-solid' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=3cm --prop y=13cm --prop width=5cm --prop height=5cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!accent-red' \
--prop preset=rect \
--prop fill=$RED \
--prop x=10cm --prop y=15cm --prop width=3cm --prop height=1cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!line-heavy' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=6cm --prop y=11cm --prop width=20cm --prop height=0.15cm
# Content: oversized titles
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=#s1-title' \
--prop text="反叛" \
--prop font="Arial Black" \
--prop size=120 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=2cm --prop y=3cm --prop width=15cm --prop height=5cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=#s1-subtitle' \
--prop text="REVOLT" \
--prop font="Arial Black" \
--prop size=48 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=2cm --prop y=8.5cm --prop width=10cm --prop height=2cm
# ============================================
# SLIDE 2 - STATEMENT (ART IS NOT DECORATION)
# ============================================
echo "Building Slide 2: Statement..."
officecli add "$OUTPUT" '/' --type slide --prop layout=blank --prop background=$WHITE
officecli set "$OUTPUT" '/slide[2]' --prop transition=morph
# Scene actors: violent position shifts (12cm+ moves)
officecli add "$OUTPUT" '/slide[2]' --type shape \
--prop 'name=!!border-box' \
--prop preset=rect \
--prop fill=none \
--prop line=$BLACK \
--prop lineWidth=3pt \
--prop x=4cm --prop y=8cm --prop width=12cm --prop height=9cm
officecli add "$OUTPUT" '/slide[2]' --type shape \
--prop 'name=!!block-solid' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=25cm --prop y=2cm --prop width=5cm --prop height=5cm
officecli add "$OUTPUT" '/slide[2]' --type shape \
--prop 'name=!!accent-red' \
--prop preset=rect \
--prop fill=$RED \
--prop x=28cm --prop y=12cm --prop width=3cm --prop height=1cm
officecli add "$OUTPUT" '/slide[2]' --type shape \
--prop 'name=!!line-heavy' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=2cm --prop y=13cm --prop width=20cm --prop height=0.15cm
# Add diagonal line (new in slide 2)
officecli add "$OUTPUT" '/slide[2]' --type shape \
--prop 'name=!!line-diag' \
--prop preset=rect \
--prop fill=$BLACK \
--prop rotation=35 \
--prop x=18cm --prop y=8cm --prop width=15cm --prop height=0.08cm
# Content: large statement
officecli add "$OUTPUT" '/slide[2]' --type shape \
--prop 'name=#s2-statement' \
--prop text="ART IS NOT\nDECORATION" \
--prop font="Arial Black" \
--prop size=96 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=2cm --prop y=2cm --prop width=25cm --prop height=10cm
# ============================================
# SLIDE 3 - PILLARS (三位参展艺术家)
# ============================================
echo "Building Slide 3: Pillars..."
officecli add "$OUTPUT" '/' --type slide --prop layout=blank --prop background=$WHITE
officecli set "$OUTPUT" '/slide[3]' --prop transition=morph
# Scene actors: structural frames
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=!!border-box' \
--prop preset=rect \
--prop fill=$WHITE \
--prop line=$BLACK \
--prop lineWidth=3pt \
--prop x=2cm --prop y=5cm --prop width=8cm --prop height=10cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=!!block-solid' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=28cm --prop y=8cm --prop width=5cm --prop height=5cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=!!accent-red' \
--prop preset=rect \
--prop fill=$RED \
--prop x=2cm --prop y=16cm --prop width=3cm --prop height=1cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=!!line-heavy' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=2cm --prop y=4.5cm --prop width=20cm --prop height=0.15cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=!!line-diag' \
--prop preset=rect \
--prop fill=$BLACK \
--prop rotation=0 \
--prop x=25cm --prop y=2cm --prop width=15cm --prop height=0.08cm
# Content: title and artist list
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=#s3-title' \
--prop text="三位参展艺术家" \
--prop font="Arial Black" \
--prop size=96 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=2cm --prop y=1.5cm --prop width=20cm --prop height=3cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=#s3-artist1' \
--prop text="01 / 张伟 - 解构主义装置艺术" \
--prop font="Courier New" \
--prop size=24 \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=6cm --prop width=25cm --prop height=1.5cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=#s3-artist2' \
--prop text="02 / 李娜 - 后现代影像创作" \
--prop font="Courier New" \
--prop size=24 \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=8.5cm --prop width=25cm --prop height=1.5cm
officecli add "$OUTPUT" '/slide[3]' --type shape \
--prop 'name=#s3-artist3' \
--prop text="03 / 王强 - 激进行为艺术" \
--prop font="Courier New" \
--prop size=24 \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=11cm --prop width=25cm --prop height=1.5cm
# ============================================
# SLIDE 4 - EVIDENCE (首展反响 / Metrics)
# ============================================
echo "Building Slide 4: Evidence..."
officecli add "$OUTPUT" '/' --type slide --prop layout=blank --prop background=$WHITE
officecli set "$OUTPUT" '/slide[4]' --prop transition=morph
# Scene actors: asymmetric layout
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=!!border-box' \
--prop preset=rect \
--prop fill=none \
--prop line=$BLACK \
--prop lineWidth=3pt \
--prop x=22cm --prop y=10cm --prop width=10cm --prop height=8cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=!!block-solid' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=2cm --prop y=15cm --prop width=5cm --prop height=3cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=!!accent-red' \
--prop preset=rect \
--prop fill=$RED \
--prop x=15cm --prop y=10.5cm --prop width=1cm --prop height=3cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=!!line-heavy' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=2cm --prop y=9.5cm --prop width=20cm --prop height=0.15cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=!!line-diag' \
--prop preset=rect \
--prop fill=$BLACK \
--prop rotation=145 \
--prop x=20cm --prop y=1cm --prop width=15cm --prop height=0.08cm
# Content: title and metrics
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-title' \
--prop text="首展反响" \
--prop font="Arial Black" \
--prop size=96 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=2cm --prop y=1.5cm --prop width=20cm --prop height=3cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-metric1-num' \
--prop text="3天" \
--prop font="Courier New" \
--prop size=72 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=6cm --prop width=10cm --prop height=2cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-metric1-label' \
--prop text="首展持续时间" \
--prop font="Courier New" \
--prop size=20 \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=8cm --prop width=15cm --prop height=1cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-metric2-num' \
--prop text="1200+" \
--prop font="Courier New" \
--prop size=72 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=15cm --prop y=6cm --prop width=10cm --prop height=2cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-metric2-label' \
--prop text="观众人次" \
--prop font="Courier New" \
--prop size=20 \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=15cm --prop y=8cm --prop width=15cm --prop height=1cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-metric3-num' \
--prop text="50+" \
--prop font="Courier New" \
--prop size=72 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=11cm --prop width=10cm --prop height=2cm
officecli add "$OUTPUT" '/slide[4]' --type shape \
--prop 'name=#s4-metric3-label' \
--prop text="媒体报道" \
--prop font="Courier New" \
--prop size=20 \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=13cm --prop width=15cm --prop height=1cm
# ============================================
# SLIDE 5 - CTA (展览持续至 4月30日)
# ============================================
echo "Building Slide 5: CTA..."
officecli add "$OUTPUT" '/' --type slide --prop layout=blank --prop background=$WHITE
officecli set "$OUTPUT" '/slide[5]' --prop transition=morph
# Scene actors: scattered edges with dramatic final positions
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=!!border-box' \
--prop preset=rect \
--prop fill=$WHITE \
--prop line=$BLACK \
--prop lineWidth=3pt \
--prop x=22cm --prop y=3cm --prop width=9cm --prop height=10cm
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=!!block-solid' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=2cm --prop y=1cm --prop width=5cm --prop height=5cm
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=!!accent-red' \
--prop preset=rect \
--prop fill=$RED \
--prop x=30cm --prop y=17cm --prop width=3cm --prop height=1cm
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=!!line-heavy' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=3cm --prop y=12cm --prop width=20cm --prop height=0.15cm
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=!!line-diag' \
--prop preset=rect \
--prop fill=$BLACK \
--prop rotation=35 \
--prop x=10cm --prop y=2cm --prop width=15cm --prop height=0.08cm
# Content: CTA message
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=#s5-title' \
--prop text="展览持续至\n4月30日" \
--prop font="Arial Black" \
--prop size=96 \
--prop bold=true \
--prop color=$BLACK \
--prop align=left \
--prop fill=none \
--prop x=3cm --prop y=4cm --prop width=25cm --prop height=8cm
officecli add "$OUTPUT" '/slide[5]' --type shape \
--prop 'name=#s5-details' \
--prop text="地点: 798艺术区 A12展厅\n时间: 10:00-20:00 (周二闭馆)\n门票: 免费" \
--prop font="Courier New" \
--prop size=20 \
--prop color=$BLACK \
--prop align=left \
--prop lineSpacing=1.6 \
--prop fill=none \
--prop x=3cm --prop y=13cm --prop width=20cm --prop height=4cm
# ============================================
# FINAL VALIDATION
# ============================================
officecli validate "$OUTPUT"
officecli view "$OUTPUT" outline
echo "✅ Build complete: $OUTPUT"
Brutalist Raw — Brutalism
Style Overview
Pure white background + black thick borders + red accents, oversized fonts, thick lines, violent typography.
- Scene: Avant-garde art exhibitions, experimental design, independent brands, anti-traditional contexts
- Mood: Rebellious, rough, impactful, raw
- Tone: Black-white-red three colors
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Pure White | #FFFFFF | Page background |
| Pure Black | #000000 | Thick borders, solid blocks, thick lines, titles |
| Pure Red | #FF0000 | Only accent color |
Typography
| Element | Font | Description |
|---|---|---|
| Main Title | Arial Black 120pt | Intentionally oversized, dominating the canvas |
| Subtitle | Arial Black 48pt | Large English text |
| Body | Arial | Regular size |
Design Techniques
- Thick borders: rect + 3pt black border lines, deliberately exposing structure
- Solid color blocks: Pure black rect (5×5cm), heavy geometric feel
- Red accents: Only color (pure red #FF0000), extremely restrained
- Thick lines: 0.15cm high black rect, as divider lines
- Oversized fonts: 120pt titles intentionally overflow conventional layout areas
- Violent Morph: Shapes move violently between pages (12cm+), not elegant drift, but "slam" over
- Difference from swiss-bauhaus: bauhaus is rigorous and rational, brutalist is intentionally rough and raw
Reference Script
Complete build script available in build.sh.
Recommended slides to read for understanding core design techniques:
- Slide 1 (hero) — Layout of oversized titles + thick borders + solid blocks
- Slide 2 (statement) — Violent morph movement (12cm+)
No need to read all — skim 2-3 representative slides.
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUTPUT="$SCRIPT_DIR/bw__mono_line.pptx"
echo "Building: bw--mono-line (Minimalist Lines)"
rm -f "$OUTPUT"
officecli create "$OUTPUT"
# Colors
BG=FFFFFF
BLACK=1A1A1A
GRAY=C8C8C8
# Off-canvas position for hidden elements
OFFSCREEN=36cm
# ============================================
# SLIDE 1 - HERO
# ============================================
echo "Building Slide 1: Hero..."
officecli add "$OUTPUT" '/' --type slide --prop layout=blank --prop background=$BG
# Scene actors: lines
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!line-h-top' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=0cm --prop y=1.5cm --prop width=20cm --prop height=0.05cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!line-h-mid' \
--prop preset=rect \
--prop fill=$GRAY \
--prop x=10cm --prop y=13cm --prop width=15cm --prop height=0.03cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!line-v-left' \
--prop preset=rect \
--prop fill=$BLACK \
--prop x=2cm --prop y=0cm --prop width=0.05cm --prop height=12cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!line-v-right' \
--prop preset=rect \
--prop fill=$GRAY \
--prop x=30cm --prop y=11cm --prop width=0.03cm --prop height=8cm
# Scene actors: dots
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!dot-accent-1' \
--prop preset=ellipse \
--prop fill=$BLACK \
--prop x=28cm --prop y=15cm --prop width=1cm --prop height=1cm
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!dot-accent-2' \
--prop preset=ellipse \
--prop fill=$GRAY \
--prop x=31cm --prop y=16cm --prop width=0.8cm --prop height=0.8cm
# Scene actors: all text elements (visible on slide 1, hidden on other slides initially)
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!hero-title' \
--prop text="Your Presentation Title" \
--prop font="Segoe UI Light" \
--prop size=54 \
--prop color=$BLACK \
--prop x=4cm --prop y=5cm --prop width=26cm --prop height=4cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!hero-subtitle' \
--prop text="Subtitle goes here" \
--prop font="Segoe UI" \
--prop size=20 \
--prop color=$GRAY \
--prop x=4cm --prop y=9.5cm --prop width=20cm --prop height=2cm --prop fill=none
officecli set "$OUTPUT" '/slide[1]/shape[7]/paragraph[1]' --prop align=l
officecli set "$OUTPUT" '/slide[1]/shape[8]/paragraph[1]' --prop align=l
# Pre-create text elements for later slides (hidden off-canvas)
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!statement-text' \
--prop text="The Big Idea" \
--prop font="Segoe UI Light" \
--prop size=64 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=2cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!pillar-1-num' \
--prop text="01" \
--prop font="Segoe UI Light" \
--prop size=40 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=10cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!pillar-1-title' \
--prop text="Strategy" \
--prop font="Segoe UI Light" \
--prop size=28 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=17cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!pillar-2-num' \
--prop text="02" \
--prop font="Segoe UI Light" \
--prop size=40 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=4cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!pillar-2-title' \
--prop text="Design" \
--prop font="Segoe UI Light" \
--prop size=28 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=12cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!pillar-3-num' \
--prop text="03" \
--prop font="Segoe UI Light" \
--prop size=40 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=20cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!pillar-3-title' \
--prop text="Growth" \
--prop font="Segoe UI Light" \
--prop size=28 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=6cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!metric-1-num' \
--prop text="42%" \
--prop font="Segoe UI Light" \
--prop size=54 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=14cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!metric-1-label' \
--prop text="Efficiency Gain" \
--prop font="Segoe UI" \
--prop size=16 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=22cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!metric-2-num' \
--prop text="3.2x" \
--prop font="Segoe UI Light" \
--prop size=54 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=8cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!metric-2-label' \
--prop text="Growth Rate" \
--prop font="Segoe UI" \
--prop size=16 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=16cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!metric-3-num' \
--prop text="98%" \
--prop font="Segoe UI Light" \
--prop size=54 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=24cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!metric-3-label' \
--prop text="Satisfaction" \
--prop font="Segoe UI" \
--prop size=16 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=0cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!cta-text' \
--prop text="Let's Connect" \
--prop font="Segoe UI Light" \
--prop size=54 \
--prop color=$BLACK \
--prop x=${OFFSCREEN} --prop y=18cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
officecli add "$OUTPUT" '/slide[1]' --type shape \
--prop 'name=!!cta-sub' \
--prop text="hello@company.com" \
--prop font="Segoe UI" \
--prop size=18 \
--prop color=$GRAY \
--prop x=${OFFSCREEN} --prop y=26cm --prop width=0.1cm --prop height=0.1cm --prop fill=none
# ============================================
# SLIDE 2 - STATEMENT
# ============================================
echo "Building Slide 2: Statement..."
# Clone slide 1
officecli add "$OUTPUT" '/' --from '/slide[1]'
officecli set "$OUTPUT" '/slide[2]' --prop transition=morph
# Move lines to center intersection
officecli set "$OUTPUT" '/slide[2]/shape[1]' --prop x=7cm --prop y=9.5cm --prop width=20cm --prop height=0.05cm
officecli set "$OUTPUT" '/slide[2]/shape[2]' --prop x=5cm --prop y=9.5cm --prop width=24cm --prop height=0.03cm
officecli set "$OUTPUT" '/slide[2]/shape[3]' --prop x=16.5cm --prop y=3cm --prop width=0.05cm --prop height=13cm
officecli set "$OUTPUT" '/slide[2]/shape[4]' --prop x=17.5cm --prop y=4cm --prop width=0.03cm --prop height=11cm
# Move dots
officecli set "$OUTPUT" '/slide[2]/shape[5]' --prop x=3cm --prop y=9cm --prop width=1cm --prop height=1cm
officecli set "$OUTPUT" '/slide[2]/shape[6]' --prop x=4.5cm --prop y=10.5cm --prop width=0.8cm --prop height=0.8cm
# Hide slide 1 text (hero)
officecli set "$OUTPUT" '/slide[2]/shape[7]' --prop x=${OFFSCREEN} --prop y=2cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[2]/shape[8]' --prop x=${OFFSCREEN} --prop y=10cm --prop width=0.1cm --prop height=0.1cm
# Show statement text
officecli set "$OUTPUT" '/slide[2]/shape[9]' --prop x=4cm --prop y=5.5cm --prop width=26cm --prop height=5cm
officecli set "$OUTPUT" '/slide[2]/shape[9]/paragraph[1]' --prop align=center
# ============================================
# SLIDE 3 - THREE PILLARS
# ============================================
echo "Building Slide 3: Three Pillars..."
# Clone slide 2
officecli add "$OUTPUT" '/' --from '/slide[2]'
officecli set "$OUTPUT" '/slide[3]' --prop transition=morph
# Move lines to create column dividers
officecli set "$OUTPUT" '/slide[3]/shape[1]' --prop x=1.2cm --prop y=1.2cm --prop width=31cm --prop height=0.05cm
officecli set "$OUTPUT" '/slide[3]/shape[2]' --prop x=1.2cm --prop y=4.5cm --prop width=31cm --prop height=0.03cm
officecli set "$OUTPUT" '/slide[3]/shape[3]' --prop x=11.5cm --prop y=5cm --prop width=0.05cm --prop height=12cm
officecli set "$OUTPUT" '/slide[3]/shape[4]' --prop x=22.5cm --prop y=5cm --prop width=0.03cm --prop height=12cm
# Move dots
officecli set "$OUTPUT" '/slide[3]/shape[5]' --prop x=5cm --prop y=2.8cm --prop width=1cm --prop height=1cm
officecli set "$OUTPUT" '/slide[3]/shape[6]' --prop x=16cm --prop y=2.8cm --prop width=0.8cm --prop height=0.8cm
# Hide statement text
officecli set "$OUTPUT" '/slide[3]/shape[9]' --prop x=${OFFSCREEN} --prop y=17cm --prop width=0.1cm --prop height=0.1cm
# Show three pillars
officecli set "$OUTPUT" '/slide[3]/shape[10]' --prop x=2cm --prop y=5.5cm --prop width=8cm --prop height=3cm
officecli set "$OUTPUT" '/slide[3]/shape[11]' --prop x=2cm --prop y=9cm --prop width=8cm --prop height=3cm
officecli set "$OUTPUT" '/slide[3]/shape[12]' --prop x=13cm --prop y=5.5cm --prop width=8cm --prop height=3cm
officecli set "$OUTPUT" '/slide[3]/shape[13]' --prop x=13cm --prop y=9cm --prop width=8cm --prop height=3cm
officecli set "$OUTPUT" '/slide[3]/shape[14]' --prop x=24cm --prop y=5.5cm --prop width=8cm --prop height=3cm
officecli set "$OUTPUT" '/slide[3]/shape[15]' --prop x=24cm --prop y=9cm --prop width=8cm --prop height=3cm
# ============================================
# SLIDE 4 - METRICS
# ============================================
echo "Building Slide 4: Metrics..."
# Clone slide 3
officecli add "$OUTPUT" '/' --from '/slide[3]'
officecli set "$OUTPUT" '/slide[4]' --prop transition=morph
# Move lines
officecli set "$OUTPUT" '/slide[4]/shape[1]' --prop x=1.2cm --prop y=8cm --prop width=31cm --prop height=0.05cm
officecli set "$OUTPUT" '/slide[4]/shape[2]' --prop x=20cm --prop y=14cm --prop width=12cm --prop height=0.03cm
officecli set "$OUTPUT" '/slide[4]/shape[3]' --prop x=19cm --prop y=1cm --prop width=0.05cm --prop height=6cm
officecli set "$OUTPUT" '/slide[4]/shape[4]' --prop x=32cm --prop y=10cm --prop width=0.03cm --prop height=7cm
# Move dots
officecli set "$OUTPUT" '/slide[4]/shape[5]' --prop x=2cm --prop y=4cm --prop width=1cm --prop height=1cm
officecli set "$OUTPUT" '/slide[4]/shape[6]' --prop x=13cm --prop y=4cm --prop width=0.8cm --prop height=0.8cm
# Hide pillars
officecli set "$OUTPUT" '/slide[4]/shape[10]' --prop x=${OFFSCREEN} --prop y=6cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[4]/shape[11]' --prop x=${OFFSCREEN} --prop y=14cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[4]/shape[12]' --prop x=${OFFSCREEN} --prop y=22cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[4]/shape[13]' --prop x=${OFFSCREEN} --prop y=0cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[4]/shape[14]' --prop x=${OFFSCREEN} --prop y=8cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[4]/shape[15]' --prop x=${OFFSCREEN} --prop y=16cm --prop width=0.1cm --prop height=0.1cm
# Show metrics
officecli set "$OUTPUT" '/slide[4]/shape[16]' --prop x=3cm --prop y=2cm --prop width=14cm --prop height=5cm
officecli set "$OUTPUT" '/slide[4]/shape[17]' --prop x=3cm --prop y=6cm --prop width=14cm --prop height=2cm
officecli set "$OUTPUT" '/slide[4]/shape[18]' --prop x=3cm --prop y=9cm --prop width=14cm --prop height=5cm
officecli set "$OUTPUT" '/slide[4]/shape[19]' --prop x=3cm --prop y=13cm --prop width=14cm --prop height=2cm
officecli set "$OUTPUT" '/slide[4]/shape[20]' --prop x=20cm --prop y=2cm --prop width=12cm --prop height=5cm
officecli set "$OUTPUT" '/slide[4]/shape[21]' --prop x=20cm --prop y=6cm --prop width=12cm --prop height=2cm
# ============================================
# SLIDE 5 - CTA
# ============================================
echo "Building Slide 5: CTA..."
# Clone slide 4
officecli add "$OUTPUT" '/' --from '/slide[4]'
officecli set "$OUTPUT" '/slide[5]' --prop transition=morph
# Move lines to create border frame
officecli set "$OUTPUT" '/slide[5]/shape[1]' --prop x=0cm --prop y=0.8cm --prop width=33.87cm --prop height=0.05cm
officecli set "$OUTPUT" '/slide[5]/shape[2]' --prop x=0cm --prop y=18.2cm --prop width=33.87cm --prop height=0.03cm
officecli set "$OUTPUT" '/slide[5]/shape[3]' --prop x=1.2cm --prop y=0cm --prop width=0.05cm --prop height=19.05cm
officecli set "$OUTPUT" '/slide[5]/shape[4]' --prop x=32.6cm --prop y=0cm --prop width=0.03cm --prop height=19.05cm
# Move dots to center
officecli set "$OUTPUT" '/slide[5]/shape[5]' --prop x=16cm --prop y=13cm --prop width=1cm --prop height=1cm
officecli set "$OUTPUT" '/slide[5]/shape[6]' --prop x=17.5cm --prop y=13.5cm --prop width=0.8cm --prop height=0.8cm
# Hide metrics
officecli set "$OUTPUT" '/slide[5]/shape[16]' --prop x=${OFFSCREEN} --prop y=8cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[5]/shape[17]' --prop x=${OFFSCREEN} --prop y=16cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[5]/shape[18]' --prop x=${OFFSCREEN} --prop y=0cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[5]/shape[19]' --prop x=${OFFSCREEN} --prop y=24cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[5]/shape[20]' --prop x=${OFFSCREEN} --prop y=2cm --prop width=0.1cm --prop height=0.1cm
officecli set "$OUTPUT" '/slide[5]/shape[21]' --prop x=${OFFSCREEN} --prop y=10cm --prop width=0.1cm --prop height=0.1cm
# Show CTA
officecli set "$OUTPUT" '/slide[5]/shape[22]' --prop x=5cm --prop y=5cm --prop width=24cm --prop height=5cm
officecli set "$OUTPUT" '/slide[5]/shape[23]' --prop x=8cm --prop y=10.5cm --prop width=18cm --prop height=2cm
officecli set "$OUTPUT" '/slide[5]/shape[22]/paragraph[1]' --prop align=center
officecli set "$OUTPUT" '/slide[5]/shape[23]/paragraph[1]' --prop align=center
# ============================================
# FINAL VALIDATION
# ============================================
officecli validate "$OUTPUT"
officecli view "$OUTPUT" outline
echo "✅ Build complete: $OUTPUT"
01-mono-line — Minimalist Lines
Style Overview
Using ultra-thin lines and small dots to construct pure black-white minimalist space, conveying professionalism through whitespace and geometric order.
- Scene: Minimalist business, academic reports, consulting proposals
- Mood: Calm, restrained, professional
- Tone: Pure black-white + mid-gray accents
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Pure White | FFFFFF | Background |
| Near Black | 1A1A1A | Main lines, title text, main dots |
| Mid Gray | C8C8C8 | Secondary lines, subtitle text, secondary dots |
Typography
| Role | Font | Size | Color |
|---|---|---|---|
| Main Title | Segoe UI Light | 54pt | 1A1A1A |
| Subtitle | Segoe UI | 20pt | C8C8C8 |
| Statement | Segoe UI Light | 64pt | 1A1A1A |
| Numbers | Segoe UI Light | 40pt | C8C8C8 |
| Column Title | Segoe UI Light | 28pt | 1A1A1A |
| Data Numbers | Segoe UI Light | 54pt | 1A1A1A |
| Data Label | Segoe UI | 16pt | C8C8C8 |
Design Techniques
- Ultra-thin rectangles simulate lines: Horizontal lines height=0.05cm / 0.03cm, vertical lines width=0.05cm / 0.03cm, implemented using
rectpreset - Small ellipses as decorative dots: 1cm / 0.8cm
ellipse, black or gray - Abundant whitespace: Only lines divide space on white background
- Morph animation: Lines slide and stretch to change length and position between pages; dots drift to new positions
- Off-canvas hidden elements: Text elements initially placed outside canvas (x=36cm), slide into view through morph
Scene Elements
6 scene elements with different positions on each page, animated through Morph transitions:
| Name | preset | fill | Typical Size | Description |
|---|---|---|---|---|
!!line-h-top | rect | 1A1A1A | 20cm x 0.05cm | Horizontal main line |
!!line-h-mid | rect | C8C8C8 | 15cm x 0.03cm | Horizontal secondary line |
!!line-v-left | rect | 1A1A1A | 0.05cm x 12cm | Vertical main line |
!!line-v-right | rect | C8C8C8 | 0.03cm x 8cm | Vertical secondary line |
!!dot-accent-1 | ellipse | 1A1A1A | 1cm x 1cm | Main dot |
!!dot-accent-2 | ellipse | C8C8C8 | 0.8cm x 0.8cm | Secondary dot |
Page Structure
5 pages total, Slides 2-5 set transition=morph:
| Slide | Type | Elements | Description |
|---|---|---|---|
| Slide 1 | Hero | Large title + subtitle left-aligned, lines construct asymmetric framework | |
| Slide 2 | Statement | Centered large text statement, lines intersect at center of canvas | |
| Slide 3 | 3-Column Pillars | Lines as column dividers, numbered 01/02/03 + titles, three columns side by side | |
| Slide 4 | Metrics / Evidence | Data display, left large numbers + right metrics, lines divide areas | |
| Slide 5 | CTA / Closing | Lines converge into canvas border frame, centered CTA text + contact info |
Reference Script
Complete build script available in build.sh. Recommended slides to read for understanding core design techniques:
- Slide 1 (Hero) — Demonstrates initial layout of lines+dots and placement of off-canvas text elements
- Slide 3 (Pillars) — How lines transform into column dividers, grid arrangement of three columns of content
- Slide 5 (CTA) — Animation effect of lines converging into full-canvas border frame
No need to read all — skim 2-3 representative slides.
Swiss Bauhaus — Swiss Bauhaus
Style Overview
Strict red-black-white three-color geometric grid, classic Swiss/Bauhaus design style.
- Scene: Design agencies, architectural firms, art exhibitions, brand design
- Mood: Rational, rigorous, classic, restrained
- Tone: Red-black-white three colors
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Off-White | F5F5F5 | Background |
| Bauhaus Red | E63322 | Main blocks, accent color |
| Near Black | 1C1C1C | Blocks, text |
| White | F5F5F5 | Blocks (matching background) |
Strict red/black/white three-color palette, no other colors used.
Typography
- Titles: Segoe UI Black
- Body: Segoe UI
- Note: Impact font not used (explicitly stated in script comments)
Scene Elements
- blk-a (red rectangle), blk-b (dark rectangle), blk-c (white rectangle) — Main color blocks
- bar-1 (thin lines) — Grid/divider lines
- dot-1, dot-2 (small squares) — Geometric punctuation decorations
- photo-1, photo-2 — Photo elements
- Uses image assets (design-workshop.jpg, design-abstract.jpg, team1.jpg) — can be ignored when using as style reference
Design Techniques
- Classic Swiss/Bauhaus design — strict geometric grid
- Large color blocks dramatically reorganize on each page: left column → top bar → middle band → bottom fill → full coverage
- Thin lines (bar) create grid/ruler lines
- Small squares (dot) as geometric punctuation decorations
- Text follows strict margin rules (x≥1.6cm, width≤block-2cm)
- 6 slides
Reference Script
Complete build script available in build.sh. Note: Script uses image resources from assets/ directory, image parts can be ignored when using as style reference. Recommended slides to read for understanding core design techniques:
- Slide 1 — Title page, initial geometric layout of blocks + thin line grid
- Slide 4 — Major block reorganization, demonstrating dramatic transformation from left column to horizontal bar
- Slide 6 — Full block coverage final state, understanding complete transformation sequence
No need to read all — skim 2-3 representative slides.
Swiss System — Pure Black and Red
Style Overview
Pure white background with ink black and fire red only. Features !!rule actor (full-width rect) that sweeps vertically across slides, creating dramatic transformations.
- Scenario: Corporate, finance, consulting, high-end professional services
- Mood: Clean, systematic, bold, Swiss design
- Tone: White with black and red accents
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Background | #FFFFFF | Pure white |
| Ink | #000000 | Black for text and rules |
| Fire | #FF0000 | Red for accents |
Design Techniques
- !!rule (full-width INK rect) sweeps slide vertically:
- S1: mid-rule
- S2: top thick
- S3: bottom thick
- S4: thin center
- S5: wide top-third band
- S6: full INK inversion (CTA - entire slide becomes black)
- Zero darkness until final CTA slide
- Swiss design principles: grid, typography, minimal color
Key Morph Pattern
The !!rule actor creates a dramatic journey from subtle horizontal line to complete slide inversion, representing transformation from light to dark, question to answer, problem to solution.
Reference Script
Complete build script available in build.py.
architectural-plan — Architectural Plan
Style Overview
Dark blue-gray background with light blue panels and gold accents, using structured panel divisions to simulate the professional layout of architectural plans.
- Scene: Architectural design, business plans, real estate development
- Mood: Professional, structured, architectural
- Color Tone: Dark blue-gray background + light blue panels + gold accents
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Dark Blue | 1C2B3A | Background |
| Panel Blue | B8D4E0 | Content panels, sidebars |
| Gold Accent | F4C430 | Accent color, title underlines, badges |
Design Techniques
- Pages divided into dark areas and light panel areas, simulating the white space and annotation zones of architectural drawings
- Left-right content panel alternating layout (left content/right panel or right content/left panel), adding rhythmic variation
- Top navigation bar + numbering system (01, 02...), reinforcing the sectional coding aesthetic of architectural drawings
- star_badge star-shaped badges as decorations, gold title underlines elevate hierarchy
- roundRect rounded buttons with gold fill, unifying CTA visual style
Reference Script
Full build script available in build.sh. Recommended slides to read for understanding core design techniques:
- Slide 1 (title) — Left-right panel division layout and star_badge decoration
- Slide 3 (services) — Alternating panel layout and top navigation bar implementation
- Slide 5 (contact) — Multi-statistic arrangement and CTA button design
No need to read all — skim 2-3 representative slides.
Aurora Softedge — Design Portfolio
Style Overview
Aurora dark background with layered soft-edge ellipses. Innovative softedge technique creates depth through graduated blur.
- Scenario: Design portfolios, creative showcases, art galleries
- Mood: Aurora-like, dreamy, artistic, mysterious
- Tone: Dark with soft aurora colors
Design Techniques
- Layered soft-edge ellipses (outer = larger softedge, inner = sharp)
- Soft-edge formula: base ellipse softedge = radius × 2.5pt
- Aurora color palette
- Graduated blur creates depth
Reference Script
Complete build script available in build.py.
S15-blueprint-grid — Engineering Blueprint Grid
Style Overview
Deep blue background with white grid lines and gold markers creates a precise engineering drafting aesthetic.
- Scene: Technical planning, engineering blueprints, system architecture
- Mood: Precise, professional, engineering-oriented
- Color Tone: Deep blue + white grid + gold accents
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Deep Blue | 1B3A5C | Background |
| Bright Blue | 4A90D9 | Highlight color, titles |
| White | FFFFFF | Grid lines, body text |
| Gold Warning | E8C547 | Warning markers, CTA buttons |
Design Techniques
- Use rect to draw evenly spaced horizontal/vertical grid lines (opacity 0.25), simulating blueprint graph paper
- Use ellipse as positioning marker points, suggesting key nodes in a coordinate system
- All shapes use low transparency overlay to maintain blueprint hierarchy
- Typography uses monospace or bold sans-serif fonts to reinforce engineering drafting aesthetic
Reference Script
Full build script available in build.sh. Recommended slides to read for understanding core design techniques:
- Slide 1 (hero) — Grid line drawing method and layout spacing
- Slide 3 (pillars) — Multi-column layout + grid-aligned typesetting technique
No need to read all — skim 2-3 representative slides.
circle-digital — Dark Cool Digital Agency
Style Overview
Near-black background with dark gray cards and neon lime accent color, creating a dark mode digital marketing agency aesthetic.
- Scene: Digital marketing, creative agencies, tech companies
- Mood: Modern, dark-cool, digital
- Color Tone: Near-black background + dark gray card layers + neon lime accents
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Near Black | 0D0E11 | Background |
| Dark Gray 1 | 171A20 | Card bottom layer |
| Dark Gray 2 | 22252E | Card middle layer |
| Dark Gray 3 | 2D3140 | Card top layer |
| Neon Lime | C4FF00 | Accent color, CTA, decorative lines |
Design Techniques
- Extensive use of circles (ellipse) as image placeholders and decorative elements, embodying the "circle" theme
- Multi-layer dark gray cards stacked to create dark mode hierarchy and depth
- Neon lime as the only bright color, used for CTA buttons, decorative dots, and dividers, creating strong contrast
- Left vertical decorative bars + numbering system, adding structural sense to the layout
- roundRect rounded buttons with neon lime fill, highlighting calls to action
Reference Script
Full build script available in build.sh. Recommended slides to read for understanding core design techniques:
- Slide 1 (title) — Circle image placeholder, neon lime CTA button, and left vertical decorative bar
- Slide 2 (services) — Dark gray multi-layer card arrangement and hierarchy construction
- Slide 4 (portfolio) — Application of circle elements in content display
No need to read all — skim 2-3 representative slides.
Cosmic Neon — Sci-Fi Time Travel
Style Overview
A futuristic sci-fi design featuring dual neon glow orbs (purple and cyan) on a near-black canvas with star decorations. Creates a mysterious cosmic atmosphere perfect for science and technology presentations.
- Scenario: Science talks, futuristic topics, physics presentations, cosmic themes
- Mood: Sci-fi, mysterious, futuristic, neon
- Tone: Near-black with purple and cyan neon
Color Palette
| Name | Hex | Usage |
|---|---|---|
| Background | #050510 | Near-black deep space |
| Glow Purple | #8A2BE2 | Primary neon glow effect |
| Glow Cyan | #00FFFF | Secondary neon glow effect |
| Card BG | #111122 | Dark indigo for card backgrounds |
| Primary text | #FFFFFF | White for headings |
| Secondary text | #AAAAAA / #CCCCCC | Gray variations for body text |
| Accent text | #00FFFF | Cyan for highlights |
Typography
| Element | Font |
|---|---|
| Title (English) | Montserrat |
| Title (Chinese) | Source Han Sans (思源黑体) |
| Body | Source Han Sans |
Design Techniques
- Dual neon glow orbs (purple + cyan) as main decorative elements
- Star decorations with varying opacity for depth
- Donut ring accent element for cosmic feel
- Neon-highlighted card backgrounds for content sections
- Large data typography for evidence slides
- Generous line spacing for readability on dark backgrounds
Page Structure (5 slides)
| Slide | Type | Elements | Description |
|---|---|---|---|
| 1 | hero | 25 | Title with dual neon glow orbs |
| 2 | statement | 25 | Centered quote with shifted glow positions |
| 3 | pillars | 25 | 3-column layout with neon card backgrounds |
| 4 | evidence | 25 | Large data number + description with neon accents |
| 5 | cta | 25 | Closing with neon accent decoration |
Reference Script
Complete build script available in build.sh.
Recommended slides to read for understanding core design techniques:
- Slide 1 (hero) — dual glow orb composition with stars
- Slide 3 (pillars) — neon card backgrounds with content hierarchy
No need to read all — skim 2-3 representative slides.