Generate presentation decks and greeting cards as full-bleed AI-rendered images. Each image is a high-resolution PNG generated by Gemini, composed from a style prefix (visual system) + content prompt (layout/description). Slides are assembled into PPTX; cards are standalone PNGs in any aspect ratio.
Style Module (getStyle) + Content Prompt (per-slide or per-card)
↓
Gemini 3 Pro Image Preview API
↓
PNG images (cached in slides-{deck}/ or cards-{deck}/)
↓
Slides: PptxGenJS assembly → .pptx
Cards: Standalone high-res PNGs (any aspect ratio)
npm install
export GEMINI_API_KEY="your-api-key"The easiest way to use mofa-pptx is through Claude Code with the PPTX skill installed. Claude Code knows all 14 included styles and can generate complete decks from natural language.
Just describe what you want:
"Make a 10-slide deck about AI infrastructure trends, dark cinematic style"
Claude Code will pick the right style, write all slide prompts, generate the PNGs, assemble the PPTX, and visually QA the output.
Your description determines which style gets used:
| You describe | Style matched | Look & feel |
|---|---|---|
| Consulting deck, strategy analysis | tectonic.js |
Lavender gradient, monochrome wireframe sketches |
| Comparing Amazon, Google, Microsoft, NVIDIA | multi-brand.js |
Each section in company brand colors |
| Open source community, Linux Foundation | dark-community.js |
Corporate blue/teal, abstract AI orbs |
| Tea culture, Chinese traditions | feng-zikai.js |
Ink-wash painting on rice paper |
| Red-branded corporate | openclaw-red.js |
Red #C7000B accent |
| Dark, cinematic, dramatic | nb-br.js |
Blade Runner 2049 dark palette |
| Warm, golden tone | cc-research.js |
Blade Runner golden hour |
| Science, physics, biology | what-is-life.js |
Physics dark + biology light |
| Enterprise AI, purple corporate | agentic-enterprise.js |
Purple consulting aesthetic |
If no existing style fits your topic, Claude Code creates a new styles/*.js for you.
- Picks a style from
styles/*.jsbased on your description (or creates a new one) - Writes slide prompts with layout descriptions, titles, diagram instructions
- Generates the deck script (
generate-*.js) with the rightrun()config - Runs it via the Gemini API
- QAs the output — visually inspects every slide for text issues, layout problems
- Fixes bad slides — deletes bad PNGs, adjusts prompts, re-runs only broken ones
- Iterates until the deck is clean
You can also write deck scripts directly without Claude Code.
Style modules live in styles/ and define the visual system — background, typography, illustration style. Each exports a getStyle(tag) function that maps style tags to prompt prefixes.
// styles/my-style.js
const STYLE_NORMAL = `Create a presentation slide image. 1920x1080 pixels, 16:9 landscape format.
BACKGROUND: Deep navy (#1A1F36), clean, premium.
TYPOGRAPHY: Noto Sans SC for ALL text. ALL text in Chinese ONLY.
- Title: bold, white, large heading
- Body: light weight, light gray, standard body size
ILLUSTRATION STYLE (CRITICAL):
Monochrome wireframe sketches in white/light gray line art on dark background.
Hand-drawn quality but clean and readable. Apple Keynote level whitespace.`;
const STYLE_COVER = `Create a presentation slide image. 1920x1080 pixels, 16:9 landscape format.
BACKGROUND: Dark gradient from #0A0F28 to #1A1F36.
TYPOGRAPHY: Noto Sans SC, bold, white, extra-large heading. ONLY the main title, nothing else.`;
const styles = { normal: STYLE_NORMAL, cover: STYLE_COVER };
function getStyle(tag) { return styles[tag] || styles.normal; }
module.exports = { styles, getStyle, STYLE_NORMAL, STYLE_COVER };// generate-my-deck.js
const { run } = require("./lib/engine");
const { getStyle } = require("./styles/my-style");
const slides = [
{
style: "cover",
prompt: `TITLE: "My Presentation Title"
Centered vertically in the left 60% of the slide. Nothing else.`,
},
{
style: "normal",
prompt: `TITLE (top-left, bold, white): "Key Findings"
LAYOUT: 3 cards in a row, each with:
- Icon: wireframe sketch (chart, gear, rocket)
- Heading: bold, white, 20pt
- Body: light gray, 16pt
CARD 1: "Revenue Growth" / "+47% year over year"
CARD 2: "Efficiency" / "3x faster deployment cycles"
CARD 3: "Scale" / "Supporting 10M+ daily users"`,
},
];
run({
slideDir: "slides-my-deck",
outFile: "My_Presentation.pptx",
slides,
getStyle,
concurrency: 5,
imageSize: "2K",
});node generate-my-deck.jsOutput: My_Presentation.pptx with cached PNGs in slides-my-deck/.
const { run } = require("./lib/engine");
run({
slideDir: "slides-my-deck", // PNG cache directory
outFile: "Output.pptx", // Output PPTX filename
slides: [ // Array of { style, prompt }
{ style: "cover", prompt: "..." },
{ style: "normal", prompt: "..." },
],
getStyle: (tag) => styleString, // Style resolver from styles/*.js
concurrency: 5, // Parallel workers (1-20)
imageSize: "2K", // "1K" | "2K" | "4K" (optional)
});| imageSize | Resolution | Use |
|---|---|---|
"1K" |
1376 x 768 | Quick drafts |
"2K" |
2752 x 1536 | Standard decks (recommended) |
"4K" |
5504 x 3072 | Print quality |
Resolution in prompt text is ignored by Gemini — must use the imageSize parameter.
Generated PNGs are cached (skip if file >10KB exists). To fix a bad slide:
rm slides-my-deck/slide-05.png # delete the bad one
node generate-my-deck.js # only slide 5 regeneratesFull regeneration: rm slides-my-deck/*.png && node generate-my-deck.js
| Setting | Behavior |
|---|---|
1 |
Sequential, one slide at a time |
5 |
Safe default for most Gemini rate limits |
10-20 |
Faster, works if your API quota allows |
Beyond presentation slides, mofa-pptx can generate standalone greeting card images using the runCards() API. Cards support any aspect ratio and output as PNG files (no PPTX assembly).
const { runCards } = require("./lib/engine");
const { getStyle } = require("./styles/cny-guochao");
const cards = [
{ name: "front", style: "front", prompt: "Card front description..." },
{ name: "greeting", style: "greeting", prompt: "Blessing text layout..." },
{ name: "scene", style: "scene", prompt: "Full scene illustration..." },
];
runCards({
cardDir: "cards-cny", // Output directory
cards, // Array of { name, style, prompt }
getStyle, // Style resolver from styles/*.js
aspectRatio: "9:16", // Any Gemini-supported ratio
concurrency: 3, // Parallel workers
imageSize: "2K", // Resolution
});Output: cards-cny/card-front.png, cards-cny/card-greeting.png, cards-cny/card-scene.png
| Ratio | Orientation | Use case |
|---|---|---|
"9:16" |
Portrait | Mobile wallpaper, e-cards, WeChat |
"3:4" |
Portrait | Classic postcard, print-friendly |
"1:1" |
Square | Social media, Instagram |
"4:3" |
Landscape | Traditional card |
"16:9" |
Landscape | Slides (default) |
Slides (run) |
Cards (runCards) |
|
|---|---|---|
| Output | PPTX + cached PNGs | PNGs only |
| Aspect ratio | 16:9 | Any (default 9:16) |
| File names | slide-01.png |
card-{name}.png |
| Directory | slides-{deck}/ |
cards-{deck}/ |
| Item fields | { style, prompt } |
{ name, style, prompt } |
Same as slides — cached PNGs >10KB are skipped. Delete to regenerate:
rm cards-cny/card-front.png # delete the bad one
node generate-cny-cards.js # only that card regeneratesAnimate static greeting cards into short videos with background music. The pipeline: static PNG → Gemini Veo animation → ffmpeg compositing → MP4.
const { runVideoCards } = require("./lib/engine");
const { getStyle } = require("./styles/laoshu");
const { getAnimPrompt, DEFAULT_BGM } = require("./styles/video-card");
const cards = [
{
name: "heming",
style: "scene",
prompt: "鹤鸣茶社场景描述...",
animStyle: "shuimo", // Animation style: shuimo | festive | gentle | dynamic
animDesc: "Steam rising from tea cups, leaves swaying...", // Scene-specific details
},
];
runVideoCards({
cardDir: "cards-laoshu",
cards,
getStyle, // Image style resolver
getAnimPrompt, // Animation prompt resolver
bgmPath: DEFAULT_BGM, // Background music (bgm-cny.mp3)
aspectRatio: "9:16",
imageSize: "2K",
});Output: cards-laoshu/card-heming.png + cards-laoshu/card-heming-animated.mp4
| Tag | Style | Best for |
|---|---|---|
shuimo |
水墨 — slow, meditative ink-wash motion | Ink painting scenes, landscapes |
festive |
喜庆 — lively, cheerful movement | Spring Festival, celebrations |
gentle |
温柔 — dreamy, minimal movement | Portraits, quiet moments |
dynamic |
动感 — energetic, noticeable motion | Action scenes, animals |
Each video includes:
- Still frame (2s) — original card image as thumbnail/preview
- Crossfade (1s) — smooth transition to animation
- Veo animation (~8s) — AI-generated motion
- Fade out (1.5s) — graceful ending
- BGM — background music with fade in/out
All parameters are configurable: stillDuration, crossfadeDur, fadeOutDur, musicVolume, musicFadeIn.
To animate an existing card PNG without regenerating the image:
node animate-card.js heming # default shuimo style
node animate-card.js huahua dynamic # dynamic styleRaw Veo videos are cached as *-raw.mp4. Delete to force re-animation:
rm cards-laoshu/card-heming-raw.mp4 # delete cached Veo video
node animate-card.js heming # re-generates animationCards (runCards) |
Video Cards (runVideoCards) |
|
|---|---|---|
| Output | PNGs only | PNGs + MP4 videos |
| Pipeline | Gemini image gen | Gemini image gen → Veo → ffmpeg |
| Style files | styles/*.js |
styles/*.js + styles/video-card.js |
| Item fields | { name, style, prompt } |
{ name, style, prompt, animStyle?, animDesc? } |
| Dependencies | Gemini API | Gemini API + ffmpeg |
| Module | Tags | Theme |
|---|---|---|
tectonic.js |
normal, data, cover | Lavender gradient, whale watermark, monochrome wireframe |
opensource.js |
normal, data, cover | Similar to tectonic, cartoon whale variant |
multi-brand.js |
cover, amazon_dark, amazon_light, google, microsoft, nvidia_dark, nvidia_light, overview | Multi-company branded wireframes |
feng-zikai.js |
scene, content, emotion | Feng Zikai ink-wash painting on rice paper |
dark-community.js |
default | Linux Foundation corporate, abstract AI orbs |
agentic-enterprise.js |
normal, cover, data, warm | Purple consulting deck |
agentic-enterprise-red.js |
normal, cover, data | Red branded |
lingnan.js |
cover, day1, day2, day3, info | Lingnan ink-wash painting |
what-is-life.js |
cover, physics_dark, biology_light, overview | Science-themed |
openclaw-red.js |
normal, cover, data | Red-branded 4K |
nb-pro.js |
default | Lavender gradient purple accent |
nb-br.js |
default | Blade Runner 2049 dark cinematic |
cc-research.js |
default | Blade Runner 2049 golden hour warm |
cny-guochao.js |
front, greeting, scene | Chinese New Year — festive red+gold Guochao style (9:16 cards) |
cny-shuimo.js |
front, greeting, scene | Chinese New Year — ink-wash elegant style (9:16 cards) |
laoshu.js |
front, greeting, scene | Old Tree ink-wash — minimal figure + colloquial poetry |
xianer.js |
front, greeting, scene | Xian Er cartoon monk — cute, warm, healing |
relevant.js |
front, greeting, scene, festive | Relevant brand — geometric minimal + festive red-gold variant |
video-card.js |
shuimo, festive, gentle, dynamic | Video animation — Veo animation prompts + ffmpeg compositing config |
git clone https://github.com/mofa-org/mofa-pptx.git
cd mofa-pptx
npm install
export GEMINI_API_KEY="your-key"Open Claude Code in the project directory and just say what you want:
"用水墨风格给张三做一张新年贺卡,祝他新春快乐,万事如意"
Or more specific:
"用Relevant红色节庆风格给陈岳做一张贺卡,内容是:风浪越大,鱼越贵。新春快乐!"
Claude Code will automatically:
- Pick the right style module
- Write the prompt
- Generate the script and run it
- Output high-res PNG
| Keyword | Style | Look |
|---|---|---|
| "国潮" "红金" | cny-guochao | Festive red + gold, dragons, lanterns |
| "水墨" "雅致" | cny-shuimo | Rice paper, ink wash, plum blossoms |
| "老树" "打油诗" | laoshu | Minimal ink figure + colloquial poetry |
| "贤二" "小和尚" | xianer | Cute cartoon monk, warm and healing |
| "Relevant" "极简" | relevant | Geometric egg-head, pure white |
| "Relevant红色" "节庆" | relevant (festive) | Red background, gold text, white accents |
Style prefixes should specify: resolution/format, background colors, typography (fonts, weights, sizes, colors), illustration style (the most important part), and overall aesthetic reference.
Slide prompts should describe: title (position, style), layout (cards, columns, grids), per-element details (diagrams, text content), and optionally page numbers.
Use visual separators for complex layouts:
═══════════════════════════════════════
─── CARD 1: "Title" ───
─── CARD 2: "Title" ───
Common pitfalls:
- Gemini may garble Chinese text — always visually inspect output
- Avoid
"title (12pt, #888)"— Gemini renders formatting hints as literal text. Use natural language:"title in small gray text" - For Chinese-only decks, explicitly state
"ALL TEXT IN CHINESE"and"English only for proper nouns" - Complex multi-card layouts may need 2-3 regeneration attempts