Slide Drawer
Builds presentation decks as HTML at 1920×1080 and renders them to PDF. Restores existing PDF/PPTX decks as semantic HTML. PPTX output exists but is only produced on request.
The design system, the render pipeline, and the export page size are all fixed and measured — they are not decisions to revisit per deck. What varies is the writing, and that is where the work goes.
The one thing that matters most
This skill exists because of a measured failure. The causes of rework on a real deck were counted. In descending order of how often each triggered a rewrite:
- Conclusions kept but premises dropped — sentences that were correct and unfollowable
- Line breaks in the wrong place — missing in wide blocks, forced into narrow cards
- Titles: split by a period, drifting in ending form, or translated out of English
- Inconsistency between sibling slides
- Invented terminology
- Source document not kept in sync
- Editing when only a proposal was asked for
The first outweighs the rest combined. Layout and color were a small minority. The problem was never correctness — it was whether a reader could take a sentence in on one pass.
So: references/slide-writing.md is not an appendix. Read it before writing the first slide, and run its four-question gate on every sentence. Skipping it is what turns a few review passes into many.
Mode routing
| Mode | Trigger | Path |
|---|---|---|
| Create | New deck, or a source document to convert | below |
| Revise | Fix, reword, or restyle existing slides | below |
| Restore | Turn a PDF/PPTX deck back into HTML + PDF | below |
| Export | Produce a PPTX — only when asked for one | below |
A session usually runs Create → Revise (many times) → Export. Once this skill is loaded its guidance stays in context; keep applying it across those turns without re-invoking.
References
Read these as the mode requires. They live in references/ beside this file.
| File | Read when |
|---|---|
slide-writing.md |
Always, before writing any slide text. The dominant source of rework |
language-notes.md |
After slide-writing.md, for the deck's language — line breaking, font stack, and the constructions that language makes too easy |
design-system.md |
Create — frame, palette, type scale, components. Restore does not use it |
revision-protocol.md |
Revise — before touching a single character |
restore-export.md |
Restore — markup contract, CSS traps, chart recipes. Export — page sizes |
assets/template.html is the runnable design system. Start every new deck from it rather than writing CSS.
Mode 1 — Create
Step 1: Read the source completely
If there is a source document, read all of it before planning anything. Not a skim, not a grep — the whole thing. The deck's structure comes from the source's structure, and a section you skipped becomes a slide you cannot write.
If the source changes mid-project, re-read it end to end rather than patching from the diff. A diff shows which lines changed, not which slides those changes invalidate — one revised figure can make every slide that cites it wrong, and none of those slides appear in the diff.
Collect the source's vocabulary as you read — the words it uses for its own entities, metrics, stages, and outputs. That list is what the deck is allowed to say; anything outside it is a term you invented. Settling this now is cheap, and settling it after fifty slides exist means chasing the change through the whole deck and the source document too. slide-writing.md § Terminology has the rule, including when a term is better left untranslated.
If there is no source document, interview first: what is being presented, to whom, what must the audience conclude, and how long is the slot.
Step 2: Settle the language
Decide before the outline, because switching later means rewriting every sentence. Priority: the user names a language → the source document's language → ask, if the stated audience differs from the source → the language the user is speaking to you in.
Read the matching section of language-notes.md now. It sets word-break, the font stack, and lang, and it names the constructions that language makes too easy to write badly.
Step 3: The outline gate
Present the outline and wait for approval before writing any HTML.
This is the single highest-leverage step in the skill. On the reference deck, structure was approved from a table like this on the first try and never revisited — every later revision was about wording and layout inside a slide, none about which slides existed.
Give a table:
| # | Title | One-line point | Components |
|---|---|---|---|
| 1 | (cover) | The deck's claim in one phrase | cover |
| 2 | (the deck's own claim row) | What it defends throughout | accent card + grid3 + warnbox |
| 3 | Contents | — | toc |
| 4 | 1. Architecture | Section: how it is built | sec |
| 5 | The build pipeline | Each stage owns one job | flow + card |
Plus the deck language, the section breakdown, and the total count.
Write the titles in the deck's language rather than in English — they are the actual titles, and reviewing them is part of what the gate is for.
Do not translate the example titles above. They are placeholders showing what a row contains, not wording to carry over. Titles translated out of this skill's English come out grammatical and unidiomatic — one observed run produced a Korean cover title that no Korean speaker would write, purely by rendering an English placeholder word for word. Write them from the source document's own vocabulary.
Conventional labels take the deck language's own word for them — a contents page, a summary, a closing slide. Leaving this skill's English in place is not avoiding a bad translation; it is mixing languages in the deck's furniture, and it shows immediately on a Korean deck whose table of contents says Contents.
Read slide-writing.md § Titles before filling the Title column. It gives four formats and ties the choice to what the slide does — a slide that only presents material takes a bare noun phrase, and forcing a colon onto it is the observed failure. All titles share one ending form. The gate is the cheapest place to settle this, since fixing it later means editing every slide.
Read design-system.md § Choosing one before filling the Components column. This column is where the component is actually chosen, and Step 4 only implements what is written here — content that belongs in a table but gets grid2 in the outline stays cards all the way to the PDF. That was the measured outcome: a slide whose four items repeated the same axis was written as four cards because the outline said so, and the rule about it was never consulted because by then the decision had been made.
On length: there is no ratio between source pages and slide count, and guessing one is how decks come out cramped. Judge each slide by a single question — does everything this slide needs to say fit in it, and can someone seeing it for the first time follow it? When the answer is no, the slide becomes two. Keep going until every slide passes, and let the total land wherever it lands.
A longer deck is not a failure. A deck that dropped half the reasoning to stay short is. Never shrink type or cut an explanation to hold a slide count you invented.
Ask explicitly whether they want full coverage or a compressed subset. These produce very different decks and the answer is not inferable.
Step 4: Write the HTML
Copy assets/template.html and build from its components. Read design-system.md for what each component is for, and slide-writing.md for every sentence you write.
Two rules decide most of the rework. Keep the premises — a slide is the source's reasoning laid out to be read once, not a shortened version of it, and a draft that keeps only conclusions is the most common first-draft failure (slide-writing.md § Keep the premises). Put those premises in one full-width block at the top and give each card below a single claim, rather than repeating the setup inside every narrow card (design-system.md § Where premises go).
Write in batches of 10–15 slides and render after each batch. Do not write 50 slides and render once — a systematic problem found at slide 13 costs one fix, and the same problem found at slide 50 costs 37.
Step 5: Render and check
python scripts/render_slides.py <deck.html> --out <png_dir>
Read the report. It states overflow and fill ratio, both of which are otherwise silent: an overflowed slide looks correct in a browser and arrives truncated in the PDF.
- Overflow must be 0. Fix by splitting the slide, not by shrinking type.
- Fill ratio under 60% flags a slide as thin. Cover and section slides are exempt. A flagged slide is a candidate for more content, not an error — 60–100% is the normal band.
Then look at the PNGs. Reading the HTML is not a substitute; problems that only appear rendered include awkward line wraps, unbalanced columns, and cards that came out visually heavier than their importance.
Step 6: Render the PDF
python -c "
from playwright.sync_api import sync_playwright
from pathlib import Path
with sync_playwright() as p:
b = p.chromium.launch(); g = b.new_page()
g.goto(Path('deck.html').resolve().as_uri()); g.wait_for_load_state('networkidle')
g.pdf(path='deck.pdf', print_background=True, prefer_css_page_size=True)
b.close()"
template.html already carries the @page and print rules this needs, so the PDF lands at 13.333 × 7.5in with one slide per page and the text stays vector text.
The deliverable is deck.html and deck.pdf. Two files, nothing else. The PNGs from Step 5 were there to check overflow — delete them once the deck is confirmed. Do not offer a PPTX; if the user wants one they will ask.
Step 7: Self-check before delivery
- Delivered
deck.htmlanddeck.pdf, and deleted the working PNGs - Overflow: 0 slides
- Every sentence passed the four-question gate in
slide-writing.md - Every constant names where it came from, and every named tool or metric says what it is
- Premises sit in a full-width block; each card below carries one claim — unless the slide is a single table, which needs no such block
- Card headings state the claim, not the topic
- Items repeating a shared axis were built as a table, not as cards
- No card mixes prose with bullets, and every label-and-value pair sits in a table row
- No code or data literal wraps to a second line
- No term appears that is absent from the source document
- Full-width cards with two or more sentences have a line break between them; grid cards have none and are short instead
- Sibling cards and sibling slides share a sentence pattern and ending form
- Every title matches one of the four formats in
slide-writing.md§ Titles, none is split by a period, and no single format covers most of the deck - The cover title names the deck as a whole, not one slide’s finding, and uses no contrast the reader cannot yet follow
- A slide with a conventional name (contents, summary) is titled with that name and nothing else
- Every title shares one ending form, and it matches the body's politeness level
- Title emphasis (
<em>) is on the same side across each section - No animation, no external resources, no scrolling containers
- Three emphasis colors at most per slide, three hierarchy levels at most
Mode 2 — Revise
Read references/revision-protocol.md before editing anything. The short version, because getting this wrong is expensive:
| Kind | Do |
|---|---|
| Ask — "suggest a fix", "what do you think?", or anything ending in "?" | Do not touch the file. Propose 2–3 options, stop |
| Dictate — "change X to Y", they supplied the wording | Apply their wording verbatim. Do not re-author it |
| Delegate — "this reads badly", they named a problem not a fix | Propose 2–3 options, wait for a pick, then apply |
Then:
- Re-read the file region from disk immediately before editing. The user edits the deck directly while reviewing, often without announcing it.
- Read the surrounding context before any string replacement. The same phrase may be correct on another slide.
- Sync the source document in the same turn — apply to the slide, read what the slide now says, then make the source say the same thing.
- Name consistency fallout. If the change alters a pattern, list the sibling slides and ask before propagating.
- Re-render only what changed:
--only 25or--only 25-27,31.
Mode 3 — Restore
Turns an existing PDF or PPTX back into an HTML deck. You do this by looking at the pages and writing the HTML — there is no converter script, and building one is the mistake this mode exists to prevent.
Restoring means delivering both deck.html and deck.pdf. Render the PDF as part of the job; do not stop at the HTML and wait to be asked.
Read references/restore-export.md for the markup contract, the CSS traps, and the chart recipes before writing anything.
Two rules that decide everything else
The output is read by an LLM. Headings are <h1>, lists are <ul>, tables are <table>, and nothing that carries meaning stays locked inside an image. Content runs a few KB per slide.
No images at all. Not base64, not an assets/ folder. Shapes, tables, and simple charts get rebuilt in CSS; whatever cannot be rebuilt becomes a labelled placeholder box. The HTML is genuinely one file.
Steps
- PPTX first:
soffice --headless --convert-to pdf --outdir . deck.pptx - Render pages to images:
mkdir -p pg && python -c "import fitz; d=fitz.open('deck.pdf'); [d[i].get_pixmap(dpi=150).save(f'pg/p{i+1:02d}.png') for i in range(len(d))]" - Skim the whole deck, then settle the shared CSS — palette, type scale, recurring layouts. Per-slide CSS gives an inconsistent deck and a bloated file.
- Read each page image and write its slide. Read the words off the page: source decks bake text into pictures, and a text-layer extractor recovers about 30% of such a deck. One measured government deck had 18 of 34 pages under 30 extractable characters.
- Render the PDF:
python -c " from playwright.sync_api import sync_playwright from pathlib import Path with sync_playwright() as p: b = p.chromium.launch(); g = b.new_page() g.goto(Path('deck.html').resolve().as_uri()); g.wait_for_load_state('networkidle') g.pdf(path='deck.pdf', print_background=True, prefer_css_page_size=True) b.close()" - Look at the output beside the original and fix what is wrong. Batches of five to ten slides, rendering as you go.
- Delete every image the job created. The source page renders, the renders made to check the result — all of it is working material, and it adds up: one measured run left 49MB behind. Hand over
deck.htmlanddeck.pdfonly; if the source was a PPTX, the intermediate PDF goes too. Do this without being asked.
What to keep
| Source | Do |
|---|---|
| All text, including text baked into images | Semantic tags, real text |
| Tables, even when drawn as a picture | <table> — read the cells off the page |
| Boxes, rules, title bars, tinted panels, badges | CSS background / border / border-radius |
| Donut, pie, bar, stacked bar charts | Rebuild in CSS — recipes in the reference |
| Charts whose values are printed as labels | Rebuild from those numbers |
| Photographs; charts whose values cannot be read | Placeholder box naming what it was |
| Decoration: gradient arrows, paper texture, shadows, ornamental icons | Drop it |
The slide keeps the source's own page size — 720×540pt for 4:3 — and @page must match so the PDF comes out at that size. A 4:3 deck stays 4:3.
What not to do
- Do not write a conversion script. Deciding "this is a title, those four are a KPI row" takes a second by eye and cannot be recovered from font sizes — "largest text is the title" makes
6.5%the title on any page built around a figure. - Do not place images, embed fonts, or bake backgrounds to raster. A previous attempt produced 26.4MB carrying 56KB of text.
- Do not score the result by pixel difference. On decks confirmed correct by eye the number ranged 5.9 to 41.2 purely with text density. Judge by eye.
- Do not debug a collapsed column by rewriting the slide. It is almost always one of the three CSS traps in the reference — a flex
bodyshrinking the slide, a<table>computing its own widths, or a bare1fr.
Revise (Mode 2) applies normally to a restored deck.
Mode 4 — Export
PDF is not this mode's job. Create renders it in Step 6 and Restore renders it in Mode 3, both at the right page size. This mode exists for one case: the user explicitly asked for a PPTX. Never propose one — the answer is a deck and a PDF unless they say otherwise.
python scripts/export_deck.py <png_dir> --format pptx --out deck.pptx
It consumes the PNG set from render_slides.py, so render first.
PPTX slides come out 13.33 × 7.5in — PowerPoint's standard 16:9 page, so full-screen viewing has no letterboxing. The same size the Create PDF lands at.
PPTX output is an image deck. Each slide holds one full-bleed PNG; text is not editable in PowerPoint. Say so when handing it over. If the user needs to edit in PowerPoint, the honest path is to edit the HTML and re-export — restore-export.md documents why native text conversion was rejected, with the specific constraints it would impose on the design system.
Dependencies
pip install playwright pillow python-pptx pymupdf
playwright install chromium
render_slides.py needs playwright; export_deck.py needs python-pptx (or pillow, for its unused PDF path). Restore has no script of its own — its inline commands need pymupdf and playwright, plus LibreOffice on PATH for PPTX input.