{"slug":"pptx-7","title":"pptx","summary":"When the office_edit tool is available and the deck is a .pptx in the workspace, use the office skill instead: the user watches the slides arrive live. Use this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide ","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-10-17T09:13:10.426658Z","repo":{"url":"https://github.com/invergent-ai/surogates","stars":28,"forks":1,"license":"AGPL-3.0","updatedAt":"2026-10-16T18:37:14Z"},"bodyHtml":"<hr>\n<h2>name: pptx\ndescription: \"When the office_edit tool is available and the deck is a .pptx in the workspace, use the office skill instead: the user watches the slides arrive live. Use this skill any time a .pptx or .potx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations as PowerPoint (.pptx) files; reading, parsing, or extracting text from any .pptx or .potx file (even if the extracted content will be used elsewhere, like in an email, summary, or creating a different type of slide deck); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates (.potx), layouts, speaker notes, or comments. Trigger whenever the user asks for a PowerPoint, a deck, slides or a presentation, or references a .pptx or .potx filename, regardless of what they plan to do with the content afterward.\"\nfallback_for_tools: [office_edit]\nlicense: Proprietary. LICENSE.txt has complete terms</h2>\n<h1>PPTX creation, editing, and analysis</h1>\n<p>A <code>.pptx</code> is a ZIP archive of XML files. Choose your approach by task:</p>\n<table>\n<thead>\n<tr>\n<th>Task</th>\n<th>Approach</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Create</strong> a new deck</td>\n<td>Copy <code>scripts/example_deck.js</code> and change its theme and slides — see gotchas below — keeping it a <a href=\"#structured-decks\">structured deck</a> unless the user asks for a quick throwaway</td>\n</tr>\n<tr>\n<td><strong>Edit</strong> an existing deck, or build from a template</td>\n<td>unzip → edit <code>ppt/slides/slideN.xml</code> → zip</td>\n</tr>\n<tr>\n<td><strong>Read</strong> content</td>\n<td><code>read_file(path=\"deck.pptx\")</code> — the harness parses it natively, one <code>## Slide N: &lt;title&gt;</code> section per slide; visual grid: <code>python scripts/thumbnail.py deck.pptx</code></td>\n</tr>\n</tbody>\n</table>\n<p>Everything this skill uses is preinstalled in the sandbox. The sandbox runs as a non-root user without <code>sudo</code>, so <code>apt-get install</code> always fails; if a CLI tool is missing, use the Python or JavaScript route on this page instead.</p>\n<h2>Scripts</h2>\n<p>Paths are relative to this skill's directory — the <code>staged_at</code> path <code>skill_view</code> returns. Everything else is plain Python, <code>node</code>, or shell.</p>\n<table>\n<thead>\n<tr>\n<th>Script</th>\n<th>What it does</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>scripts/thumbnail.py deck.pptx [prefix]</code></td>\n<td>Labeled grid of every slide, for picking template layouts. <code>.pptx</code> only. Pass <code>prefix</code> — it defaults to <code>thumbnails</code>, which overwrites the grids of any other deck done in the same directory</td>\n</tr>\n<tr>\n<td><code>scripts/add_slide.py unpacked/ slide2.xml [--after slideN.xml]</code></td>\n<td>Duplicate a slide (or a <code>slideLayoutN.xml</code>) with all the package bookkeeping. Also takes a <code>.pptx</code> directly with <code>-o out.pptx</code></td>\n</tr>\n<tr>\n<td><code>scripts/clean.py unpacked/</code></td>\n<td>Delete slides, media, and rels no longer referenced. Run <strong>after</strong> <code>&lt;p:sldIdLst&gt;</code> is final</td>\n</tr>\n<tr>\n<td><code>scripts/apply_theme.js</code></td>\n<td>Node module: <code>const { applyTheme } = require(\"&lt;this skill's directory&gt;/scripts/apply_theme.js\")</code>. <code>await applyTheme(\"deck.pptx\", THEME)</code> after <code>pres.writeFile()</code> writes <code>THEME</code>'s colors and name (escaped for XML) into the deck's theme, for <a href=\"#structured-decks\">structured decks</a>. Also a CLI: <code>node scripts/apply_theme.js deck.pptx theme.json</code></td>\n</tr>\n<tr>\n<td><code>scripts/example_deck.js [out.pptx]</code></td>\n<td>A minimal <a href=\"#structured-decks\">structured deck</a> — theme, two layouts with named placeholders, sections, text on a shape, a native column chart, notes — that passes <code>validate.py</code>. Copy it and change the content rather than rediscovering the API</td>\n</tr>\n<tr>\n<td><code>scripts/office/validate.py deck.pptx [--original src.pptx]</code></td>\n<td>Schema, relationship, content-type, chart and slide checks; each failure names its fix. Pass <code>--original</code> for any template-derived deck — it baselines the schema checks against the template, so the template's own XSD errors don't read as yours</td>\n</tr>\n<tr>\n<td><code>scripts/office/soffice.py --headless --convert-to pdf deck.pptx</code></td>\n<td>LibreOffice wrapper — bare <code>soffice</code> hangs in this sandbox</td>\n</tr>\n</tbody>\n</table>\n<h2>Creating with pptxgenjs — gotchas</h2>\n<p><code>pptxgenjs</code> is preinstalled — do not run <code>npm install</code> first. Start from <code>scripts/example_deck.js</code>: copy it into the workspace and change the theme and slides; its calls are tested. These are the footguns:</p>\n<ul>\n<li><strong>A name that doesn't exist fails silently.</strong> Chart types are <code>pres.ChartType.area</code>, <code>bar</code>, <code>bar3d</code>, <code>bubble</code>, <code>doughnut</code>, <code>line</code>, <code>pie</code>, <code>radar</code>, <code>scatter</code>. A column chart is <code>ChartType.bar</code> with <code>barDir: \"col\"</code>: <strong>there is no <code>ChartType.col</code></strong>, and it writes an empty chart frame without an error. Shapes are <code>pres.ShapeType.rect</code>, <code>ellipse</code>, <code>line</code> — on the instance, lower camelCase; there is no <code>pptxgen.shapes</code>.</li>\n<li><strong>Set <code>pres.layout</code> before adding slides.</strong> The default canvas is <code>LAYOUT_16x9</code> = <strong>10\" × 5.625\"</strong>, not 13.3\" wide. Coordinates past the edge are written, not clamped — the shape just isn't on the slide. (<code>LAYOUT_WIDE</code> is 13.3\" × 7.5\".)</li>\n<li><strong>Hex colors: never <code>#</code>, never 8 digits.</strong> <code>color: \"FF0000\"</code>. Both <code>\"#FF0000\"</code> and alpha baked into the hex (<code>\"00000020\"</code>) <strong>corrupt the file</strong>. For translucency: <code>transparency: 0-100</code> on fills and images, <code>opacity: 0.0-1.0</code> on shadows — each is silently ignored on the other.</li>\n<li><strong>pptxgenjs mutates option objects in place</strong> (converts values to EMU on first use). Never share one <code>shadow</code>/options object across two <code>add*</code> calls — build a fresh object each time.</li>\n<li><strong>Shadow <code>offset</code> must be ≥ 0</strong> — a negative offset corrupts the file. To cast a shadow upward, use <code>angle: 270</code> with a positive offset.</li>\n<li><strong><code>letterSpacing</code> is silently ignored</strong> — the real option is <code>charSpacing</code>.</li>\n<li><strong>Lists:</strong> <code>bullet: true</code> on each item, never a literal <code>•</code> (renders double bullets). Set <code>breakLine: true</code> on every array item except the last. Space bulleted paragraphs with <code>paraSpaceAfter</code>, not <code>lineSpacing</code> (huge gaps).</li>\n<li><strong>One <code>new pptxgen()</code> per output file</strong> — never reuse an instance.</li>\n<li><strong>Never use rounded rectangles</strong> (<code>roundRect</code>, <code>rectRadius</code>): use <code>rect</code>, square corners.</li>\n<li><strong>Text on a filled shape is one <code>addText</code> with <code>shape</code> and <code>fill</code></strong>, as <code>example_deck.js</code> does: <code>addText(\"…\", { shape: pres.ShapeType.rect, fill: { color: C.accent1 }, margin: [14, 14, 0, 0], valign: \"middle\", … })</code>. <code>margin</code> is in points, ordered <code>[left, right, bottom, top]</code>. A separate text box laid on top of a shape, or <code>margin: 0</code>, starts the text on the shape's edge.</li>\n<li><strong>Gradient fills aren't supported</strong> — use a gradient image as the background instead.</li>\n<li><strong>Every <code>addText</code> call without a <code>placeholder</code> needs <code>isTextBox: true</code></strong> — without it the shape lacks <code>txBox=\"1\"</code>, so screen readers announce the text as a \"graphic\" instead of a text box. No visual change.</li>\n<li><strong>Text boxes have built-in internal padding</strong> — set <code>margin: 0</code> whenever text must align with a shape, line, or icon at the same x.</li>\n<li><strong>Speaker notes go in <code>slide.addNotes(\"...\")</code></strong> (plain text, once per slide), never in a text box on the slide.</li>\n<li><strong>Keep charts native.</strong> Use <code>addChart()</code> for everything PowerPoint can chart (pass an array of <code>{type, data, options}</code> for combos). For PowerPoint-native features the library doesn't expose (trendlines, error bars), compute the extra series yourself or post-process the generated OOXML — do not fall back to a rendered image. Only chart types PowerPoint has no native form for (Sankey, network, chord) go in as images. <strong>Native charts render in the PDF you check with:</strong> a chart missing from a rendered slide is missing in PowerPoint too. Run <code>validate.py</code>, which names the cause (usually an unknown <code>ChartType</code>), and fix the <code>addChart</code> call.</li>\n<li><strong>Default charts render bare</strong> — no title, no data labels, dated palette. Set <code>showTitle</code> + <code>title</code>, <code>showValue: true</code> + <code>dataLabelPosition</code>, <code>chartColors: [...]</code> from your palette, and quiet the frame (<code>catAxisLabelColor</code>/<code>valAxisLabelColor</code>, <code>valGridLine: { color, size }</code>, <code>catGridLine: { style: \"none\" }</code>, <code>showLegend: false</code> for a single series).</li>\n<li><strong>Data labels round to whole numbers</strong> — the default format is <code>#,##0</code>, so 1.2, 2.4 and 4.1 label as 1, 2 and 4. Set <code>dataLabelFormatCode</code> (e.g. <code>\"0.0\"</code>) whenever a value has decimals.</li>\n<li><strong>Plot-area fill and border go in <code>plotArea: { fill: { color }, border: { pt, color } }</code>.</strong> Top-level <code>fill</code>/<code>border</code> objects pass <code>addChart()</code> and then crash <code>writeFile()</code> with <code>(colorStr || \"\").replace is not a function</code>.</li>\n<li><strong><code>background</code> takes <code>{ color: \"2A211E\" }</code></strong> (or <code>{ path }</code> / <code>{ data }</code> for an image), not <code>{ fill: { color } }</code> — that object crashes <code>writeFile()</code> with the same <code>(colorStr || \"\").replace is not a function</code>.</li>\n<li><strong>On a stacked bar or column chart, <code>dataLabelPosition</code> must be <code>ctr</code>, <code>inEnd</code>, or <code>inBase</code>.</strong> <code>outEnd</code> <strong>corrupts the file</strong>.</li>\n<li><strong>A combo series using <code>secondaryValAxis</code>/<code>secondaryCatAxis</code> needs both <code>valAxes</code> and <code>catAxes</code> on the chart options, two entries each.</strong> Without them pptxgenjs writes axis <em>ids</em> it never declares, and PowerPoint <strong>discards that chart</strong> and reports the file as corrupt. Supplying only <code>valAxes</code> is not enough.</li>\n<li><strong>After <code>writeFile()</code>, run <code>python scripts/office/validate.py deck.pptx</code>.</strong> It reports the two chart faults above and the slide-XML defects PowerPoint refuses, and names the fix for each. Fix them in your generator, not by hand-editing the packed XML.</li>\n<li><strong>Never reorder the children of <code>&lt;p:presentation&gt;</code>.</strong> pptxgenjs writes <code>&lt;p:notesMasterIdLst&gt;</code> right after <code>&lt;p:sldIdLst&gt;</code> and points both masters at one theme part. PowerPoint reads that happily — move the element and the same deck becomes unopenable.</li>\n<li><strong>Icons:</strong> render <code>react-icons</code> to SVG (<code>ReactDOMServer.renderToStaticMarkup</code>), rasterize with <code>sharp</code> at ≥256px, and insert via <code>addImage({ data: \"image/png;base64,\" + buf.toString(\"base64\") })</code> — the <code>image/png;base64,</code> prefix is required (<code>react-icons</code>, <code>react</code>, <code>react-dom</code>, and <code>sharp</code> are preinstalled — <code>npm install react-icons react react-dom sharp</code> only if a require fails).</li>\n<li><strong><code>defineSlideMaster()</code> creates a slide <em>layout</em>, not a master</strong> — one <code>slideLayoutN.xml</code> per call, under a single master you cannot change. Call them layouts when talking to the user.</li>\n<li><strong><code>pres.company</code> and a <code>defineSlideMaster()</code> <code>title</code> are written into the XML unescaped</strong> — an <code>&amp;</code> in either (<code>\"Harrow &amp; Sons\"</code>) leaves <code>docProps/app.xml</code> or the layout unparseable, and <code>validate.py</code> fails. Write <code>&amp;amp;</code> there, and in the matching <code>masterName</code>. <code>title</code>, <code>subject</code>, <code>author</code>, section titles, <code>objectName</code>, notes and slide text are escaped for you.</li>\n</ul>\n<h3>Structured decks</h3>\n<p>A deck built on a theme, layouts, placeholders and sections can be restyled in one place and edited in PowerPoint without fighting it. Build every new deck this way unless the user asks for a quick throwaway; <code>scripts/example_deck.js</code> is a working starting point.</p>\n<ol>\n<li><strong>Theme first:</strong> <code>const THEME = { name, headFontFace, bodyFontFace, colors: { dk1, lt1, dk2, lt2, accent1 … accent6, hlink, folHlink } }</code>, colors in hex. <code>pres.theme</code> takes the two fonts and nothing else. Name no font after that — titles get the heading font, other text the body font — except on chart text, which is hard-coded Arial: give its <code>*FontFace</code> options <code>\"+mn-lt\"</code> (theme body font; <code>\"+mj-lt\"</code> is the heading font).</li>\n<li><strong><code>const C = pres.SchemeColor</code> for every color</strong> — <code>C.text1</code>/<code>C.text2</code> are <code>dk1</code>/<code>dk2</code>, <code>C.background1</code>/<code>C.background2</code> are <code>lt1</code>/<code>lt2</code>. These take hex only, read from <code>THEME</code>: <code>valGridLine.color</code>, <code>catGridLine.color</code>, <code>shadow.color</code>, and on a one-series bar chart <code>invertedColors</code> or more than one <code>chartColors</code>. A scheme color there is written as <code>&lt;a:srgbClr val=\"bg2\"/&gt;</code> without complaint, and being hex, these will not follow a later change of theme.</li>\n<li><strong>Sections:</strong> <code>pres.addSection({ title })</code> just before that section's first slide, then <code>sectionTitle</code> on every <code>addSlide</code>. A slide without one — or, when sections are all declared up front, a table's auto-paged continuation slides — lands in a stray \"Default-N\" section.</li>\n<li><strong>One layout per slide frame</strong> (<code>defineSlideMaster()</code>): title, section divider, title only, chart + commentary, table; dark and light variants if the design alternates. Text regions are named placeholders carrying position, size, weight and <code>color</code> — chart, image and table placeholders need a <code>color</code> too, or prompt text and arrays of runs come out hard-coded <code>000000</code>. Footer text, logo and <code>slideNumber</code> go on the layout, never on a slide. <strong>Title placeholders render centred</strong> unless their options set <code>align: \"left\"</code>; body placeholders start left.</li>\n<li><strong>Declare placeholders inside the layout's <code>objects</code>:</strong> <code>objects: [{ placeholder: { options: { name: \"title\", type: \"title\", x, y, w, h, … }, text: \"\" } }]</code> — <code>scripts/example_deck.js</code> has the full shape. pptxgenjs has no <code>placeholders:</code> key: it is ignored, the layout gets no placeholders, and every fill by name drops a bare text box at 0,0, which looks like a broken library rather than a typo. A layout's <code>objects</code> take only <code>placeholder</code>, <code>text</code>, <code>image</code>, <code>rect</code>, <code>line</code> and <code>chart</code> — any other key (<code>ellipse</code>) is dropped without a warning, so draw those on the slide. A <code>text</code> object's position goes inside its <code>options</code> (<code>{ text: { text, options: { x, y, w, h, … } } }</code>); coordinates beside <code>options</code> are ignored and the text lands in the top-left corner.</li>\n<li><strong>Fill by name:</strong> <code>addSlide({ masterName, sectionTitle })</code>, then <code>addText(\"…\", { placeholder: \"title\" })</code> with no position, size or color — the placeholder's own options win, and anything it doesn't set (italic, <code>align</code>, <code>fontFace</code>) still applies; style one run with <code>[{ text, options }]</code>. A misspelt <code>placeholder</code> or <code>masterName</code> fails silently (a stray text box at 0,0; the blank layout). Only <code>title</code>, <code>body</code> and <code>chart</code> placeholders fill by name alone: <code>addImage({ placeholder })</code> takes the placeholder's x and y but not its size (a 1\" × 1\" picture) — pass <code>w</code>/<code>h</code> too. <code>addTable({ placeholder })</code> takes its x and y only — always pass <code>w</code>; when it auto-pages it ignores the placeholder and runs off the slide, so also pass <code>x</code>, <code>y</code> and <code>autoPageSlideStartY</code> (same as <code>y</code>, or continuation pages are laid out from the top margin), give the layout a <code>margin</code> whose bottom clears the footer, and fill the continuation slides' titles through <code>slide.newAutoPagedSlides</code>.</li>\n<li><strong>A layout is the frame, not the \"layout\" Design Ideas means.</strong> Cards, icons, stat callouts and images are still composed slide by slide on top of it, in scheme colors, each with an <code>objectName</code> — every Design Idea applies, and most content slides sit on the title-only layout.</li>\n<li><strong>Write, then apply the theme:</strong> <code>await pres.writeFile({ fileName: \"deck.pptx\" })</code>, then <code>await applyTheme(\"deck.pptx\", THEME)</code>. pptxgenjs cannot write theme colors: until this runs, scheme colors resolve to Office's stock palette. It throws, changing nothing, on a scheme color in a hex-only option — fix what it names and rebuild.</li>\n</ol>\n<h2>Editing existing decks and templates</h2>\n<p>Pick layouts first: <code>python scripts/thumbnail.py template.pptx template-thumbs</code> writes a labeled grid of every slide and prints the file(s) it created — <code>template-thumbs.jpg</code>, split into <code>template-thumbs-N.jpg</code> past 12 slides. <strong>Always pass that second argument, named after the deck.</strong> It defaults to <code>thumbnails</code>, so two decks thumbnailed in one directory silently overwrite each other's grids — the first deck's are simply gone (template analysis only — visual QA needs the full-resolution renders from <a href=\"#converting-to-images\">Converting to Images</a>; it only accepts <code>.pptx</code>, so copy a <code>.potx</code> to a <code>.pptx</code> name first). Look at the grid with <code>vision_analyze</code>, and use it with the template's <code>read_file</code> text to map each content section onto a template slide, and vary the layouts — don't put every section on the same title-and-bullets slide.</p>\n<pre><code>python3 -c \"import sys,zipfile; zipfile.ZipFile(sys.argv[1]).extractall('unpacked')\" deck.pptx\npython scripts/add_slide.py unpacked/ slide2.xml --after slide2.xml   # duplicate a slide (or slideLayoutN.xml); prints the new slide's path\n# reorder / delete slides = edit &lt;p:sldIdLst&gt; in ppt/presentation.xml\npython scripts/clean.py unpacked/                                     # after deletions: removes orphaned slides, media, rels\n# edit slide content in ppt/slides/slideN.xml\n(cd unpacked &amp;&amp; rm -f ../out.pptx &amp;&amp; zip -Xr ../out.pptx .)           # zip from INSIDE the dir; rm first or deleted parts survive\npython scripts/office/validate.py out.pptx --original deck.pptx\n</code></pre>\n<ul>\n<li><strong>Do all structural work — add, delete, reorder — before editing any slide's content.</strong> <code>add_slide.py</code> copies a slide file verbatim, so duplicating after you edit clones the edited content; and <code>clean.py</code> deletes any slide missing from <code>&lt;p:sldIdLst&gt;</code>, including one you just wrote.</li>\n<li><strong>Never copy a slide file by hand</strong> — <code>add_slide.py</code> does every registration a new slide needs and reports what it made (<code>Created ppt/slides/slide17.xml from slide2.xml</code>). It also works directly on a file: <code>add_slide.py deck.pptx slide2.xml -o out.pptx</code> — <strong>pass <code>-o</code>, or it rewrites the input deck in place.</strong> A duplicated slide still <em>references</em> its source's chart/SmartArt/embedded-object parts rather than cloning them, so editing one slide's chart changes the other's.</li>\n<li><strong>If you use <code>python-pptx</code></strong>, three things it won't do: duplicate a slide (its only entry point is <code>add_slide(layout)</code>), preserve formatting through <code>text_frame.text = \"...\"</code> (that collapses the paragraph to a single unstyled run — assign <code>run.text</code> instead), or read the SVG/EMF most template art uses (<code>add_picture</code> raises <code>UnidentifiedImageError</code>).</li>\n<li>Legacy <code>.ppt</code> must be converted first: <code>python scripts/office/soffice.py --headless --convert-to pptx file.ppt</code>. <code>.potx</code> templates unpack and pack identically — keep the <code>.potx</code> extension on the output.</li>\n<li>To reuse a template icon or image, duplicate a slide or layout that already contains it.</li>\n</ul>\n<p>When filling in a template:</p>\n<ul>\n<li>If you script an XML transform, parse with <code>defusedxml.minidom</code> — round-tripping OOXML through <code>xml.etree.ElementTree</code> rewrites namespace prefixes and corrupts the deck.</li>\n<li><strong>Template slots ≠ source items.</strong> If the template shows 4 team members and you have 3, delete the 4th member's entire group (image + text boxes), not just its text — then check for orphaned visuals in QA.</li>\n<li>One <code>&lt;a:p&gt;</code> per list item — never concatenate items into a single paragraph. Copy the sibling <code>&lt;a:pPr&gt;</code> to preserve spacing, and put <code>b=\"1\"</code> on the <code>&lt;a:rPr&gt;</code> of titles, section headers, and inline labels (<code>Status:</code>, <code>Owner:</code>).</li>\n<li>Let bullets inherit from the layout; only add <code>&lt;a:buChar&gt;</code>, <code>&lt;a:buAutoNum&gt;</code> (numbered), or <code>&lt;a:buNone&gt;</code> to override — never a literal <code>•</code> in the text.</li>\n<li>Text with leading or trailing spaces needs <code>xml:space=\"preserve\"</code> on its <code>&lt;a:t&gt;</code>.</li>\n</ul>\n<h2>Deck content and flow</h2>\n<ul>\n<li><strong>One message per slide.</strong> A deck goes theme by theme, message by message.</li>\n<li><strong>A common structure is the pyramid:</strong> the main message up front, an executive summary that previews the sections, the sections themselves, then a conclusion, usually with next steps that act on the findings.</li>\n<li><strong>Match the structure to the size of the deck.</strong> A short deck might not need an introduction and a conclusion; a long deck might need dividers between topics.</li>\n<li><strong>Build toward what the deck is for:</strong> a decision, consensus, a celebration, resolving a disagreement, or opening a discussion.</li>\n<li><strong>Build for how it will be used.</strong> A live deck gets narration and possibly discussion; a pre-read has to carry the whole narrative itself. An executive audience in a live meeting needs few words and highly visual slides. Detailed, word-heavy slides are sometimes right for subject-matter experts or for a deck read offline. Analytical reports lean on charts and tables.</li>\n<li><strong>Size a live deck to the talk:</strong> about 1.5 to 2 minutes per slide, with time left for questions if there will be any.</li>\n</ul>\n<h2>Design Ideas</h2>\n<p><strong>Don't create boring slides.</strong> Plain bullets on a white background won't impress anyone. Consider ideas from this list for each slide.</p>\n<h3>Before Starting</h3>\n<ul>\n<li><strong>Pick a bold, content-informed color palette</strong>: The palette should feel designed for THIS topic. If swapping your colors into a completely different presentation would still \"work,\" you haven't made specific enough choices.</li>\n<li><strong>Dominance over equality</strong>: One color should dominate (60-70% visual weight), with 1-2 supporting tones and one sharp accent. Never give all colors equal weight.</li>\n<li><strong>Dark/light contrast</strong>: Dark backgrounds for title + conclusion slides, light for content (\"sandwich\" structure). Or commit to dark throughout for a premium feel.</li>\n<li><strong>Commit to a visual motif</strong>: Pick ONE distinctive element and repeat it — full-bleed images, icons in colored circles. Carry it across every slide. <strong>Do not use a color bar or accent stripe as your motif</strong> (see Avoid list).</li>\n</ul>\n<h3>Color Palettes</h3>\n<p>Choose colors that match your topic — don't default to generic blue. Use these palettes as inspiration:</p>\n<table>\n<thead>\n<tr>\n<th>Theme</th>\n<th>Primary</th>\n<th>Secondary</th>\n<th>Accent</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>Midnight Executive</strong></td>\n<td><code>1E2761</code> (navy)</td>\n<td><code>CADCFC</code> (ice blue)</td>\n<td><code>FFFFFF</code> (white)</td>\n</tr>\n<tr>\n<td><strong>Forest &amp; Moss</strong></td>\n<td><code>2C5F2D</code> (forest)</td>\n<td><code>97BC62</code> (moss)</td>\n<td><code>F5F5F5</code> (cream)</td>\n</tr>\n<tr>\n<td><strong>Coral Energy</strong></td>\n<td><code>F96167</code> (coral)</td>\n<td><code>F9E795</code> (gold)</td>\n<td><code>2F3C7E</code> (navy)</td>\n</tr>\n<tr>\n<td><strong>Warm Terracotta</strong></td>\n<td><code>B85042</code> (terracotta)</td>\n<td><code>E7E8D1</code> (sand)</td>\n<td><code>A7BEAE</code> (sage)</td>\n</tr>\n<tr>\n<td><strong>Ocean Gradient</strong></td>\n<td><code>065A82</code> (deep blue)</td>\n<td><code>1C7293</code> (teal)</td>\n<td><code>21295C</code> (midnight)</td>\n</tr>\n<tr>\n<td><strong>Charcoal Minimal</strong></td>\n<td><code>36454F</code> (charcoal)</td>\n<td><code>F2F2F2</code> (off-white)</td>\n<td><code>212121</code> (black)</td>\n</tr>\n<tr>\n<td><strong>Teal Trust</strong></td>\n<td><code>028090</code> (teal)</td>\n<td><code>00A896</code> (seafoam)</td>\n<td><code>02C39A</code> (mint)</td>\n</tr>\n<tr>\n<td><strong>Berry &amp; Cream</strong></td>\n<td><code>6D2E46</code> (berry)</td>\n<td><code>A26769</code> (dusty rose)</td>\n<td><code>ECE2D0</code> (cream)</td>\n</tr>\n<tr>\n<td><strong>Sage Calm</strong></td>\n<td><code>84B59F</code> (sage)</td>\n<td><code>69A297</code> (eucalyptus)</td>\n<td><code>50808E</code> (slate)</td>\n</tr>\n<tr>\n<td><strong>Cherry Bold</strong></td>\n<td><code>990011</code> (cherry)</td>\n<td><code>FCF6F5</code> (off-white)</td>\n<td><code>2F3C7E</code> (navy)</td>\n</tr>\n</tbody>\n</table>\n<h3>For Each Slide</h3>\n<p><strong>Every slide needs a visual element</strong> — image, chart, icon, or shape. Text-only slides are forgettable.</p>\n<p><strong>Layout options:</strong></p>\n<ul>\n<li>Two-column (text left, illustration on right)</li>\n<li>Icon + text rows (icon in colored circle, bold header, description below)</li>\n<li>2x2 or 2x3 grid (image on one side, grid of content blocks on other)</li>\n<li>Half-bleed image (full left or right side) with content overlay</li>\n</ul>\n<p><strong>Data display:</strong></p>\n<ul>\n<li>Large stat callouts (big numbers 60-72pt with small labels below)</li>\n<li>Comparison columns (before/after, pros/cons, side-by-side options)</li>\n<li>Timeline or process flow (numbered steps, arrows)</li>\n</ul>\n<p><strong>Visual polish:</strong></p>\n<ul>\n<li>Icons in small colored circles next to section headers</li>\n<li>Italic accent text for key stats or taglines</li>\n</ul>\n<h3>Typography</h3>\n<p><strong>Font names you write into the .pptx are rendered by the user's PowerPoint, not by this environment.</strong> Your visual QA renders via LibreOffice, which substitutes fonts it doesn't have — and for some fonts the substitute has different widths, so your QA preview can show text overflow (or fit) that the real deck won't have. To keep your QA trustworthy:</p>\n<ul>\n<li><strong>Safe fonts</strong> (render true-to-width in QA <em>and</em> ship with Office): <strong>Arial, Calibri, Cambria, Times New Roman, Courier New, Bookman Old Style, Century Schoolbook</strong>. Use these for body text and anything where fit matters.</li>\n<li><strong>Headers with personality at zero QA risk</strong>: pair a safe-list serif header (Cambria, Bookman Old Style, Century Schoolbook) with a safe-list sans body (Calibri or Arial). You get visual contrast without giving up reliable overflow checks.</li>\n<li><strong>If the user asks for a font outside the safe list</strong> (e.g. Georgia or Trebuchet MS): use it where the user asked, but size those containers with extra slack (~10%) and don't trust QA text-fit on those elements — the preview of that font is approximate. If the user hasn't specified, prefer safe-list fonts for body text.</li>\n<li><strong>QA-unreliable fonts</strong> (substitute has different widths — overflow checks can be wrong): Georgia, Trebuchet MS, Impact, Arial Black, Garamond, Consolas, Palatino Linotype. Calibri Light substitution varies by environment; treat as QA-unreliable. Fine for titles/accents with slack; don't trust QA text-fit on these.</li>\n<li><strong>Never default to Aptos</strong> — Office's post-2023 default has no metric-compatible substitute here <em>and</em> is missing from older Office installs, so it's unreliable on both ends.</li>\n</ul>\n<table>\n<thead>\n<tr>\n<th>Element</th>\n<th>Size</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Slide title</td>\n<td>36-44pt bold</td>\n</tr>\n<tr>\n<td>Section header</td>\n<td>20-24pt bold</td>\n</tr>\n<tr>\n<td>Body text</td>\n<td>14-16pt</td>\n</tr>\n<tr>\n<td>Captions</td>\n<td>10-12pt muted</td>\n</tr>\n</tbody>\n</table>\n<h3>Spacing</h3>\n<ul>\n<li>0.5\" minimum margins</li>\n<li>0.3-0.5\" between content blocks</li>\n<li>Leave breathing room—don't fill every inch</li>\n</ul>\n<h3>Avoid (Common Mistakes)</h3>\n<ul>\n<li><strong>Don't repeat the same layout</strong> — vary columns, cards, and callouts across slides</li>\n<li><strong>Don't end a slide title with a period</strong></li>\n<li><strong>Don't let titles drift between slides of the same type</strong> — content slides with the title at the top put it at the same position, font, size, and alignment on every slide; a different slide type (a section divider with the title mid-slide) can place it differently</li>\n<li><strong>Don't change how chart labels look from chart to chart</strong> — axis and data labels use the same color, font, and size on every chart in the deck; vary one only where a chart needs it (a second axis, a highlighted series, a label sitting on a dark bar)</li>\n<li><strong>Don't center body text</strong> — left-align paragraphs and lists; center only titles</li>\n<li><strong>Don't skimp on size contrast</strong> — titles need 36pt+ to stand out from 14-16pt body</li>\n<li><strong>Don't default to blue</strong> — pick colors that reflect the specific topic</li>\n<li><strong>Don't mix spacing randomly</strong> — choose 0.3\" or 0.5\" gaps and use consistently</li>\n<li><strong>Don't style one slide and leave the rest plain</strong> — commit fully or keep it simple throughout</li>\n<li><strong>Don't create text-only slides</strong> — add images, icons, charts, or visual elements; avoid plain title + bullets</li>\n<li><strong>Don't forget text box padding</strong> — when aligning lines or shapes with text edges, set <code>margin: 0</code> on the text box or offset the shape to account for padding</li>\n<li><strong>Don't use low-contrast elements</strong> — icons AND text need strong contrast against the background; avoid light text on light backgrounds or dark text on dark backgrounds</li>\n<li><strong>NEVER use rounded rectangles</strong> — cards, callouts, labels and frames get square corners (<code>rect</code>)</li>\n<li><strong>NEVER use accent lines under titles</strong> — these are a hallmark of AI-generated slides; use whitespace or background color instead</li>\n<li><strong>NEVER add decorative color bars or accent stripes</strong> — this includes: header/footer bars spanning the slide width, vertical sidebar stripes down one edge of the slide, thin accent stripes along one edge of a card or content block, and \"single-side borders\" on rectangles. These read as AI-generated filler. If you want to set a card apart, use a subtle background tint, a drop shadow, or an icon — not an edge stripe.</li>\n<li><strong>Don't default to cream/beige backgrounds</strong> — when no background is specified, use white (<code>FFFFFF</code>) or the user's brand palette; avoid warm-neutral defaults like <code>F5F5DC</code>, <code>FAF0E6</code>, <code>FAEBD7</code>, <code>FFF8E1</code></li>\n<li><strong>Don't ship text that overflows its shape</strong> — if text doesn't fit, reduce font size, split across slides, or enlarge the container; never leave content cut off or spilling past bounds. <strong>Don't shrink below the sizes in the Typography table</strong> (body text 14pt, captions 10pt) in a deck you create — once you reach that floor, cut words, enlarge the container, or split the slide instead; when editing an existing deck or template, match its sizes</li>\n</ul>\n<h2>QA (Required)</h2>\n<p>Your first render usually has a few real issues — overlaps, overflow, misalignment. Find and fix those, re-render only the slides you changed, and stop.</p>\n<h3>Content QA (required)</h3>\n<pre><code>read_file(path=\"output.pptx\")\n</code></pre>\n<p>Check for missing content, typos, wrong order.</p>\n<p><strong>When using templates, check for leftover placeholder text:</strong></p>\n<pre><code>unzip -p output.pptx 'ppt/slides/slide*.xml' | grep -oiE \"\\bx{3,}\\b|lorem|ipsum|\\bTODO|\\[insert|this[^&lt;]*(page|slide)[^&lt;]*layout\"\n</code></pre>\n<p>If grep returns results, fix them before declaring success.</p>\n<h3>File QA (required)</h3>\n<pre><code>python scripts/office/validate.py output.pptx                      # built from scratch\npython scripts/office/validate.py output.pptx --original src.pptx  # built from a template\n</code></pre>\n<p><strong>If the deck came from a template, always pass <code>--original</code>.</strong> A template may itself\ncontain parts the XSD rejects, so a bare run can report failures you never caused — and\na genuine regression can hide among them. <code>--original</code> baselines\nthe schema and slide checks against the template, suppressing errors it already had.\nThe structural checks — relationships, content types, charts — ignore <code>--original</code> and\nreport template-inherited problems either way, so read those on their own merits.</p>\n<p>pptxgenjs emits chart XML PowerPoint refuses to open, and every other tool\naccepts: python-pptx opens those decks, LibreOffice renders them, the XSD\npasses them. It also writes charts with no data at all, which every viewer\ndraws as an empty frame. <code>validate.py</code> reports both; every failure names its\nfix. Fix it in the generator and rebuild.</p>\n<h3>Layout QA (required)</h3>\n<p>After converting to PDF (see <a href=\"#converting-to-images\">Converting to Images</a>), read each slide's text as it was laid out:</p>\n<pre><code>pdftotext -layout -f 3 -l 3 output.pdf -      # one slide; drop -f/-l for all\n</code></pre>\n<p>Every line break and roughly where each line starts are in the output: a word wrapped onto a line of its own, a title centred that should sit left, text out of order. It is exact and cheap — check it before the images.</p>\n<h3>Visual QA (required)</h3>\n<p>Convert the slides to images (see <a href=\"#converting-to-images\">Converting to Images</a>) and inspect every one. After staring at the generating code you tend to see what you expect rather than what rendered, so look at the images fresh with <code>vision_analyze</code>, asking it about the defects below (or hand the image paths to a <code>delegate_task</code> worker). <code>vision_analyze</code> sometimes reports defects that aren't there and misses real ones: confirm a reported wrap or overflow with <code>pdftotext -layout</code> before changing the deck, and use the images for what text can't show — overlaps, crops, contrast, shape alignment. User-visible defects to look for:</p>\n<ul>\n<li><strong>Text overflow or text cut off at a box or slide boundary — check this first.</strong> It is the most common defect and always user-visible. (For a font the previewer renders unreliably per Typography, the preview is approximate: trust the ~10% slack you left, not its apparent fit.)</li>\n<li>Overlapping elements (text through shapes, lines through words, stacked elements)</li>\n<li>Source citations or footers colliding with content above</li>\n<li>Elements too close (&lt; 0.3\" gaps) or cards/sections nearly touching</li>\n<li>Uneven gaps (large empty area in one place, cramped in another)</li>\n<li>Insufficient margin from slide edges (&lt; 0.5\")</li>\n<li>Columns or similar elements not aligned consistently</li>\n<li>Low-contrast text (e.g., light gray text on cream-colored background)</li>\n<li>Template decoration mispositioned after text replacement — e.g., a title underline positioned for one line, but the replaced title wrapped to two</li>\n<li>Low-contrast icons (e.g., dark icons on dark backgrounds without a contrasting circle)</li>\n<li>Text boxes too narrow causing excessive wrapping</li>\n<li>Titles at different heights, or chart labels that look different from chart to chart, across slides of the same type — compare slides with each other, not only each slide with itself</li>\n<li>A word or number broken across two lines — a word split mid-word, or a figure separated from its unit (\"26\" on one line, \"M\" on the next)</li>\n<li>Leftover placeholder content</li>\n</ul>\n<h2>Converting to Images</h2>\n<p>Convert presentations to individual slide images for visual inspection:</p>\n<pre><code>python scripts/office/soffice.py --headless --convert-to pdf output.pptx\nrm -f slide-*.jpg\npdftoppm -jpeg -r 150 output.pdf slide\nls -1 \"$PWD\"/slide-*.jpg\n</code></pre>\n<p><strong>Pass the absolute paths printed above directly to <code>vision_analyze</code></strong> — it reads workspace files only, so render into the workspace, never <code>/tmp</code>. The <code>rm</code> clears stale images from prior runs. <code>pdftoppm</code> zero-pads based on page count: <code>slide-1.jpg</code> for decks under 10 pages, <code>slide-01.jpg</code> for 10-99, <code>slide-001.jpg</code> for 100+.</p>\n<p><strong>After fixes, rerun all four commands above</strong> — the PDF must be regenerated from the edited <code>.pptx</code> before <code>pdftoppm</code> can reflect your changes.</p>\n<h2>Dependencies</h2>\n<p><code>pptxgenjs</code>, <code>jszip</code>, <code>react-icons</code>, <code>react</code>, <code>react-dom</code>, <code>sharp</code> (npm, preinstalled — install only if a <code>require</code> fails) · <code>python-pptx</code>, <code>Pillow</code>, <code>defusedxml</code>, <code>lxml</code> (pip — thumbnail, clean, validate) · <code>zip</code> / <code>unzip</code> · LibreOffice (<code>soffice</code>, auto-configured for sandboxed environments via <code>scripts/office/soffice.py</code>) · <code>pdftoppm</code> (Poppler)</p>\n","files":[{"path":"LICENSE.txt","sizeBytes":1467,"isText":true},{"path":"scripts/add_slide.py","sizeBytes":14537,"isText":true},{"path":"scripts/apply_theme.js","sizeBytes":5334,"isText":true},{"path":"scripts/clean.py","sizeBytes":10415,"isText":true},{"path":"scripts/example_deck.js","sizeBytes":4750,"isText":true},{"path":"scripts/__init__.py","sizeBytes":0,"isText":true},{"path":"scripts/office/helpers/__init__.py","sizeBytes":3380,"isText":true},{"path":"scripts/office/helpers/pptx_chart.py","sizeBytes":6441,"isText":true},{"path":"scripts/office/helpers/pptx_slide.py","sizeBytes":1675,"isText":true},{"path":"scripts/office/helpers/pptx_theme.py","sizeBytes":3510,"isText":true},{"path":"scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd","sizeBytes":1963,"isText":false},{"path":"scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd","sizeBytes":2515,"isText":false},{"path":"scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd","sizeBytes":2856,"isText":false},{"path":"scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd","sizeBytes":1344,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd","sizeBytes":6956,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd","sizeBytes":74984,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd","sizeBytes":51302,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd","sizeBytes":624,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd","sizeBytes":152039,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd","sizeBytes":1231,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd","sizeBytes":8862,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd","sizeBytes":14795,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd","sizeBytes":83612,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd","sizeBytes":1269,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd","sizeBytes":7328,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd","sizeBytes":6382,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd","sizeBytes":1248,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd","sizeBytes":880,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd","sizeBytes":2608,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd","sizeBytes":3507,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd","sizeBytes":7507,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd","sizeBytes":23313,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd","sizeBytes":1367,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd","sizeBytes":242277,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd","sizeBytes":26148,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd","sizeBytes":25279,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd","sizeBytes":535,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd","sizeBytes":5712,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd","sizeBytes":4010,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd","sizeBytes":171367,"isText":false},{"path":"scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd","sizeBytes":4646,"isText":false},{"path":"scripts/office/schemas/mce/mc.xsd","sizeBytes":3127,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-2010.xsd","sizeBytes":26549,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-2012.xsd","sizeBytes":3745,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-2018.xsd","sizeBytes":901,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-cex-2018.xsd","sizeBytes":1778,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-cid-2016.xsd","sizeBytes":1002,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd","sizeBytes":600,"isText":false},{"path":"scripts/office/schemas/microsoft/wml-symex-2015.xsd","sizeBytes":745,"isText":false},{"path":"scripts/office/soffice.py","sizeBytes":5945,"isText":true},{"path":"scripts/office/validate.py","sizeBytes":6142,"isText":true},{"path":"scripts/office/validators/base.py","sizeBytes":33721,"isText":true},{"path":"scripts/office/validators/docx.py","sizeBytes":17482,"isText":true},{"path":"scripts/office/validators/__init__.py","sizeBytes":336,"isText":true},{"path":"scripts/office/validators/pptx.py","sizeBytes":15981,"isText":true},{"path":"scripts/office/validators/redlining.py","sizeBytes":18017,"isText":true},{"path":"scripts/thumbnail.py","sizeBytes":9802,"isText":true},{"path":"SKILL.md","sizeBytes":32407,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-10-17T09:13:46.486942Z","sha256":"4D2842CAE3A7CE1D4090125789ABD56A295B3877C07829AC6EBD14C4782903F2","sizeBytes":175139},"review":null,"source":{"repositoryUrl":"https://github.com/invergent-ai/surogates","path":"skills/productivity/pptx","license":"AGPL-3.0","commit":"f423b85eed3b230f0085ed007adc86e8aa639d02","subtreeSha":"F3F3A32731BA1F7805E35D17FCC85BCC513B1C06FFD05274E93256371EAFE672","lastSyncedAt":"2026-10-17T09:13:07.689271Z"},"reviewedAt":"2026-10-17T09:18:43.435811Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/invergent-ai/surogates/tree/master/skills/productivity/pptx"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install invergent-ai-surogates@llmmart"},{"target":"git","command":"git clone https://github.com/invergent-ai/surogates.git"}]}