AionUi morph-ppt

Generate Morph-animated PPTs with officecli

install
source · Clone the upstream repo
git clone https://github.com/iOfficeAI/AionUi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/iOfficeAI/AionUi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/src/process/resources/skills/morph-ppt" ~/.claude/skills/iofficeai-aionui-morph-ppt && rm -rf "$T"
manifest: src/process/resources/skills/morph-ppt/SKILL.md
source content

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 grows

Three 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 calling
officecli set '/slide[N]' --prop transition=morph
, paths like
/slide[N]/!!my-shape
return '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:

# 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
:

PairSlide A (start)Slide B (end)Visual narrative purpose
1→2Ring centered, title appearsRing shifts right, subtitle revealedAttention → context
2→3Feature box largeFeature box small, metric card growsZoom out → detail
3→4Metric card exits (ghost), new actor entersActor repositionsSection 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}
      or
      name=!!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=36cm
        
        Rule: 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-box
    introduced 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_check
    tool 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-title
    is 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 --json

Confirm 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.md
      for 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 typeHow to achieve it
Simple moveSame shape on slide A and B, same size, different
x
/
y
— morph interpolates position
Scale transformSame shape on slide A and B, different
width
/
height
— morph interpolates size and position
Move + scaleDifferent
x
,
y
,
width
,
height
simultaneously — morph handles all dimensions at once
Color shiftSame 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 calling
    officecli set '/slide[N]' --prop transition=morph
    , paths like
    /slide[N]/!!my-shape
    return '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:

    # 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 syntax
  • reference/styles/<name>/
    — Visual style examples (optional inspiration, browse by use case in
    styles/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 outline

4B. 截图目视验证(必须执行)

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=morph
    
  2. 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=36cm
    
  3. Visual issues:

    # Open HTML preview to debug layout
    officecli view <file>.pptx html
    

Note:

!!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 Principle
  • reference/pptx-design.md
    — Design principles (Canvas, Fonts, Colors, Scene Actors, Page Types)
  • reference/officecli-pptx-min.md
    — Tool syntax
  • reference/styles/INDEX.md
    — Visual style examples organized by use case

Adjustments After Creation

When the user requests changes after the deck is built:

RequestCommand
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. Use
officecli get deck.pptx '/slide[N]' --depth 1
to 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.