| 1 | # Page Transitions & Per-Element Animations |
| 2 | |
| 3 | Execution contract for generated-PPTX **page transitions** and **per-element |
| 4 | object animations**, including deterministic Morph object pairing. This file |
| 5 | owns defaults, sidecar semantics, anchor selection, validation, and package |
| 6 | read-back. |
| 7 | |
| 8 | ## Capability Menu — Open Here |
| 9 | |
| 10 | Motion here is several separate capabilities, not one dial. Two of them are |
| 11 | decided **upstream, while pages are still being authored** — read this menu |
| 12 | before the page plan is frozen, not only when a deck is already exported. |
| 13 | |
| 14 | | What the deck needs | Reach for | Decided at | |
| 15 | |---|---|---| |
| 16 | | A generic deck-wide entrance build | `-a auto`; with the default `after-previous` Start mode, groups use fixed `--animation-stagger` timing rather than narration cues | Post-processing; §2, §4 | |
| 17 | | Explicit object lifecycle choreography | An `animations.json` sidecar for selected enter/emphasize/move/exit/static duties, order, Start mode, and timing | Post-processing; §2, §4, [`customize-animations`](../workflows/stages/customize-animations.md) | |
| 18 | | Object reveals semantically synchronized to recorded narration | Narration-cue sync derives `narration_animations.json` from canonical `animations.json`, page-local SRT, and `narration_timing.json`; `-a auto` alone does not provide this mapping | Audio stage; [`generate-audio`](../workflows/stages/generate-audio.md) | |
| 19 | | A continuous action — slide-in, flip, camera push-in, progressive reveal, camera pan | **Morph: author the action as two static pages, then select Morph and add explicit pairs when identity must be deterministic.** There is no keyframe timeline anywhere in this pipeline; the difference between two ordinary editable slides *is* the animation | **Page authoring (Step 6), then motion post-processing** — §2.1, §3.1 | |
| 20 | | A static full-bleed page that should stop looking frozen | Consider slow `path_*` motion on a visually subordinate image or atmospheric layer; §4.1 gives one starting recipe | Post-processing; §4.1 | |
| 21 | | Carousel, counting numerals, parallax depth, click-to-reveal flip card | Four recurring recipes assembled from the mechanisms above | §4.2 — the carousel and odometer both need paired pages | |
| 22 | | Kiosk or unattended playback | `--auto-advance <seconds>`, optionally with `-t none` | Export; §3 | |
| 23 | | A transition or object animation needs an audible cue | Optional `transition.sound` or object-animation `sound`; select it only after the visual motion solution is complete, then sync the chosen global-library ids into the project. For direct narrated MP4 delivery, [`generate-audio`](../workflows/stages/generate-audio.md) selects either the verified native-export mix or explicit real-time slideshow capture; never combine them | Post-motion; §2.2 | |
| 24 | | Nothing should move | `-t none`, and leave per-element animation at its default `none` | Export; §1 | |
| 25 | |
| 26 | **Hard rule — Morph geometry is an authoring decision; pairing is a later |
| 27 | execution decision**: export cannot invent the two visible endpoint states. |
| 28 | Author both consecutive pages while `svg_output/` is still being built. For |
| 29 | deterministic identity, expose each endpoint as a compatible direct-root group |
| 30 | and declare the pair in `animations.json` (§2.1); the source and destination ids |
| 31 | and geometry may differ. `-t morph` without explicit pairs leaves matching to |
| 32 | PowerPoint's heuristic and is not proof that the intended objects will tween. |
| 33 | |
| 34 | **Reference — not a constraint**: per-element animation stays off by default |
| 35 | (§1). Auto-firing element builds on every page are an unsolicited "AI deck" |
| 36 | tell; each capability above earns its place per page, not per deck. |
| 37 | |
| 38 | --- |
| 39 | |
| 40 | ## 1. Defaults |
| 41 | |
| 42 | | Layer | Default | Why | |
| 43 | |---|---|---| |
| 44 | | Page transition | CLI: `fade`, 0.4s | Calm baseline that suits most decks; the public Python builder retains its legacy 0.5s default | |
| 45 | | Per-element animation | **`none` (off)** | A page appears as a whole. Auto-firing element builds are an unsolicited "AI deck" tell, so object animation is opt-in. Turn on the content-aware canonical entrance policy with `-a auto`, or select one PowerPoint-native `entrance_*`, `emphasis_*`, `path_*`, or `exit_*` key explicitly | |
| 46 | | Sound effects | **`none` (off)** | No global sound is copied and no `<project>/sounds/` directory is created unless a resolved transition or object-animation cue actually selects one | |
| 47 | |
| 48 | To regenerate a deck with different settings, rerun the final checker when its current matching report is absent or stale, then rerun `svg_to_pptx.py` against the same `svg_output/`; the content-generation LLM need not rerun unless authored SVG requires repair. `-s final` is reserved for diagnostic comparison and is not a supported release source. To turn per-element animation on for the whole deck, pass `-a auto`. |
| 49 | |
| 50 | --- |
| 51 | |
| 52 | ## 2. Custom Object-Level Animation |
| 53 | |
| 54 | Per-element animation is off by default. To enable generic entrance reveals |
| 55 | deck-wide, pass `-a auto` at export (no config needed). When a deck instead |
| 56 | needs a specific object lifecycle—for example enter, move, emphasize, then |
| 57 | exit—use the optional `animations.json` sidecar. The SVG remains the visual |
| 58 | source; the custom stage may rewrite its grouping hierarchy, ids, and bounds to |
| 59 | create better semantic anchors without changing visible output, while the |
| 60 | sidecar controls PPTX animation behavior. |
| 61 | |
| 62 | Run the [`customize-animations`](../workflows/stages/customize-animations.md) |
| 63 | post-processing stage when the project already carries `animations.json`, when |
| 64 | the user explicitly asks to tune animation order/effects/timing/object-level |
| 65 | reveals, or when the effective Custom Animations outcome in |
| 66 | `design_spec.md §I` is enabled. A §IX `Motion suggestion` remains Strategist |
| 67 | advice and informs an active pass, but never triggers the stage alone. |
| 68 | |
| 69 | **Hard rule — semantic anchors before object-targeted sidecar entries**: when |
| 70 | object animation is in scope, derive motion units and their lifecycle duties |
| 71 | from page meaning and narration, then regroup coarse/fragmented Slide-local |
| 72 | content without changing its appearance. Only post-regroup top-level ids are |
| 73 | valid object targets. |
| 74 | |
| 75 | ```bash |
| 76 | # Inspect the real anchors after the semantic regrouping pass |
| 77 | python3 skills/ppt-master/scripts/animation_config.py list-groups <project> |
| 78 | |
| 79 | # Build a neutral editable scaffold from the post-regroup anchors when useful |
| 80 | python3 skills/ppt-master/scripts/animation_config.py scaffold <project> |
| 81 | |
| 82 | # Validate references before export |
| 83 | python3 skills/ppt-master/scripts/animation_config.py validate <project> |
| 84 | |
| 85 | # Export reads <project>/animations.json automatically when present |
| 86 | python3 skills/ppt-master/scripts/svg_to_pptx.py <project> |
| 87 | ``` |
| 88 | |
| 89 | The scaffold keeps `defaults.animation.effect: none` and may list untouched |
| 90 | groups as empty `{}` placeholders; creating it does not opt the deck into |
| 91 | object motion. Populate only adopted motion units. |
| 92 | |
| 93 | Sparse sidecar excerpt (unlisted slides inherit resolved defaults): |
| 94 | |
| 95 | ```json |
| 96 | { |
| 97 | "version": 1, |
| 98 | "slides": { |
| 99 | "03_threshold": { |
| 100 | "groups": { |
| 101 | "risk-marker": { |
| 102 | "effects": [ |
| 103 | { "effect": "entrance_fade", "order": 1, "duration": 0.25 }, |
| 104 | { "effect": "path_right", "effect_options": { "relative": true }, "order": 2, "duration": 0.7 }, |
| 105 | { "effect": "emphasis_teeter", "order": 3, "duration": 0.45 }, |
| 106 | { "effect": "exit_fade", "order": 4, "duration": 0.3 } |
| 107 | ] |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | ``` |
| 114 | |
| 115 | Rules: |
| 116 | |
| 117 | - `slides` keys match SVG stems (`03_market.svg` → `03_market`). |
| 118 | - `groups` keys match top-level `<g id="...">` anchors. |
| 119 | - A populated group block chooses exactly one representation: the |
| 120 | backward-compatible single-effect object, or |
| 121 | `{ "effects": [row, ...] }`. `effects` is non-empty and mutually exclusive |
| 122 | with every legacy single-effect field; each row explicitly names `effect`. |
| 123 | An untouched scaffold `{}` remains a neutral placeholder. |
| 124 | - `effect: none` in the legacy form removes that group from the object-animation |
| 125 | sequence and is useful for overriding inherited generic animation. |
| 126 | - `effects[]` permits the same PowerPoint shape to carry several Animation Pane |
| 127 | rows. `order` sorts ordinary rows across the slide; ties retain SVG group |
| 128 | order and then array order. `trigger_shape` rows keep that relative ordering |
| 129 | in separate interactive sequences rather than interleaving with the main |
| 130 | sequence. Ordering never changes slide layering. |
| 131 | - `delay` is seconds added to that row's resolved Start. |
| 132 | - `trigger` may be set per legacy row or `effects[]` row; otherwise it inherits |
| 133 | the resolved slide Start mode. |
| 134 | - `trigger_shape` is a row-specific reference to another unique, triggerable |
| 135 | top-level group. It maps to PowerPoint **Trigger → On Click of**, makes only |
| 136 | that row interactive, and uses `delay` as `TriggerDelayTime`. It implies |
| 137 | `on-click`; an explicit row `trigger` may accompany it only when also |
| 138 | `on-click`. |
| 139 | - `duration` overrides the per-row schedule duration. `entrance_appear` |
| 140 | remains a 1ms visibility flip, and instantaneous native emphasis presets |
| 141 | retain their PowerPoint-authored duration; the configured value still spaces |
| 142 | the next `after-previous` row. |
| 143 | - `effect_options` requires an explicit canonical `effect` in the same legacy |
| 144 | block or `effects[]` row and accepts only parameters PowerPoint exposes for |
| 145 | that effect: |
| 146 | |
| 147 | | Option | Applies to | |
| 148 | |---|---| |
| 149 | | `direction` | Directional Fly/Crawl/Wipe/Peek/Strips/Split/Stretch/Zoom and related entrance/exit effects | |
| 150 | | `amount` | Wheel spokes (`1`, `2`, `3`, `4`, `8`), emphasis Spin degrees, or Transparency ratio | |
| 151 | | `color` | Color-capable emphasis effects; `#RRGGBB` or `theme:<scheme-color>` | |
| 152 | | `font_name` | Change Font; required for `emphasis_change_font`; one installed PowerPoint face, not a CSS list | |
| 153 | | `size` | Grow/Shrink | |
| 154 | | `relative` | Motion paths (`true` = shape-relative, `false` = fixed slide path) | |
| 155 | - Any animation block or effect row may set `repeat_count` or `repeat_duration` |
| 156 | (mutually exclusive), `auto_reverse`, `rewind`, `accelerate`, `decelerate`, |
| 157 | `bounce_end`, `restart`, `after_effect`, and `sound`. Ratios are `0..1`; |
| 158 | `bounce_end` requires an interpolated behavior and cannot combine with |
| 159 | `decelerate`; `restart` is `always`, `when-not-active`, or `never`; |
| 160 | `after_effect` is `none`, `dim` (with `color`), `hide`, or |
| 161 | `hide-on-next-click`; `sound` is a project-relative or absolute `.m4a`, |
| 162 | `.mp3`, or `.wav` path. New generated configurations use a project-relative |
| 163 | path. A bundled library choice first follows §2.2 and resolves to a |
| 164 | project-local `.wav`; never point new output at `templates/sounds/`. |
| 165 | - `Speed` and smooth start/end are not duplicate sidecar fields: they are |
| 166 | derived from `duration` and `accelerate`/`decelerate`. |
| 167 | - This is the complete parameter surface for the generated top-level-group |
| 168 | target model, including multiple ordered effects on one group. PowerPoint |
| 169 | paragraph/text-range build fields are intentionally absent because grouped |
| 170 | SVG content is not emitted as paragraph builds; media play/pause/stop |
| 171 | commands remain in the audio/video workflows. |
| 172 | - Run `python3 skills/ppt-master/scripts/pptx_animations.py --describe |
| 173 | <canonical_effect>` for that effect's exact option values and full parameter |
| 174 | contract. |
| 175 | - `--animation none` overrides the sidecar and disables all per-element animation. |
| 176 | - An explicit sidecar group may override the legacy chrome-name heuristic, but it cannot override `data-pptx-layer` or an explicit static role/placeholder marker. |
| 177 | - Unknown effects, modes, or triggers and invalid numeric/order fields fail validation; no fallback effect is substituted. |
| 178 | |
| 179 | **Inheritance**: the sidecar and its `defaults` block are optional. Unlisted |
| 180 | slides and omitted slide fields inherit `defaults.transition` / |
| 181 | `defaults.animation`, then CLI/exporter resolution. Explicit CLI flags override |
| 182 | the corresponding sidecar default/slide fields; explicit group overrides remain |
| 183 | unless `-a none` hard-disables all object motion. Groups inherit the resolved |
| 184 | slide duration, Start mode, timing modifiers, after-effect, and sound into each |
| 185 | legacy or `effects[]` row. `effect_options` remains coupled to an explicit |
| 186 | effect; `trigger_shape` is never inherited; omitted `order`/`delay` use |
| 187 | exporter defaults. |
| 188 | |
| 189 | ### 2.1 Deterministic Morph Object Pairing |
| 190 | |
| 191 | When one semantic object continues across two adjacent slides, the destination |
| 192 | slide may declare explicit forced-Morph pairs. This is separate from `groups`: |
| 193 | Morph owns cross-slide identity, while `groups` owns Animation Pane rows. |
| 194 | The generated names follow Microsoft's |
| 195 | [forced object-matching convention](https://support.microsoft.com/en-us/powerpoint/morph-transition-tips-and-tricks). |
| 196 | |
| 197 | ```json |
| 198 | { |
| 199 | "version": 1, |
| 200 | "slides": { |
| 201 | "02_detail": { |
| 202 | "transition": { |
| 203 | "effect": "morph", |
| 204 | "effect_options": { "morph_by": "object" }, |
| 205 | "duration": 0.8 |
| 206 | }, |
| 207 | "morph": { |
| 208 | "from": "01_overview", |
| 209 | "pairs": { |
| 210 | "hero-image": { |
| 211 | "from": "hero-overview", |
| 212 | "to": "hero-detail" |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | } |
| 217 | } |
| 218 | } |
| 219 | ``` |
| 220 | |
| 221 | - `morph` belongs to the destination slide. `morph.from` must be the |
| 222 | immediately preceding SVG stem in export order. |
| 223 | - `animation_config.py scaffold` never guesses cross-slide identity. Add pairs |
| 224 | from the semantic motion plan after inspecting the final direct-root ids. |
| 225 | - Each `pairs` key is a stable identity; its `from` and `to` values are unique |
| 226 | direct-root `<g id>` values on the source and destination slides. Supply the |
| 227 | key without `!!`; export writes the PowerPoint Selection Pane name |
| 228 | `!!<key>` on both objects. |
| 229 | - A destination with explicit pairs must explicitly set `effect: morph`. |
| 230 | `morph_by` may be omitted for its `object` default or set to `object`; |
| 231 | `word`/`character` are rejected. A CLI transition override that changes the |
| 232 | resolved effect fails export. |
| 233 | - A middle slide may continue the same object into another Morph transition, |
| 234 | but the same group must retain the same key. One key cannot name two objects |
| 235 | on one slide, and one object cannot carry two keys. Every `!!` key shared by |
| 236 | two adjacent Morph pages must be declared in that destination's `pairs`; |
| 237 | undeclared forced matches are rejected. |
| 238 | - Explicit pairing can coexist with in-slide object animation and remains |
| 239 | active when `-a none` disables Animation Pane rows. `--no-animations` |
| 240 | disables the sidecar and all page/object motion. |
| 241 | - The exporter resolves both group ids to final Slide-local PowerPoint shapes, |
| 242 | writes names only after Master/Layout processing, then reopens the package |
| 243 | and verifies adjacency, Morph by object, one name per slide, and matching |
| 244 | OOXML object types. Missing, structural, moved, ambiguous, or mismatched |
| 245 | targets fail instead of falling back to automatic Morph matching. |
| 246 | |
| 247 | ### 2.2 On-Demand Sound Selection |
| 248 | |
| 249 | **Hard rule — select after motion, materialize after selection**: sound is not a |
| 250 | Strategist resource and does not belong in `design_spec.md`, `spec_lock.md`, or |
| 251 | pre-SVG resource preparation. First complete the SVG roster and resolve the |
| 252 | transition/object-motion solution. Only when a specific cue is then selected, |
| 253 | copy its global-library file into the project and reference that local copy. |
| 254 | |
| 255 | | Source | Action | |
| 256 | |---|---| |
| 257 | | Bundled CC0 library | Read the complete objective [`sound-vocabulary.md`](../templates/sounds/sound-vocabulary.md), select one exact id from the resolved auditory job, sync only that selection, then use the corresponding `sounds/<namespace>/<file>.wav` path | |
| 258 | | User-provided audio already inside the project | Reference its existing project-relative `.m4a`, `.mp3`, or `.wav` path for object animation; a transition sound uses `.wav` | |
| 259 | | External absolute file | The low-level object-animation path remains compatible, but new generated projects should copy or sync the intended file into the project and use a relative path | |
| 260 | | No concrete auditory cue job | Keep `sound` omitted; do not create `<project>/sounds/` and do not copy the library | |
| 261 | |
| 262 | ```bash |
| 263 | # Optional exact filtering only after the complete vocabulary is in context |
| 264 | python3 skills/ppt-master/scripts/sound_sync.py list --query <term> |
| 265 | |
| 266 | # Materialize only the chosen ids |
| 267 | python3 skills/ppt-master/scripts/sound_sync.py \ |
| 268 | <project_path> <namespace>/<sound_id> [<namespace>/<sound_id> ...] |
| 269 | ``` |
| 270 | |
| 271 | `sound_sync.py` is the only bundled-library materialization path. Stable ids |
| 272 | include their namespace; copied files remain under |
| 273 | `<project_path>/sounds/<namespace>/`. The exporter never reads the global |
| 274 | `templates/sounds/` library directly, and sidecars store paths rather than |
| 275 | library ids. |
| 276 | |
| 277 | **Default — silence (may override for a specific cue)**: do not add sound to |
| 278 | demonstrate capability or spread it across a deck for coverage. A sound may |
| 279 | support a named transition, reveal, confirmation, warning, or drawn/moving |
| 280 | gesture after the corresponding visual behavior is already selected. |
| 281 | |
| 282 | **Hard rule — PPTX and MP4 are separate sound deliveries**: sound fields and |
| 283 | package read-back prove the editable PPTX contains the intended native cue; |
| 284 | they do not prove PowerPoint's video encoder placed it in the MP4 audio track. |
| 285 | For direct narrated MP4 delivery with resolved cues, follow `generate-audio` |
| 286 | and choose exactly one branch: mix from the final narrated trace plus final |
| 287 | PPTX after native encoding, or explicitly capture the live PowerPoint Slide |
| 288 | Show with system audio. Never mix the capture again. Keep post-production gain |
| 289 | and limiter settings out of `animations.json`. |
| 290 | |
| 291 | --- |
| 292 | |
| 293 | ## 3. Page Transitions |
| 294 | |
| 295 | **Reference — not a constraint**: choose a transition from the relationship |
| 296 | between adjacent pages, not from gallery coverage. Run this playbook before |
| 297 | selecting a canonical key: |
| 298 | |
| 299 | | Pass | Decision | |
| 300 | |---|---| |
| 301 | | Relate | Decide whether the destination continues the same object or space, advances in a meaningful direction, opens a new section, or intentionally breaks continuity. | |
| 302 | | Diagnose | Name the transition's job: neutral continuity, immediate cut, directional progress, object/state continuity, spatial movement, or a deliberate thematic beat. | |
| 303 | | Select | Use the smallest family that performs that job; keep `fade` when no stronger relationship exists. | |
| 304 | | Coordinate | Align direction, duration, and recurrence with reading order, narration, and the deck's established motion language. | |
| 305 | | Stop | Keep `fade` or `none` when another effect adds no meaning; never vary transitions for catalog coverage. | |
| 306 | |
| 307 | | Page relationship | Candidate family | |
| 308 | |---|---| |
| 309 | | Ordinary continuation within one section | `fade` | |
| 310 | | Immediate change with no continuity to preserve | `none` or `cut` | |
| 311 | | Directional steps, timeline, or layer progression | `push` / `wipe`; use `cover` / `uncover` when an overlay relationship is visible | |
| 312 | | The same semantic object or scene changes across adjacent pages | `morph`; use §2.1 pairs when identity must be deterministic | |
| 313 | | Section opening, key reveal, or marked state boundary | Selective `split` / `reveal` / `shape` / `flash` / `random_bars` | |
| 314 | | A repeated collection advances through one spatial frame | `pan` / `conveyor` / `ferris_wheel`; use the §4.2 Morph carousel when individual cards need deterministic identity | |
| 315 | | The viewpoint travels around or through a continuous space | `rotate` / `window` / `orbit` / `fly_through` | |
| 316 | | The narrative or theme supports a stage, paper, or physical-page metaphor | Selective `fall_over` / `drape` / `curtains` / `wind` / `prestige` / `peel_off` / `page_curl` / `airplane` / `origami` / `doors` | |
| 317 | | A disruptive beat represents breakage, collapse, or dispersal | Selective `fracture` / `crush` / `dissolve` / `vortex` / `shred` | |
| 318 | | A marked reveal benefits from a geometric, timed, or textured pattern | Selective `checkerboard` / `blinds` / `clock` / `ripple` / `honeycomb` / `glitter` / `comb` | |
| 319 | | A card, panel, gallery, or viewpoint visibly turns or changes face | Selective `switch` / `flip` / `gallery` / `cube` / `box` / `zoom` | |
| 320 | | Unpredictability is itself the requested behavior | `random`; never use it merely to create variety | |
| 321 | |
| 322 | ```bash |
| 323 | # Pick a different effect |
| 324 | python3 skills/ppt-master/scripts/svg_to_pptx.py <project> -t push --transition-duration 0.6 |
| 325 | |
| 326 | # Remove the visual transition |
| 327 | python3 skills/ppt-master/scripts/svg_to_pptx.py <project> -t none |
| 328 | |
| 329 | # Auto-advance every 5 seconds (kiosk-style playback) |
| 330 | python3 skills/ppt-master/scripts/svg_to_pptx.py <project> --auto-advance 5 |
| 331 | |
| 332 | # Auto-advance with no visual transition |
| 333 | python3 skills/ppt-master/scripts/svg_to_pptx.py <project> -t none --auto-advance 5 |
| 334 | ``` |
| 335 | |
| 336 | The native registry covers PowerPoint's complete Subtle, Exciting, and Dynamic |
| 337 | Content gallery: 48 canonical keys. New selection, sidecars, plans, conversion |
| 338 | traces, and writers use only those keys. Run `pptx_animations.py --list` for |
| 339 | the categorized identifiers. |
| 340 | |
| 341 | Eight old low-level names remain accepted only as compatibility inputs. They |
| 342 | desugar to a native key plus native `effect_options`: for example, `diamond` |
| 343 | becomes `shape` with `shape: diamond`, and `wedge` becomes `clock` with |
| 344 | `style: wedge`. They are never selected for new output. |
| 345 | |
| 346 | Effects expose their real PowerPoint Effect Options through |
| 347 | `transition.effect_options`. Common examples include Push/Wipe direction, |
| 348 | Morph by object/word/character, Reveal through black, Shape geometry, Page |
| 349 | Curl direction/pages, Glitter pattern/direction, and Fly Through bounce. Run |
| 350 | `pptx_animations.py --describe-transition <effect>` for the exact |
| 351 | effect-specific contract; unknown or inapplicable options fail validation. |
| 352 | `none` removes the visual effect. Effects that require newer Office namespaces |
| 353 | carry a real PowerPoint effect in `mc:Choice` and a `fade` fallback for older |
| 354 | consumers; validation requires the requested primary effect and never accepts |
| 355 | the fallback as a silent substitute. |
| 356 | |
| 357 | An optional `transition.sound` adds one `.wav` cue to the transition. It is a |
| 358 | sidecar field rather than a CLI flag. Bundled choices must first be synced by |
| 359 | §2.2 and referenced through their project-relative path. `effect: none` may |
| 360 | still carry a transition sound and/or automatic advance without restoring a |
| 361 | visual effect. A slide-level `transition.sound: null` explicitly clears an |
| 362 | inherited default transition sound for that page. |
| 363 | |
| 364 | Flags: |
| 365 | |
| 366 | - `-t/--transition` — native effect name, compatibility input, or `none` for no visual transition. Default: `fade`. `none` does not remove an explicitly configured automatic advance. |
| 367 | - `--transition-duration` — seconds, default `0.4`. |
| 368 | - `--auto-advance` — seconds; click remains enabled, so the slide advances on click or when the timer expires. Omit for presenter-controlled advance. |
| 369 | |
| 370 | **Hard rule — no silent downgrade**: an unknown transition effect, unsupported Effect Option, or invalid/non-finite duration fails export. It is never replaced by `fade`. Recorded narration keeps the resolved visual transition; `-t none --recorded-narration ...` writes narration-driven advance timing without restoring a visual effect. |
| 371 | |
| 372 | ### 3.1 Morph — author an action as the difference between two pages |
| 373 | |
| 374 | Morph tweens objects it can match across consecutive slides. That makes it a general mechanism, not just a transition: **any continuous action can be authored as two static pages plus a Morph transition**, with no keyframe timeline anywhere. Duplicate the page, change one property on one object, and PowerPoint interpolates the rest. Use §2.1 explicit pairs when the match must be deterministic. |
| 375 | |
| 376 | | Change between the two pages | Reads as | |
| 377 | |---|---| |
| 378 | | Object sits off-canvas, then on-canvas | Slide-in, drawer pull, card extending | |
| 379 | | Object rotates | Flip, turn, hinge | |
| 380 | | Image container scales up | Camera push-in | |
| 381 | | Scrim opacity drops, or a cut contour grows | Progressive reveal | |
| 382 | | Same wide image at two `x` offsets | Camera pan (see image-layout-patterns `#C2-01`) | |
| 383 | |
| 384 | Chain three or more pages to build a sequence — extend, hold, retract — where each page is still an ordinary editable slide. |
| 385 | |
| 386 | **Hard rule — matching needs compatible object identity, not identical SVG |
| 387 | geometry**: for generated decks, prefer §2.1 deterministic pairs. The source |
| 388 | and destination direct-root group ids may differ, and position, size, crop, or |
| 389 | other visible state is expected to change; both endpoints must still resolve to |
| 390 | one compatible top-level PowerPoint object kind. Automatic Morph without pairs |
| 391 | is heuristic and may cross-fade instead of tweening. |
| 392 | |
| 393 | **Give text somewhere to come from.** Morph tweens objects present on both pages; text that only exists on the second page can only fade in. The standard fix is to place the *next* page's copy on the current page just outside the canvas (below), and the *previous* page's copy just outside the opposite edge (above). Each block then slides through the frame instead of blinking, and the deck reads as one continuous surface being scrolled. A wholly off-canvas endpoint must be one direct-root `<g id>` with valid `data-pptx-bounds` and `data-pptx-morph-staging="true"`; when Morph remains enabled, pair it explicitly under §2.1. The marker only declares an intentional invisible endpoint; it cannot excuse a partially clipped group or text carrier. |
| 394 | |
| 395 | **When Morph refuses to match**: PowerPoint pairs compatible object kinds; a |
| 396 | shape and a picture will cross-fade instead of tweening. For generated pages, |
| 397 | declare the identity through the destination slide's `morph` block (§2.1). |
| 398 | The exporter writes the shared `!!<key>` name after structure processing and |
| 399 | reads the package back. Do not author `data-pptx-shape-name` for this purpose; |
| 400 | that attribute remains importer metadata for mirror/preserve packages |
| 401 | ([`svg-effects.md`](./svg-effects.md) §6.6). |
| 402 | |
| 403 | **Not supported — Slide Zoom / Summary Zoom.** Click-to-jump navigation built on PowerPoint's Zoom objects (the "click a portrait, zoom into that section" pattern) has no exporter path. Build click-driven navigation with `trigger_shape` on ordinary object animations instead, or with plain hyperlinks. |
| 404 | |
| 405 | **No 3D**: perspective rotation, extrusion, and shear are outside the SVG contract — `skewX` / `skewY` and shear matrices fail closed ([`svg-effects.md`](./svg-effects.md) §6.8). Build the same impression with 2D means — offset, scale, overlap, and per-facet lightness — rather than attempting a 3D tilt. |
| 406 | |
| 407 | --- |
| 408 | |
| 409 | ## 4. Per-Element Animations |
| 410 | |
| 411 | Off by default — enable deck-wide with `-a auto` (or another effect). Once enabled, three Start modes are available — these mirror PowerPoint's animation-pane "Start" dropdown: |
| 412 | |
| 413 | - **`on-click`** — each click reveals the next group. Use only for a controlled semantic reveal; live delivery alone is insufficient. Forbidden with `--recorded-narration`. |
| 414 | - **`with-previous`** — groups start together as one coordinated beat. Stagger ignored. |
| 415 | - **`after-previous`** (default) — click-free cascade on slide entry with `--animation-stagger` spacing. Use when controlled reveals are unnecessary. |
| 416 | |
| 417 | **Default — coherent Start rhythm (may override when a semantic beat needs |
| 418 | different control)**: Keep one dominant deck rhythm and normally one mode per |
| 419 | slide. Mix only for a distinct simultaneous or presenter-controlled beat. |
| 420 | |
| 421 | Enable with `-a auto`, select a canonical effect with |
| 422 | `--animation entrance_fade`, and choose Start behavior with |
| 423 | `--animation-trigger on-click|with-previous|after-previous`. |
| 424 | |
| 425 | PowerPoint's separate **Trigger → On Click of** behavior uses row-specific |
| 426 | `trigger_shape`. It links that row to another top-level group while unlinked |
| 427 | rows keep the slide Start mode; it is not a fourth deck-wide Start mode. |
| 428 | |
| 429 | **Mandatory — lifecycle before effect selection**: start from `static`, then |
| 430 | classify semantic `initial → action → end` before choosing an effect. Generic |
| 431 | staged reveals normally use `enter`; narrower communication jobs select their |
| 432 | matching lifecycle instead. |
| 433 | |
| 434 | | Duty | State contract | Use when | Effect family | |
| 435 | |---|---|---|---| |
| 436 | | `static` | present → hold as reference → present | Motion adds no clarity or intended feeling | No row; legacy `effect: none` only suppresses inheritance | |
| 437 | | `enter` | absent → introduce → present | Information should be withheld, ordered, or revealed with narration | `entrance_*`; modes only for generic reveal | |
| 438 | | `emphasize` | present → redirect attention → present/altered | An already visible object must regain attention or show a local change; never substitute for its first reveal | Explicit `emphasis_*` | |
| 439 | | `move` | state/position A → progress → state/position B | The trajectory carries spatial or causal meaning, or §4.1 adopts subordinate ambient motion; use Morph for cross-page continuity | Explicit `path_*`, or endpoint pages + Morph | |
| 440 | | `exit` | present → retire → absent | The same slide must remove, replace, or make room for content; an ordinary page change needs no object exit | Explicit `exit_*` | |
| 441 | |
| 442 | **Default — restrained entrance-led choreography (may override for content, |
| 443 | tone, or the request)**: Use entrances for ordinary builds. Add emphasis or |
| 444 | exit sparingly, only for a real duty and fitting effect. Multiple `effects[]` |
| 445 | rows require multiple duties. |
| 446 | |
| 447 | The registry exposes two layers: |
| 448 | |
| 449 | - **203 PowerPoint-native object presets**: 53 `entrance_*` presets, 33 |
| 450 | `emphasis_*` effects, 64 `path_*` motion paths, and 53 `exit_*` effects. |
| 451 | Examples include `entrance_bounce`, `emphasis_spin`, `path_circle`, and |
| 452 | `exit_faded_zoom`. Each native key carries the complete PowerPoint-authored |
| 453 | behavior tree, not a generic filter approximation. |
| 454 | - **29 legacy compatibility inputs**, listed by `--list`; new output never |
| 455 | selects them. |
| 456 | |
| 457 | Run the registry command for the exact categorized key list: |
| 458 | |
| 459 | ```bash |
| 460 | python3 skills/ppt-master/scripts/pptx_animations.py --list |
| 461 | ``` |
| 462 | |
| 463 | Compatibility names normalize before selection and writing: for example, |
| 464 | `fade` resolves to `entrance_fade`; every old Fly direction name resolves to |
| 465 | `entrance_fly`; every old Wipe direction name resolves to `entrance_wipe`; and |
| 466 | `cut` resolves to `entrance_appear` because current PowerPoint has no separate |
| 467 | Cut object effect. Directional aliases preserve their old direction through |
| 468 | `effect_options`; legacy `wheel` maps to `entrance_wheel` with four spokes. |
| 469 | These names are accepted only as compatibility inputs. |
| 470 | Automatic selection, new sidecars, conversion traces, and writers use |
| 471 | canonical keys. |
| 472 | |
| 473 | The native keys mirror the object-capable `MsoAnimEffect` surface. The four |
| 474 | media commands—play, pause, stop, and play from bookmark—are not object effects |
| 475 | for SVG groups and remain owned by the audio/video workflows. |
| 476 | |
| 477 | - `auto` handles generic `enter` duties only and maps semantic ids to canonical entrances: charts/tables/timelines use |
| 478 | `entrance_wipe`; cards/steps use `entrance_fly`; titles/takeaways use |
| 479 | `entrance_fade`; image-like ids cycle a richer pool; unmatched ids cycle |
| 480 | fade/wipe/fly/zoom. |
| 481 | - `mixed` (legacy mode name) handles generic `enter` duties only and is |
| 482 | deterministic. The first animated group on each |
| 483 | slide uses `entrance_fade`; later groups cycle through a 16-effect canonical |
| 484 | PowerPoint entrance pool across the deck. The mode name remains compatible; |
| 485 | it no longer selects hand-authored compatibility rows. |
| 486 | - `random` handles generic `enter` duties only and samples from the same |
| 487 | canonical PowerPoint entrance pool. |
| 488 | Resolution is seeded from the effective deck input, so the same input |
| 489 | produces the same choices; `--conversion-trace` records every resolved effect |
| 490 | when diagnostics are enabled. |
| 491 | |
| 492 | `entrance_appear` is excluded from every variation pool because it has no |
| 493 | visible motion. `auto`, `mixed`, and `random` never satisfy an adopted |
| 494 | `emphasize`, `move`, or `exit` duty; those require explicit canonical effects. |
| 495 | |
| 496 | Flags: `-a/--animation` selects effect/mode; `--animation-trigger` selects Start; |
| 497 | `--animation-duration` and `--animation-stagger` control base timing; |
| 498 | `--animation-config` selects a sidecar; `--no-animations` disables page/object |
| 499 | motion but preserves narration audio and recorded advance timing. |
| 500 | |
| 501 | > Note: `--recorded-narration` rejects `on-click` and `trigger_shape`. Narration-cue sync uses `narration_animations.json` and blocks when only canonical `animations.json` exists. Narration-independent custom motion explicitly passes `--animation-config animations.json`, even when a derived sidecar also exists. With no sidecar, pass `--inherit-motion-from <base_postflight_report>`; explicit all-motion-off uses `--no-animations`. |
| 502 | |
| 503 | ### 4.1 Slow ambient motion — the page that breathes |
| 504 | |
| 505 | **Reference — not a constraint**: ambient motion can keep a static page from |
| 506 | feeling frozen when it remains visually subordinate to the message. A common |
| 507 | starting recipe is `path_left` or `path_right` on a background image, started |
| 508 | `with-previous` and paced much more slowly than a content reveal. The same |
| 509 | principle may suit another atmospheric or non-information-bearing layer. Choose |
| 510 | duration, distance, and moving-object count from the composition and delivery |
| 511 | context. |
| 512 | |
| 513 | Keep a full-bleed moving image covering the canvas at both endpoints; exposing |
| 514 | the slide beneath it is a visible failure. |
| 515 | |
| 516 | It pairs naturally with a fixed foreground: with image-layout-patterns `#M1-07`, the scrim and its cut contour stay locked while the world moves behind the cuts, which reads as looking through windows rather than as a sliding photo. The same logic applies to `#M1-10` and `#P1-09`. |
| 517 | |
| 518 | Motion remains subordinate: avoid competing ambient paths or movement that |
| 519 | reduces the readability of body copy or data. Multiple coordinated layers are |
| 520 | valid when they express one intentional depth or atmosphere relationship. |
| 521 | |
| 522 | ### 4.2 Recurring recipes |
| 523 | |
| 524 | Four combinations that recur constantly in authored decks. Each is built from |
| 525 | mechanisms already defined above — none needs a new capability. |
| 526 | |
| 527 | **Carousel** (Morph, §2.1 and §3.1) — hold a fixed row of card frames and rotate the *content* through them: on each page every image advances one position, so the card at centre changes while the frames stay put. Explicitly pair each moving content unit across adjacent pages; the fixed frames stay static and need no pair. Scales to any number of images with one page each. |
| 528 | |
| 529 | **Odometer / counting numerals** (morph or motion path) — build a vertical strip of digits 0–9 and show one through a fixed window formed by background-filled rectangles above and below ([`image-layout-patterns.md`](./image-layout-patterns.md) `#M1-08`). Shift the strip so the target digit lands in the window, then either morph between two pages or run a `path_up` motion on the strip. A small stagger, such as `0.1s`, can make digit columns settle in sequence; synchronized motion is also valid when it fits the intended rhythm. |
| 530 | |
| 531 | **Parallax depth** (morph) — move a background layer a *short* distance and a foreground layer a longer one between two pages. The differing travel is read as depth. Keep both layers' z-order identical on both pages; a layer that changes stacking between pages breaks the tween and the transition jumps. |
| 532 | |
| 533 | **Flip-card / click-to-reveal** (`trigger_shape`, §4) — pair a face group and a back group at the same position, give the face an exit and the back an entrance, and set the back's `trigger_shape` to the face's id. Clicking the face plays both. This is the supported route for click-driven interaction; PowerPoint's Zoom objects are not (§3.1). |
| 534 | |
| 535 | --- |
| 536 | |
| 537 | ## 5. Anchor Logic — Top-Level `<g id="...">` |
| 538 | |
| 539 | Per-element animations are anchored on **top-level `<g id="...">` content |
| 540 | groups** in the SVG (e.g. `<g id="cover-title">`, `<g id="card-1">`). IDs must |
| 541 | be unique within the page. A backward-compatible single-effect group produces |
| 542 | one Animation Pane row; `effects[]` may produce several ordered rows targeting |
| 543 | the same PowerPoint shape. Each row inherits the slide Start mode unless it |
| 544 | declares its own `trigger`. Nested implementation groups may remain anonymous |
| 545 | because the sidecar does not target them. |
| 546 | |
| 547 | **Hard rule — existing groups are not custom-animation intent**: the |
| 548 | pre-existing SVG hierarchy is implementation evidence, not an authoritative |
| 549 | motion plan. During the custom-animation stage, derive one group per logical |
| 550 | motion unit from claims, comparisons, sequence, causality, and narration beats; |
| 551 | split coarse wrappers and merge fragmented atoms when needed, then use |
| 552 | `list-groups` only after that rewrite. This is also the granularity PowerPoint |
| 553 | uses for group-select / group-move. Do not split or merge units to hit a target |
| 554 | count. |
| 555 | |
| 556 | **Chrome stays static.** `data-pptx-layer` and explicit static |
| 557 | role/placeholder markers are absolute. The legacy chrome-like ID heuristic |
| 558 | (background, header/footer, decor, watermark, page number, nav, logo, rule) |
| 559 | applies only to a top-level group that itself lacks `data-pptx-layer`, |
| 560 | `data-pptx-role`, and `data-pptx-placeholder` semantics; an explicit sidecar |
| 561 | entry may override only this name heuristic. Keep wrappers and use |
| 562 | `effect: none` for static content. |
| 563 | |
| 564 | **Fallback for flat SVGs** (no top-level `<g>` wrappers, only raw `<rect>` / `<text>` / `<path>` at the root): |
| 565 | |
| 566 | - ≤ 8 visible top-level primitives → each becomes one anchor (capped to avoid 70+ atom cascades on dense pages). |
| 567 | - > 8 → animation is skipped on that slide. The slide still renders, just without object animation. |
| 568 | |
| 569 | Executors should wrap logical sections in `<g id>` regardless of whether you plan to animate. [`shared-standards-core.md`](./shared-standards-core.md) requires it. |
| 570 | |
| 571 | --- |
| 572 | |
| 573 | ## 6. Validation and Read-Back |
| 574 | |
| 575 | Animation configuration is strict. Export fails on an unknown effect, mode, or |
| 576 | trigger; invalid timing/order values; a missing slide/group/`trigger_shape` |
| 577 | reference; a self-trigger; or any attempt to animate or trigger from a |
| 578 | structural layer. These errors never downgrade or silently omit a target. |
| 579 | |
| 580 | Generated export reads each slide's timing tree back and checks row count/order, |
| 581 | including repeated rows on one shape, trigger, trigger shape, shape target, |
| 582 | preset class, resolved effect tuple, native behavior signature, duration, and |
| 583 | timeline offset. Package validation then checks root |
| 584 | timing placement, unique and valid `p:cTn` ids, and every `p:spTgt` reference. |
| 585 | Deterministic Morph additionally checks the final adjacent slide parts for the |
| 586 | requested `!!` names, one-to-one uniqueness, compatible object types, and a |
| 587 | real Morph-by-object transition on the destination. |
| 588 | The writer does not emit `p:bldP` for groups or pictures. Direct-PPTX preserve |
| 589 | mode tolerates unchanged legacy group/picture `p:bldP` rows from earlier PPT |
| 590 | Master exports; new generated packages remain strict. |
| 591 | |
| 592 | Narration injection preserves animation and updates both p14 Choice/Fallback |
| 593 | when bounce timing is present; unsupported nested timing fails safely. |
| 594 | Direct-PPTX routes fingerprint source |
| 595 | object-animation timing before and after their allowed edits, then run |
| 596 | structural package validation; they do not author or normalize animation |
| 597 | effects. |
| 598 | |
| 599 | `pptx_to_svg.py` uses the same generated-transition read-back validator to |
| 600 | project supported source `p:transition` into canonical `animations.json` rows. |
| 601 | It retains the registry effect, effective options, exact duration, automatic |
| 602 | advance, and supported WAV sound; the sidecar defaults to `none` so absent |
| 603 | source transitions remain absent on re-export. Unknown or inexact native |
| 604 | carriers stay diagnosed/direct-preserve. This is a closed PPT Master-owned |
| 605 | contract, not an arbitrary OOXML transition normalizer. |
| 606 | |
| 607 | For source `p:timing`, the importer accepts only current generated behavior |
| 608 | trees whose registry effect/options, pane order, Start trigger, exact duration, |
| 609 | relative delay, and target/optional trigger shape map to unique top-level slide |
| 610 | SVG groups. It emits one group row or `effects[]` in the same sidecar. Rows |
| 611 | without a native duration, advanced timing modifiers, sounds, builds/media |
| 612 | commands, unknown behavior trees, and unmapped targets remain diagnosed/direct- |
| 613 | preserve; no timing value is inferred. |
| 614 | |
| 615 | **Validation boundary**: these checks prove PPTX timing, relationships, and |
| 616 | embedded sound parts. They are not final-video audio acceptance. The |
| 617 | native-export branch requires a triggered `video_sound_mix.py` receipt; the |
| 618 | slideshow-capture branch requires the human picture/audio/all-cue acceptance |
| 619 | owned by `generate-audio` and never claims that receipt. |
| 620 | |
| 621 | --- |
| 622 | |
| 623 | ## 7. Video Adaptation Contract |
| 624 | |
| 625 | Video renderers consume the resolved conversion trace through |
| 626 | `video_motion_plan.py`, never a raw sidecar or delay-only inference. The plan |
| 627 | locks identity, order, effect, direction, and timing; video may refine only its |
| 628 | declared renderer parameters. Unsupported families fail visibly. See |
| 629 | [`video-motion-plan.md`](../scripts/docs/video-motion-plan.md). |
| 630 | |
| 631 | On the native-export mix branch, direct narrated video sound uses the final |
| 632 | resolved trace for cue order and offsets, the final PPTX relationships for the |
| 633 | exact embedded audio bytes, and page-level narration correlation for the |
| 634 | exported-video clock. It never reads sound timing from a raw sidecar or |
| 635 | filename. The explicit slideshow-capture branch records PowerPoint's real-time |
| 636 | playback instead and does not consume the trace for sound mixing. |
| 637 | |
| 638 | --- |
| 639 | |
| 640 | ## 8. Limitations |
| 641 | |
| 642 | - Generated animation belongs to the native PPTX built from `svg_output/`. |
| 643 | `svg_final/` is a static preview, and inserting it as one SVG picture does |
| 644 | not create object anchors. |
| 645 | - PowerPoint OOXML is the compatibility target; other presentation apps may |
| 646 | reinterpret individual native behavior trees. |
| 647 | - PowerPoint's native MP4 encoder may omit transition and object-animation |
| 648 | sounds even when the PPTX package is valid. Direct sound-enabled MP4 delivery |
| 649 | therefore uses either the post-export mix or the explicit real-time |
| 650 | slideshow-capture contract owned by `generate-audio`; the branches never |
| 651 | stack. |
| 652 | - Direct-PPTX routes preserve unknown transition `AlternateContent`; timing |
| 653 | edits keep Choice and Fallback advance attributes synchronized. |
| 654 | |
| 655 | --- |
| 656 | |
| 657 | ## 9. Implementation References |
| 658 | |
| 659 | See [`pptx_transitions.py`](../scripts/pptx_transitions.py), |
| 660 | [`svg-pipeline.md`](../scripts/docs/svg-pipeline.md), |
| 661 | [`pptx-transitions.md`](../scripts/docs/pptx-transitions.md), |
| 662 | [`pptx-animations.md`](../scripts/docs/pptx-animations.md), and |
| 663 | [`video-motion-plan.md`](../scripts/docs/video-motion-plan.md). |
| 664 |