slide-deck
When you want to draft, update, convert, or export a slide deck for a React/Next.js slide system (${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/). Writes TypeScript Slide[] arrays using your primitives (Eyebrow, Heading, Accent, Body, BulletList, Divider, Two
Install
npx skills add https://github.com/coreyhaines31/makerskills/tree/main/skills/slide-deck
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install coreyhaines31-makerskills@llmmart
git clone https://github.com/coreyhaines31/makerskills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole coreyhaines31/makerskills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
/slide-deck — Draft, update, convert, and export branded React decks
Authors React/TypeScript decks for ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/page.tsx using the user's slide system. Branded output (no separate HTML pipeline) — when portable HTML/PDF is needed, export mode snapshots the rendered React deck via Playwright so the output is brand-perfect.
Modes (pick one before Step 1)
| Mode | Invocation | Goal |
|---|---|---|
| new | /slide-deck new <topic> (default) |
Draft a new deck from a brief |
| update | /slide-deck update <slug> |
Modify an existing deck (with overflow guards) |
| ppt | /slide-deck ppt <path-to-pptx> |
Convert a legacy PPTX into a React deck |
| export | /slide-deck export <slug> [html\|pdf\|vercel] |
Snapshot a deck to HTML, PDF, or Vercel URL |
For update, ppt, and export, skip to the corresponding mode section below. For new, continue through Steps 1–8.
Step 1 — Capture the brief (ask all at once)
Ask in one structured message. Don't round-trip on each.
- Topic / title — what's the deck about?
- Audience — who's in the room? (technical / executive / mixed / clients) Push for specificity: "founders" is too broad; "B2B SaaS founders at MicroConf" is workable.
- Length — rough slide count: 5–10 (lightning / short internal) / 10–15 (short keynote, pitch) / 20–25 (keynote) / 30+ (workshop — uncommon for branded decks).
- Density — speaker-led (1 idea/slide, big type, lots of breathing room, more slides if needed) or reading-first (4–6 bullets/slide, structured grids, self-contained for async review).
- CTA — what should the audience do/think/feel after?
- Slug — kebab-case (e.g.,
marketing-like-an-engineer). - Content state — all content ready / rough notes / topic only.
Remember density — it affects slide count, copy length per slide, and which primitives to favor. See references/narrative-and-voice.md for the density-specific rules.
Step 2 — Three narrative angles
Riff on zarazhangrui/frontend-slides' "show, don't tell" — but applied to story, not visuals (your visual system is fixed).
Pitch 3 angles in 1–2 sentences each:
- Safe — most likely to land. Conventional structure for the audience.
- Bold — contrarian or counterintuitive frame. Higher upside, slight risk.
- Wildcard — unexpected structure (story-first, single-question deck, anti-thesis, etc.).
The user picks one. If they reject all three, propose three more — don't force a path.
Step 3 — Outline
For the chosen angle:
- Sections — propose 3–7 named sections (e.g.,
Title,Hook,Problem,Framework,Examples,Close). The default 3-act structure is OPTIONAL; only use it if the user wants it or the deck is a keynote-length talk that benefits from one. - Slide titles within each section
- One-line takeaway per slide
Total slide count should match the duration estimate from Step 1.
Show the outline as a table. the user edits / approves before expand.
Step 4 — Expand to slide content
For each slide, write the full content using the user's primitives. Reference references/system.md for the primitive vocabulary and references/narrative-and-voice.md for hook patterns and voice rules.
Slide types and which primitives fit:
| Slide type | Primitives | Pattern |
|---|---|---|
| Title | Eyebrow + <h1> with <Accent> keyword |
First slide, sets brand and topic |
| Hook | Heading + Body |
A take, story open, contrarian frame, or specific stat |
| Section divider | Eyebrow + Heading (centered, large) |
Clean break between sections |
| Framework | Heading + BulletList or custom layout |
The thing the user's teaching |
| Two-column | TwoCol |
Comparison, before/after, problem/solution |
| Quote / pull-quote | Body (large) |
Authority or audience-recognition moment |
| Resource / link | Body + URL on its own line |
Outbound (rare — the user's voice says minimize) |
| Close / CTA | Heading + Body + BulletList for next steps |
What the audience should do |
Voice anchors — write for the ear, not the page (see narrative-and-voice.md for full rules):
- Decks are spoken aloud. Slide text + speaker notes both get said out loud. Write as you'd talk.
- Read every slide out loud before saving. If you stumble, if it sounds like a press release, rewrite.
- Contractions everywhere (don't, won't, you're, I'd). Strip them only on reading-first decks.
- "You" — never "the audience," "users," "people."
- Numbers said naturally ("a quarter" not "23.7%").
- Conviction-coded, listener-perspective, short sentences, specific nouns.
- No filler ("Today I want to talk about…") — open with a take.
- (Reading-first density mode exception: written voice is fine since no one will speak the slides — see
narrative-and-voice.md.)
Step 5 — Speaker notes
Every slide gets 3–5 notes lines. Notes are the full spoken talk — write them the way you'd actually say them, pauses and asides included. Slide text is the headline; notes are the full thought.
Notes structure:
- The opener — what you say when the slide comes up
- The point — the one idea this slide is making
- The supporting beat — example, data, or color
- The transition — how this connects to the next slide
- (Optional) The aside — a quip or callback
For title slides and section dividers, 2–3 notes is fine.
Step 6 — Generate files
Write to ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/:
layout.tsx— Next.js layout with<title>metadata (use the deck title)page.tsx— the slide deck
Use the templates in references/template.md as the skeleton.
The <AUTHOR_HANDLE> / <AUTHOR_SITE> tokens (close-slide footer) come from ${MAKERSKILLS_CONFIG:-$HOME/.config/makerskills}/slide-deck/identity.yaml (keys: handle, site) if it exists. Otherwise ask the user for their handle and site once, offer to save them there, and use those. If they don't want a footer, drop the <p> entirely — never ship a deck with someone else's identity on it.
page.tsx must:
- Have
"use client"at the top - Import
SlideDeckandSlidetype from@/components/slides/slide-deck - Import
SectionRangefrom@/components/slides/sections(only if using sections) - Import the primitives the user uses from
@/components/slides/slide-primitives - Declare
const slides: Slide[] = [...] - (Optional) Declare
const sections: SectionRange[] = [...]with 0-indexedfrom/to - Export default a component that returns
<SlideDeck slides={slides} sections={sections} />
Step 7 — Preview
After writing the files, check if your slide-deck dev server is running.
lsof -i :3000-3099 2>/dev/null | grep -E "LISTEN" | head
If a port is in use (portless-compatible — check package.json for the dev script naming):
# Suggested URL pattern
echo "Preview at: http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>"
If no dev server is running, tell the user:
cd ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site} && npm run dev
# then visit http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>
Don't auto-start the dev server (might disrupt other work).
Step 8 — Archive
The archive lives in ${MAKERSKILLS_CONFIG:-$HOME/.config/makerskills}/slide-deck/archive/ (create the directory if missing). Never write the archive inside the skill's own folder — skill installs and upgrades re-sync from source and wipe anything saved there. Migration: if this skill's folder contains an old references/decks-archive.md with user entries, move it to <archive dir>/INDEX.md first.
Append a one-liner to <archive dir>/INDEX.md (create if missing):
- 2026-06-17 — [<title>](${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/page.tsx) — <audience> — <one-line angle>
This compounds — future decks can grep "what talks have I done about X" to avoid repetition and find reusable patterns.
Mode: update
Modify an existing deck without breaking it. Risks: overflowing slides, exceeding density limits, breaking the section ranges.
Before modifying, check:
- Read the existing
page.tsx - Count slides + identify the section ranges
- For each modification, apply the right guard:
| Change | Guard |
|---|---|
| Adding bullets | Max 6 per BulletList in reading-first; 3 in speaker-led. If exceeded → split into two slides. |
| Adding text | If <Body> paragraph >2 sentences in speaker-led → split. >5 sentences in reading-first → split. |
| Adding images | Images must fit in the 1920×1080 stage. If the slide already has heavy content, move image to its own slide. |
| Adding slides | Update sections array — increment from/to for all sections after the insertion point. |
| Removing slides | Decrement from/to for sections after the removal. Watch for orphaned id references. |
| Rewriting a slide | Preserve the id (used for anchors). Only change content and notes. |
After modifying:
- Re-count slides and verify section ranges sum correctly
- Spot-check the visual in the dev server before committing
Mode: ppt
Convert a legacy PPTX (client deck, conference template) into the user's React system.
Extract content via
python3andpython-pptx:pip install python-pptx 2>/dev/null python3 -c " from pptx import Presentation import json, sys p = Presentation(sys.argv[1]) out = [] for i, s in enumerate(p.slides): title = next((sh.text for sh in s.shapes if sh.has_text_frame and sh.shapes_element.tag.endswith('}sp') == False), '') texts = [sh.text for sh in s.shapes if sh.has_text_frame] notes = s.notes_slide.notes_text_frame.text if s.has_notes_slide else '' out.append({'i': i, 'texts': texts, 'notes': notes}) print(json.dumps(out, indent=2)) " "<path-to-pptx>"See
references/ppt-conversion.mdfor the full extraction + mapping recipe.Show the user the extracted summary — slide titles, content excerpts, image count. Confirm before proceeding.
Map to React primitives:
- First slide → title pattern (Eyebrow + h1 + Accent)
- Content slides with bullets →
Heading + BulletList - Comparison slides →
TwoCol - Section breaks (title-only slides in PPTX) → section divider pattern
- Speaker notes from PPTX →
notesarray on each slide
Images — copy referenced images from PPTX assets to
${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/public/slide-assets/<slug>/, reference them in slides via<img src="/slide-assets/<slug>/<filename>" />inside thecontent.Write
page.tsx+layout.tsxper the new-deck flow (Step 6).Preserve the original PPTX — never modify in place. Save under
~/Documents/slide-conversions/<slug>-from-pptx/with the original file + extracted JSON for audit.
Mode: export
Snapshot a deck rendered in your slide-site dev server to portable HTML / PDF / Vercel URL. Output is brand-perfect because it's screenshots of your actual rendered React deck.
Output options:
html— standalone HTML file with snapshots as inline<img>, keyboard nav (arrow keys) baked inpdf— combined slide snapshotsvercel— push standalone HTML to a Vercel project for a shareable URL
Flow (full details in references/export.md):
- Verify dev server: confirm
${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>loads. If not, prompt the user tocd ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site} && npm run dev. - Count slides: read
page.tsx, count entries in theslidesarray. - Run Playwright snapshot:
bash references/export.md script: snapshot-deck <slug> <count>- Launches headless Chromium at 1920×1080
- Loads
http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>?present=1(presenter mode hides chrome) - Sets
localStorage["slides:/slides/<slug>"] = "0"to start at slide 0 - Loops: screenshot → keyboard ArrowRight → wait — for N slides
- Saves PNGs to
~/Documents/slide-exports/<slug>-<YYYY-MM-DD>/slide-<n>.png
- Combine per output type:
html→ wrap snapshots in a minimal HTML shell with arrow-key navigationpdf→ usemagick(ImageMagick) orimg2pdfto combine PNGsvercel→vercel deploy ~/Documents/slide-exports/<slug>-<date>/
- Report path / URL.
Caveats (mention to the user):
- Animations and presenter view are not preserved — exports are static snapshots.
- For interactive demo, present the React version live; for sharing/PDF/portable, use exports.
- Snapshots are 1920×1080 — high-quality on any device, but file size scales with slide count.
Modes (quick invocations)
| Invocation | Mode | Behavior |
|---|---|---|
/slide-deck new <topic> |
new | Full pipeline (Steps 1–8) |
/slide-deck angles <topic> |
new | Stop at Step 2 (just the 3 angles) |
/slide-deck outline <topic> |
new | Stop at Step 3 (outline only) |
/slide-deck expand <slug> |
new | Skip to Step 4 from an existing outline |
/slide-deck notes <slug> |
update | Rewrite speaker notes for an existing deck |
/slide-deck rewrite <slug> <slide-id> |
update | Edit one slide |
/slide-deck update <slug> |
update | General modification with overflow guards |
/slide-deck ppt <pptx-path> |
ppt | Convert legacy PPTX → React deck |
/slide-deck export <slug> html |
export | Snapshot to standalone HTML |
/slide-deck export <slug> pdf |
export | Snapshot to PDF |
/slide-deck export <slug> vercel |
export | Snapshot + Vercel deploy → shareable URL |
/slide-deck preview <slug> |
— | Just check the deck URL |
Composes with
business-brainstorm— if a pitch deck, brainstorm the offer first; deck draws from the briefdecide— for talks that hinge on a decision (e.g., "should I take VC money?"),/decidefirstdeep-research— for talks needing data the deck doesn't have yetsecond-brain— pull relevant[[wiki]]pages as content sources; the deck can cite themwatch-video— turn a podcast episode, Loom, or talk recording into a deck outline (run invisualmode to also capture key moments + slides shown)jab-hook— once delivered, the talk becomes promo angles for the BIP rotationfrontend-skills(external) — for non-branded HTML decks where your React deck repo isn't the home
Notes on quality
- Show, don't tell — applied to narrative. Present 3 different angles for the deck (not one polished draft); let the user pick. Committing to a narrative before the alternatives are surfaced produces decks that are workmanlike, not memorable.
- Density modes matter more than aesthetic. A speaker-led deck has 3-word slides and 5-line speaker notes. A reading-first deck (leave-behind, async) reverses it. Same content, opposite artifact. Ask which mode before drafting.
- Slide text is abbreviated spoken. Both pass the read-aloud test. If a slide reads like a memo, it's wrong. If speaker notes read like slides, they're wrong.
- Overflow guards on update mode. Don't blindly cram more content into an existing slide — if the addition tips the slide over its target word count or line count, propose splitting the slide or trimming existing content. Silently overflowing produces cramped decks.
- Presenter-view stripped for exports. Playwright snapshots the
?present=0mode so the export is clean. Never export the presenter view. - Brand-perfect > portable. The React deck is the source of truth. HTML/PDF/Vercel exports are snapshots for sharing — animations, interactions, presenter view live only in the React version.
- 1920×1080 default, 1280×720 for previews. Full-res snapshots run 30-50 MB for a 20-slide deck; low-res halves that at minimal visual loss.
- Deploy is a real-money operation. Export mode's
vercelpath prompts before deploying. Auto-renew is on for domains + Vercel projects.
Files (makerskills)
-
references
-
export.md 6.5 KB
# Export reference Snapshot a rendered React deck to portable HTML / PDF / Vercel URL via Playwright. Output is brand-perfect because it's actual screenshots of the deck rendered in the dev server. ## Prerequisites | Tool | Install | Check | |---|---|---| | Playwright | `npm install -g playwright && npx playwright install chromium` | `npx playwright --version` | | ImageMagick (for PDF) | `brew install imagemagick` | `magick --version` | | img2pdf (PDF alternative, simpler) | `pip install img2pdf` | `img2pdf --version` | | Vercel CLI | `npm install -g vercel` | `vercel --version` | If any are missing on first run, surface the install command to the user; don't try to install silently. ## Step 1 — Verify dev server ```bash # Ping the dev server (portless-compatible — set SLIDE_DECK_DEV_HOST to e.g. yoursite.localhost:1355) curl -sf -o /dev/null -w "%{http_code}" http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug> && echo " OK" || echo " not running" ``` If not running: ``` Dev server isn't running. Start it with: cd ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site} && npm run dev Then re-run /slide-deck export <slug> <html|pdf|vercel>. ``` Don't auto-start — `npm run dev` is long-running. ## Step 2 — Count slides ```bash # Counts top-level objects in the `slides: Slide[]` array grep -E "^\s+\{$" ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/page.tsx | wc -l ``` Better: open the file and count `id:` entries in the slides array. Don't trust grep for production decks — verify by reading the file. ## Step 3 — Snapshot script Save as `~/Documents/slide-exports/_scripts/snapshot.mjs` (create on first run): ```javascript import { chromium } from 'playwright'; import { mkdir } from 'node:fs/promises'; import path from 'node:path'; const [, , slug, slideCountStr, outDir] = process.argv; const slideCount = parseInt(slideCountStr, 10); const host = process.env.SLIDE_DECK_DEV_HOST || 'localhost:3000'; const baseUrl = `http://${host}/slides/${slug}`; await mkdir(outDir, { recursive: true }); const browser = await chromium.launch(); const ctx = await browser.newContext({ viewport: { width: 1920, height: 1080 } }); const page = await ctx.newPage(); // Reset persisted slide index so we start at slide 0 await page.addInitScript(({ slug }) => { localStorage.setItem(`slides:/slides/${slug}`, '0'); }, { slug }); // Load presenter-stripped view await page.goto(`${baseUrl}?present=0`, { waitUntil: 'networkidle' }); await page.waitForTimeout(500); for (let i = 0; i < slideCount; i++) { const filename = path.join(outDir, `slide-${String(i).padStart(3, '0')}.png`); await page.screenshot({ path: filename, fullPage: false }); console.log(`Saved ${filename}`); if (i < slideCount - 1) { await page.keyboard.press('ArrowRight'); await page.waitForTimeout(400); // let any animation settle } } await browser.close(); console.log(`Done: ${slideCount} slides`); ``` Run: ```bash mkdir -p ~/Documents/slide-exports/<slug>-$(date +%Y-%m-%d) node ~/Documents/slide-exports/_scripts/snapshot.mjs <slug> <count> ~/Documents/slide-exports/<slug>-$(date +%Y-%m-%d) ``` ## Step 4 — Combine per output type ### html (standalone, portable, with arrow-key nav) Generate a single `index.html` next to the PNGs: ```html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title><DECK_TITLE></title> <style> html, body { margin: 0; padding: 0; height: 100%; background: #fff; overflow: hidden; } .stage { display: flex; align-items: center; justify-content: center; height: 100vh; } .stage img { max-width: 100%; max-height: 100%; object-fit: contain; display: none; } .stage img.active { display: block; } .nav { position: fixed; bottom: 1rem; right: 1rem; font: 14px/1 system-ui; color: #4d4d4d; opacity: 0.5; } </style> </head> <body> <div class="stage" id="stage"> <!-- Inject one <img src="slide-000.png" class="active"> per slide --> </div> <div class="nav"><span id="n">1</span> / <span id="t">N</span></div> <script> const slides = document.querySelectorAll('.stage img'); let i = 0; document.getElementById('t').textContent = slides.length; function go(n) { i = Math.max(0, Math.min(slides.length - 1, n)); slides.forEach((s, j) => s.classList.toggle('active', j === i)); document.getElementById('n').textContent = i + 1; } document.addEventListener('keydown', (e) => { if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') go(i + 1); if (e.key === 'ArrowLeft' || e.key === 'PageUp') go(i - 1); if (e.key === 'Home') go(0); if (e.key === 'End') go(slides.length - 1); }); go(0); </script> </body> </html> ``` Inject the `<img>` tags by iterating the PNG filenames in the directory. ### pdf Simplest: `img2pdf` (preserves aspect, smaller output than ImageMagick): ```bash img2pdf ~/Documents/slide-exports/<slug>-<date>/slide-*.png \ -o ~/Documents/slide-exports/<slug>-<date>/<slug>.pdf ``` Alternative: ImageMagick (more flexible, larger output): ```bash magick ~/Documents/slide-exports/<slug>-<date>/slide-*.png \ ~/Documents/slide-exports/<slug>-<date>/<slug>.pdf ``` Open after generation: ```bash open ~/Documents/slide-exports/<slug>-<date>/<slug>.pdf ``` ### vercel (shareable URL) 1. Ensure the standalone `html` has been generated (see html section above) 2. Deploy: ```bash cd ~/Documents/slide-exports/<slug>-<date>/ && vercel --prod --yes ``` 3. Report the URL Vercel returns. First-time only: the user needs to be logged in (`vercel login`). The skill should check `vercel whoami` and prompt if needed. To take a deployed deck down later: `vercel rm <project-name>`. ## Caveats to surface to the user - **Animations are not preserved.** Exports are static slide snapshots — any micro-interactions, presenter view, or animations live only in the React version. - **For interactive demos, present the React version live.** Exports are for sharing, sending, printing, archiving. - **File sizes**: a 20-slide deck at 1920×1080 PNG is ~30–50 MB before PDF compression. PDF output is usually 5–15 MB depending on content. - **Snapshot quality requires the dev server to render fonts and gradients correctly.** If a slide looks wrong in the PNG, it'll look wrong in the export. First-time export of a brand-new deck: open the dev server and eyeball every slide first. ## Optional: low-resolution preview For quick previews / Slack thumbnails, snapshot at 1280×720 instead of 1920×1080. Halves the file size at minimal visual loss: ```javascript // In snapshot.mjs, change: viewport: { width: 1280, height: 720 } ``` -
narrative-and-voice.md 11.1 KB
# Narrative and voice for branded decks ## Voice (LOAD-BEARING — write for the ear, not the page) **Decks are spoken aloud.** Slide text gets read by the presenter (or the audience reads it WHILE the presenter is talking). Speaker notes are spoken verbatim. Write everything as you'd *say* it — not as you'd *write* it. The fastest gut check: **read the slide out loud.** If you stumble, if it sounds like a slide deck, if a friend listening would think "weird thing to say" — rewrite. This rule dominates everything below. For the rare exception (reading-first / async handouts where no one will speak the slides), see the bottom of this file. ### Conversational rules (apply to slide text AND notes) 1. **Use contractions everywhere.** *don't, won't, you're, I'd, we've, can't.* Formal writing strips them; spoken language requires them. A slide without contractions reads like a press release. 2. **Sentences short enough for one breath.** If you have to inhale mid-sentence to finish it, split it. 3. **"You" — never "the audience," "users," "people."** You're talking to a person, not a category. 4. **Spell numbers the way you'd say them.** *"roughly a quarter"* not *"approximately 23.7%."* *"three out of four"* not *"75%."* If precision matters, say the number and then translate ("76% — three in four"). 5. **Cut words you wouldn't say.** *Therefore, furthermore, additionally, in conclusion* — never. *So, also, plus, here's the thing* — sometimes. Match how you actually transition between thoughts when talking. 6. **No nouns that sound smart but feel dead.** *Optimization, utilization, implementation* — replace with verbs (*tightening, using, shipping*). 7. **Read every slide aloud before saving.** If it doesn't sound like something you'd say, it won't sound like something you'd say. ### Voice DNA (carries over from social + copy) 1. **Conviction-coded** — make the audience feel the take, not the hedging 2. **Listener-perspective** — "you'll know X" not "I'll teach you X" 3. **Specific nouns, short sentences, verbs that do work** 4. **Lead with a take, not a question** ### Voice anti-patterns to avoid - "Today I want to talk about…" / "Let me start by…" / "First, let me…" - "I'm excited to be here" (save it for notes, not the slide) - "Has anyone ever…?" (engagement bait) - Emojis as decoration - Generic AI aesthetics in copy ("dive deep," "leverage synergies" — full list below) - Sentences that *parse* on the page but *don't land* in the ear (compound subordinate clauses, parentheticals) - Words you'd never say in real life (*aforementioned, henceforth, utilize*) ## Anti-AI-slop discipline LLMs converge toward generic copy. Hold a higher bar by avoiding these specific patterns: **Banned words/phrases on slides** (use anything else): - *Dive deep, dive in, deep-dive* - *Leverage, unlock, supercharge, turbocharge* - *Game-changing, paradigm shift, next-level* - *Synergies, ecosystems, holistic* - *Robust, scalable, future-proof* - *Empowering, enabling, transforming* - *Journey* (as a metaphor — "their journey," "your journey") - *Solutions* (as a noun standalone — "marketing solutions") - *Best-in-class, world-class, cutting-edge* - "*In today's fast-paced world…*" / "*Now more than ever…*" / "*In an era of…*" **Banned slide patterns:** - Definitions on slide 1 ("Marketing is…") - Three bullets that are just three rephrasings of the same idea - A heading that doesn't survive being read aloud ("Optimizing Customer Engagement Through Strategic Initiatives") - Stat with no source ("76% of CMOs say…") — either name the source or drop the stat - Trailing question to invite engagement ("So what do you think?") **Test:** if a slide's heading could appear unchanged in a generic SaaS pitch deck, rewrite it. Specific > vague every time. The voice should be unmistakably the user's. If you can imagine three other marketers reading the same slide and it landing, it's not voiced enough. ### Slide text vs speaker notes — the gap is small Both get spoken. The difference: - **Slide text** = the headline of what you're saying. Tight, scannable, the *take*. Still conversational — still uses contractions, still reads aloud naturally — but fewer words. - **Speaker notes** = the full spoken delivery. More room for asides, callbacks, "you know," story digressions, second-person directness. Still in your voice — just looser. Old framing said slide text was "written the user" and notes were "spoken the user." That's wrong — slide text is *abbreviated spoken the user*. Both pass the read-aloud test. ## Narrative patterns (pick one, don't force any) ### Take-first 1. Open with the take as the title slide (e.g., *"Marketing Like an Engineer"*) 2. Brief moment of surreal/unexpected stat or image (slide 2) 3. Frame the problem the take solves 4. Walk through the system / framework 5. Show it working with examples 6. Close with the action the audience takes Monday Best for: keynotes, conference talks, opinionated frameworks. ### Story-first 1. Open inside a specific moment (a client conversation, a screenshot, a number) 2. Pull back: this isn't unusual — it's common 3. Why it happens 4. The reframe / framework 5. Concrete next step Best for: persuasion talks, sales decks, fundraising pitches. ### Problem → Reframe → System → Close 1. Show the broken thing the audience knows 2. The reframe (why their current model misses) 3. The system that fixes it 4. The path forward Best for: pitches, internal updates that change direction. ### Anti-thesis 1. Show the conventional wisdom 2. Take it apart 3. What's actually true 4. What to do with that Best for: contrarian talks, hot-take keynotes. ### Single-question deck The whole deck answers ONE question, building up the answer slide by slide. Best for: short decks (5–10 slides), lightning talks. ## Slide-level patterns ### Hooks that work (slide 2 / right after title) - A specific number that's bigger or smaller than expected ("$120k saved by removing one button") - A counterintuitive observation ("the more we asked customers, the less they bought") - A short story with a named person and place ("Last week, a founder asked me…") - A surreal moment / image that earns "wait, what?" ### Hooks that don't work - Definitions ("Marketing is…") - Generic stats from a McKinsey report - Questions - "Today I want to share…" ## Speaker notes discipline Notes are the *full spoken talk*. If a presenter could read just the notes (not the slides) and deliver the talk, the notes are doing their job. Write them as you'd actually say them — pauses, callbacks, asides included. Structure per slide (3–5 lines): 1. **Opener** — the exact phrase you say when the slide appears 2. **Point** — the one idea this slide makes 3. **Support** — example, data, color 4. **Transition** — segue into the next slide 5. *(Optional)* **Aside** — quip, callback, audience read Title slides and section dividers get 2–3 notes lines, not 5. ### Notes-specific tics that work (since notes are pure spoken) - "Here's the thing —" / "So check this out —" - Direct address: "You've probably felt this…" - Callbacks to earlier slides ("Remember when I said X? This is why.") - Mini-stories: "Last quarter, a client told me…" - One-line laughs / asides — fine in notes, awkward on slides ### Notes-specific tics to skip - "I'm going to talk about…" (just talk about it) - Restating the slide verbatim — notes should *say more* than the slide does - Reading numbers or bullets aloud — translate them to natural speech ("the first one" not "bullet point one") ## Length calibration | Slide count | Format | Use case | |---|---|---| | 5–10 | Lightning / single-question | Internal update, 5-min talk, demo intro | | 10–15 | Short keynote / pitch | 15-min conference slot, sales pitch, board update | | 20–25 | Keynote | 20–30 min headliner talk | | 30+ | Workshop / training | Deep teaching, less appropriate for branded decks | For pitch decks specifically: 10–15 slides is the sweet spot. 20+ becomes a doc, not a deck. ## Density modes Borrowed from `frontend-slides`. Ask the user up front — affects slide count, copy length per slide, and which primitives to favor. ### speaker-led (low density) Best for: public talks, keynotes, conference slots, live explanation. Rules: - **One idea per slide.** If two ideas are fighting, split. - **Large type, lots of negative space.** Let the slide breathe. - **Max 3 bullets per `BulletList`** — and consider whether bullets are even right (vs. one strong line). - **`<Body>` paragraphs: ≤2 sentences** on a single slide. Beyond that, split. - **More slides, not denser slides.** A 20-min talk might be 30 speaker-led slides — that's fine, each slide is short. - **Favor: title slide, hook slides, single-quote slides, section dividers, framework slide with 3-bullet list, close with one CTA line.** - **Avoid: dense tables, multi-column comparisons with long content, big numbered lists.** ### reading-first (high density) Best for: reports, async handouts, async review, detailed internal docs, post-meeting recap. Rules: - **Self-contained slides.** The audience reads alone — no presenter to fill in gaps. - **4–6 bullets per `BulletList` allowed.** Up to 6 cards in a grid. - **Structured layouts**: TwoCol comparisons, tables, annotated lists, captions. - **Strong hierarchy** — `Eyebrow → Heading → Body → BulletList` so the reader can scan. - **Fewer slides, denser slides.** A 10-page reading-first deck conveys what a 25-page speaker-led deck would. - **Speaker notes optional** (no one's reading them; audience reads the slide). - **Favor: TwoCol, BulletList with longer items, framework slides with explanations under each bullet, tables.** **Voice exception:** the "write for the ear" rule at the top relaxes here. Reading-first decks are *read silently*, so written voice is fine (more formal sentences, fuller words, longer constructions). Still avoid AI-slop banned terms — those are bad written too. But the conversational gut check (read aloud, contractions everywhere) doesn't apply to reading-first decks. ### When stated needs are mixed Pick the closer mode rather than inventing a middle. Live audience persuasion → speaker-led. Async circulation or detailed review → reading-first. Never let high density become visual clutter — if a slide starts to overflow, split it. ## Section discipline When using sections (`SectionRange[]`): - 3–7 sections per deck (more than 7 = sprawl) - Each section is named with a noun phrase, not a question - The first section is always `Title` (single slide) - The last section is always `Close` or `CTA` - Section names appear in the presenter view "where am I" header --- ## Local overlay (your personal voice DNA) Same pattern as `jab-hook/references/voice.md`: layer personal rules on top via `${MAKERSKILLS_CONFIG:-$HOME/.config/makerskills}/slide-deck/voice.local.md`. The skill loads generic rules first, then your overlay. This is the right place for: - Your stage voice tics - Your specific anti-AI-slop additions (the public list is solid; you may have personal additions) - Speaker-note conventions unique to your delivery - Brand-specific narrative patterns -
ppt-conversion.md 5.4 KB
# PPT conversion reference Convert a legacy PowerPoint deck (client deck, conference template, vendor pitch) into the user's React system. Preserves text, structure, speaker notes, and images. ## Prerequisites ```bash pip install python-pptx ``` ## Step 1 — Extract content Save the script at `~/Documents/slide-conversions/_scripts/extract_pptx.py`: ```python #!/usr/bin/env python3 """Extract titles, body text, speaker notes, and image refs from a PPTX.""" import json import sys from pathlib import Path from pptx import Presentation from pptx.util import Emu def extract(pptx_path: str, out_dir: str) -> None: out = Path(out_dir) assets = out / "assets" out.mkdir(parents=True, exist_ok=True) assets.mkdir(exist_ok=True) p = Presentation(pptx_path) slides = [] for i, slide in enumerate(p.slides): title = "" bodies = [] images = [] for shape in slide.shapes: if shape.has_text_frame: text = shape.text_frame.text.strip() if not text: continue # Heuristic: first non-empty text on a slide is the title if not title: title = text else: bodies.append(text) elif shape.shape_type == 13: # PICTURE image = shape.image ext = image.ext fname = f"slide-{i:03d}-{len(images)}.{ext}" (assets / fname).write_bytes(image.blob) images.append({ "filename": fname, "alt": "", }) notes = "" if slide.has_notes_slide: notes = slide.notes_slide.notes_text_frame.text.strip() slides.append({ "index": i, "title": title, "bodies": bodies, "notes": notes, "images": images, }) (out / "extracted.json").write_text(json.dumps(slides, indent=2)) print(f"Extracted {len(slides)} slides → {out}/extracted.json") print(f"Assets ({sum(len(s['images']) for s in slides)} images) → {assets}/") if __name__ == "__main__": if len(sys.argv) < 3: print("Usage: extract_pptx.py <input.pptx> <output-dir>") sys.exit(1) extract(sys.argv[1], sys.argv[2]) ``` Run: ```bash python3 ~/Documents/slide-conversions/_scripts/extract_pptx.py \ "<path-to-pptx>" \ ~/Documents/slide-conversions/<slug>-from-pptx/ ``` ## Step 2 — Confirm summary with the user Read `extracted.json`. Present a one-line-per-slide summary: ``` Slide 1: "Title — Brand Name" Slide 2: "Agenda" — 4 bullets Slide 3: "Problem statement" — 2-paragraph body, 1 image ... Slide 18: "Thank you" — speaker notes only ``` Get confirmation before generating the React file (the LLM should sanity-check, not assume the extraction is perfect — PPTX is messy). ## Step 3 — Map to React primitives For each extracted slide, choose a primitive composition based on its shape: | Extracted shape | React mapping | |---|---| | Title-only (no body) | Title slide pattern OR section divider | | Title + 1 paragraph | `Heading + Body` | | Title + bullet list (3–6 items) | `Heading + BulletList` | | Title + 2 columns | `Heading + TwoCol` | | Title + image, no body | `Heading + <img>` centered | | Title + image + body | `TwoCol` with image on one side, body other | | Big quote | Pull quote pattern (see `template.md`) | | Final / thank-you slide | Close/CTA pattern | ## Step 4 — Generate the React file Write to `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/`: - `layout.tsx` (use the deck title from slide 1) - `page.tsx` (full Slide[] array) For images: 1. Copy from `~/Documents/slide-conversions/<slug>-from-pptx/assets/` to `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/public/slide-assets/<slug>/` 2. Reference in slides via `<img src="/slide-assets/<slug>/<filename>" alt="..." className="max-h-[60vh] mx-auto" />` ## Step 5 — Speaker notes Speaker notes in the PPTX → `notes` array on the corresponding Slide. Split notes on sentence boundaries or empty lines; aim for 3–5 lines per slide per the user's voice rules. If a PPTX slide has no notes, generate 3 starter notes lines based on the slide's content. Mark these with a `// TODO: review` comment so the user can refine in their voice. ## Step 6 — Apply voice + density rules The extracted content is in whatever voice the original deck had — likely corporate. After mapping to primitives, do a voice pass: - Apply the user's anti-AI-slop rules (`references/narrative-and-voice.md`) - Rewrite generic headings into specific, take-coded ones - Trim wordy bullets — aim for 3–6 words each - Honor the chosen density mode (speaker-led vs reading-first) This is the most subjective step. When in doubt, show the user the original and proposed rewrite side by side and let them pick. ## Step 7 — Archive the conversion Keep the original PPTX + extracted JSON forever. Never modify the source. ``` ~/Documents/slide-conversions/<slug>-from-pptx/ ├── original.pptx # untouched copy ├── extracted.json # parsed structure ├── assets/ # images extracted from PPTX │ └── slide-XXX-N.png └── conversion-notes.md # any decisions made during mapping (the user-readable audit) ``` The audit lets future conversions of similar decks reuse the mapping decisions. -
system.md 5.9 KB
# Slide-deck system reference Reference layout for the Next.js repo that hosts your branded React decks. Adopt the file structure below, then point `SLIDE_DECK_REPO` at your repo path. ## Locations | What | Path | |---|---| | **Deck routes** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/page.tsx` | | **Per-deck layout** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/layout.tsx` | | **Shared primitives** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/components/slides/slide-primitives.tsx` | | **SlideDeck component** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/components/slides/slide-deck.tsx` | | **Sections types** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/components/slides/sections.ts` | | **Brand gradients** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/lib/gradients.ts` (12 cycling, applied automatically) | | **Existing decks (style refs)** | `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/marketing-like-an-engineer/page.tsx`, `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/skill-for-your-skills/page.tsx` | ## Imports a `page.tsx` typically needs ```typescript "use client"; import { SlideDeck, type Slide } from "@/components/slides/slide-deck"; import type { SectionRange } from "@/components/slides/sections"; import { Eyebrow, Heading, Accent, Body, BulletList, Divider, TwoCol, GradientText, } from "@/components/slides/slide-primitives"; ``` Only import the primitives the deck actually uses. ## Primitives | Primitive | Use | Notes | |---|---|---| | `<Eyebrow>` | Small all-caps label above a heading | Section tag, slide kind, "Keynote" / "Chapter 1" etc. | | `<Heading>` | Slide-level header (`<h2>`-equivalent) | Most slides have one | | `<Accent>` | Inline keyword styled with the slide's gradient | Use in `<h1>` or `<Heading>`. One per slide max. | | `<GradientText>` | Same as Accent but takes `className` for custom layout | Use when `<Accent>` doesn't fit | | `<Body>` | Paragraph text | Soft gray, max-width-2xl, relaxed leading | | `<BulletList items={[]}>` | Tight 3–7 bullet list | Pass array of strings | | `<Divider>` | Dashed horizontal rule | Use sparingly to break up content | | `<TwoCol left={} right={}>` | Two-column grid | Comparison, before/after, problem/solution | ## Title-slide pattern (from `marketing-like-an-engineer`) ```tsx { id: "title", content: ( <div className="flex flex-col justify-center h-full max-w-3xl"> <p className="text-sm uppercase tracking-[0.08em] font-heading font-medium text-[#4d4d4d]/40 mb-4"> Keynote </p> <h1 className="font-heading font-medium text-[4.236rem] leading-[1.05] tracking-[-0.02em] text-[#4d4d4d] mb-6 text-balance"> Marketing Like an <Accent>Engineer</Accent> </h1> <p className="text-[#4d4d4d]/50 text-xl font-heading"> <SPEAKER_NAME> </p> </div> ), notes: [ "Welcome everyone — excited to be here.", "This talk is about applying the engineering mindset to marketing.", "By the end you'll have a framework you can take home and start building Monday.", ], } ``` Use this as the title slide template. Swap copy + adjust `<Accent>` to the topic's keyword. ## Slide type ```typescript type Slide = { id: string; // kebab-case, unique per deck (used for anchors) content: React.ReactNode; notes?: string[]; // 3–5 lines, complete spoken thoughts }; ``` ## Sections type + pattern ```typescript type SectionRange = { name: string; from: number; to: number }; ``` `from` / `to` are **zero-indexed slide indices** (slide 1 = index 0). Example from `marketing-like-an-engineer`: ```typescript const sections: SectionRange[] = [ { name: "Title", from: 0, to: 0 }, { name: "Intro", from: 1, to: 4 }, { name: "Takeaways", from: 5, to: 6 }, { name: "Reframe marketing", from: 7, to: 11 }, { name: "The system", from: 12, to: 18 }, { name: "All roads lead to Rome", from: 19, to: 19 }, { name: "Close", from: 20, to: 22 }, ]; ``` Sections drive the presenter view's "where am I" context. Use them for keynote-length decks. Optional for short decks. ## Component invocation ```tsx export default function MyDeckPage() { return <SlideDeck slides={slides} sections={sections} />; } ``` `sections` is optional. ## layout.tsx pattern ```tsx import type { Metadata } from "next"; export const metadata: Metadata = { title: "<Deck Title>", }; export default function <DeckName>Layout({ children, }: { children: React.ReactNode; }) { return <>{children}</>; } ``` The `title` becomes the browser tab title when presenting. ## Brand colors used in primitives - **Text**: `#4d4d4d` (charcoal) - **Muted text**: `#4d4d4d` with opacity (`/40`, `/45`, `/50`, `/70`, `/80`) - **Font heading**: `font-heading` (custom class) for display sizes - **Brand gradients**: 12 in rotation, applied automatically via CSS vars — don't hardcode gradient colors per slide ## Comment patterns from existing decks Use these horizontal rules as section markers in the slides array — they make the file scan-able: ```typescript // ─── Slides ─────────────────────────────────────────────────────────────────── // ═══════════════════════════════════════════════════════════════════════════ // ACT 1 — HOOK & FRAME // ═══════════════════════════════════════════════════════════════════════════ // 1. Title { ... }, // 2. Surreal moment { ... }, ``` Comments aren't strictly necessary but help when editing a 20+ slide deck. -
template.md 6.4 KB
# File templates Drop-in skeletons for `page.tsx` and `layout.tsx`. Substitute `<TOKENS>`. --- ## `layout.tsx` ```tsx import type { Metadata } from "next"; export const metadata: Metadata = { title: "<DECK_TITLE>", }; export default function <DeckNameCamelCase>Layout({ children, }: { children: React.ReactNode; }) { return <>{children}</>; } ``` --- ## `page.tsx` — minimal (no sections) ```tsx "use client"; import { SlideDeck, type Slide } from "@/components/slides/slide-deck"; import { Eyebrow, Heading, Accent, Body, BulletList, } from "@/components/slides/slide-primitives"; // ─── Slides ─────────────────────────────────────────────────────────────────── const slides: Slide[] = [ // 1. Title { id: "title", content: ( <div className="flex flex-col justify-center h-full max-w-3xl"> <p className="text-sm uppercase tracking-[0.08em] font-heading font-medium text-[#4d4d4d]/40 mb-4"> <ROOM_KIND_OR_EYEBROW> </p> <h1 className="font-heading font-medium text-[4.236rem] leading-[1.05] tracking-[-0.02em] text-[#4d4d4d] mb-6 text-balance"> <DECK_TITLE_LINE_1> <Accent><DECK_TITLE_KEYWORD></Accent> </h1> <p className="text-[#4d4d4d]/50 text-xl font-heading"> <SPEAKER_NAME> </p> </div> ), notes: [ "<OPENING_LINE>", "<ONE_LINE_FRAME>", "<WHY_THEY_SHOULD_CARE>", ], }, // 2. <SLIDE_NAME> { id: "<slide-id>", content: ( <div className="flex flex-col justify-center h-full max-w-3xl"> <Eyebrow><SECTION_NAME></Eyebrow> <Heading><SLIDE_HEADLINE> <Accent>keyword</Accent></Heading> <Body> <PARAGRAPH> </Body> </div> ), notes: [ "<OPENER>", "<POINT>", "<SUPPORT>", "<TRANSITION>", ], }, // ... more slides ]; // ─── Page ───────────────────────────────────────────────────────────────────── export default function <DeckNameCamelCase>Page() { return <SlideDeck slides={slides} />; } ``` --- ## `page.tsx` — with sections (keynote-length) ```tsx "use client"; import { SlideDeck, type Slide } from "@/components/slides/slide-deck"; import type { SectionRange } from "@/components/slides/sections"; import { Eyebrow, Heading, Accent, Body, BulletList, Divider, TwoCol, } from "@/components/slides/slide-primitives"; // ─── Slides ─────────────────────────────────────────────────────────────────── const slides: Slide[] = [ // (slides here — see minimal template) ]; // ─── Sections ───────────────────────────────────────────────────────────────── const sections: SectionRange[] = [ { name: "Title", from: 0, to: 0 }, { name: "<SECTION_NAME>", from: 1, to: 4 }, { name: "<SECTION_NAME>", from: 5, to: 11 }, { name: "<SECTION_NAME>", from: 12, to: 18 }, { name: "Close", from: 19, to: 22 }, ]; // ─── Page ───────────────────────────────────────────────────────────────────── export default function <DeckNameCamelCase>Page() { return <SlideDeck slides={slides} sections={sections} />; } ``` --- ## Slide primitives — common compositions ### Section divider (mid-deck) ```tsx { id: "divider-<n>", content: ( <div className="flex flex-col justify-center items-center h-full"> <Eyebrow>Part <n></Eyebrow> <h2 className="font-heading font-medium text-[4rem] leading-[1.05] tracking-[-0.02em] text-[#4d4d4d] text-balance text-center max-w-3xl"> <SECTION_TITLE> </h2> </div> ), notes: [ "<TRANSITION_FROM_PREVIOUS_SECTION>", "<WHAT_WE'RE_ABOUT_TO_COVER>", ], }, ``` ### Framework / bullet list ```tsx { id: "<slide-id>", content: ( <div className="flex flex-col justify-center h-full max-w-3xl"> <Eyebrow><SECTION_NAME></Eyebrow> <Heading>The <Accent><N></Accent> things</Heading> <BulletList items={[ "<bullet 1>", "<bullet 2>", "<bullet 3>", ]} /> </div> ), notes: [ "<OPENER>", "<POINT_ABOUT_THE_FRAMEWORK>", "<WHY_THIS_COUNT_MATTERS>", "<TRANSITION>", ], }, ``` ### Two-column comparison ```tsx { id: "<slide-id>", content: ( <div className="flex flex-col justify-center h-full max-w-5xl"> <Eyebrow><SECTION_NAME></Eyebrow> <Heading><SLIDE_HEADLINE></Heading> <TwoCol left={ <> <p className="text-sm uppercase tracking-wider text-[#4d4d4d]/50 mb-2">Before</p> <Body><LEFT_CONTENT></Body> </> } right={ <> <p className="text-sm uppercase tracking-wider text-[#4d4d4d]/50 mb-2">After</p> <Body><RIGHT_CONTENT></Body> </> } /> </div> ), notes: [ "<OPENER>", "<LEFT_SIDE_EXPLAINED>", "<RIGHT_SIDE_EXPLAINED>", "<THE_DIFFERENCE_THAT_MATTERS>", ], }, ``` ### Pull quote / standout ```tsx { id: "<slide-id>", content: ( <div className="flex flex-col justify-center h-full max-w-3xl"> <p className="text-[2.618rem] leading-[1.15] font-heading font-light text-[#4d4d4d] text-balance"> "<QUOTE_TEXT>" </p> <p className="text-[#4d4d4d]/50 text-base mt-6 font-heading">— <ATTRIBUTION></p> </div> ), notes: [ "<WHY_THIS_QUOTE>", "<WHAT_IT_REVEALS>", ], }, ``` ### Close / CTA ```tsx { id: "close", content: ( <div className="flex flex-col justify-center h-full max-w-3xl"> <Eyebrow>Close</Eyebrow> <Heading>One thing to <Accent>do Monday</Accent></Heading> <Body> <ONE_SENTENCE_CTA> </Body> <p className="text-[#4d4d4d]/40 text-base mt-12 font-heading"> <AUTHOR_HANDLE> · <AUTHOR_SITE> </p> </div> ), notes: [ "<RECAP_THE_TAKE>", "<THE_ONE_ACTION>", "<HOW_TO_REACH_ME>", "<THANK_THE_AUDIENCE>", ], }, ```
-
-
SKILL.md 17 KB
--- name: slide-deck description: When you want to draft, update, convert, or export a slide deck for a React/Next.js slide system (${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/). Writes TypeScript Slide[] arrays using your primitives (Eyebrow, Heading, Accent, Body, BulletList, Divider, TwoCol, GradientText), 12 cycling brand gradients, optional sections for "where am I" context, and speaker notes. Inspired by zarazhangrui/frontend-slides — "show, don't tell" applied to narrative (presents 3 angles, you pick) plus density modes (speaker-led vs reading-first). Four modes — new (draft from brief), update (modify existing, with overflow guards), ppt (convert legacy PPTX → React deck), export (Playwright snapshot existing React deck to standalone HTML, PDF, or Vercel URL — keeps brand). Triggers on "/slide-deck," "/slides new," "/slides export," "/slides update," "/slides ppt," "draft a deck," "deck for [topic]," "talk on [topic]," "keynote on [topic]," "internal deck for [audience]," "convert this pptx," "export this deck." metadata: version: 0.2.0 --- # /slide-deck — Draft, update, convert, and export branded React decks Authors React/TypeScript decks for `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/page.tsx` using the user's slide system. Branded output (no separate HTML pipeline) — when portable HTML/PDF is needed, export mode snapshots the rendered React deck via Playwright so the output is brand-perfect. ## Modes (pick one before Step 1) | Mode | Invocation | Goal | |---|---|---| | **new** | `/slide-deck new <topic>` (default) | Draft a new deck from a brief | | **update** | `/slide-deck update <slug>` | Modify an existing deck (with overflow guards) | | **ppt** | `/slide-deck ppt <path-to-pptx>` | Convert a legacy PPTX into a React deck | | **export** | `/slide-deck export <slug> [html\|pdf\|vercel]` | Snapshot a deck to HTML, PDF, or Vercel URL | For `update`, `ppt`, and `export`, skip to the corresponding mode section below. For `new`, continue through Steps 1–8. --- ## Step 1 — Capture the brief (ask all at once) Ask in one structured message. Don't round-trip on each. 1. **Topic / title** — what's the deck about? 2. **Audience** — who's in the room? (technical / executive / mixed / clients) Push for specificity: "founders" is too broad; "B2B SaaS founders at MicroConf" is workable. 3. **Length** — rough slide count: 5–10 (lightning / short internal) / 10–15 (short keynote, pitch) / 20–25 (keynote) / 30+ (workshop — uncommon for branded decks). 4. **Density** — *speaker-led* (1 idea/slide, big type, lots of breathing room, more slides if needed) or *reading-first* (4–6 bullets/slide, structured grids, self-contained for async review). 5. **CTA** — what should the audience do/think/feel after? 6. **Slug** — kebab-case (e.g., `marketing-like-an-engineer`). 7. **Content state** — all content ready / rough notes / topic only. Remember density — it affects slide count, copy length per slide, and which primitives to favor. See `references/narrative-and-voice.md` for the density-specific rules. ## Step 2 — Three narrative angles Riff on `zarazhangrui/frontend-slides`' "show, don't tell" — but applied to *story*, not visuals (your visual system is fixed). Pitch 3 angles in 1–2 sentences each: - **Safe** — most likely to land. Conventional structure for the audience. - **Bold** — contrarian or counterintuitive frame. Higher upside, slight risk. - **Wildcard** — unexpected structure (story-first, single-question deck, anti-thesis, etc.). The user picks one. If they reject all three, propose three more — don't force a path. ## Step 3 — Outline For the chosen angle: 1. **Sections** — propose 3–7 named sections (e.g., `Title`, `Hook`, `Problem`, `Framework`, `Examples`, `Close`). The default 3-act structure is OPTIONAL; only use it if the user wants it or the deck is a keynote-length talk that benefits from one. 2. **Slide titles** within each section 3. **One-line takeaway** per slide Total slide count should match the duration estimate from Step 1. Show the outline as a table. the user edits / approves before expand. ## Step 4 — Expand to slide content For each slide, write the full content using the user's primitives. Reference `references/system.md` for the primitive vocabulary and `references/narrative-and-voice.md` for hook patterns and voice rules. Slide types and which primitives fit: | Slide type | Primitives | Pattern | |---|---|---| | Title | `Eyebrow` + `<h1>` with `<Accent>` keyword | First slide, sets brand and topic | | Hook | `Heading` + `Body` | A take, story open, contrarian frame, or specific stat | | Section divider | `Eyebrow` + `Heading` (centered, large) | Clean break between sections | | Framework | `Heading` + `BulletList` or custom layout | The thing the user's teaching | | Two-column | `TwoCol` | Comparison, before/after, problem/solution | | Quote / pull-quote | `Body` (large) | Authority or audience-recognition moment | | Resource / link | `Body` + URL on its own line | Outbound (rare — the user's voice says minimize) | | Close / CTA | `Heading` + `Body` + `BulletList` for next steps | What the audience should do | **Voice anchors — write for the ear, not the page** (see `narrative-and-voice.md` for full rules): - **Decks are spoken aloud.** Slide text + speaker notes both get said out loud. Write as you'd talk. - **Read every slide out loud before saving.** If you stumble, if it sounds like a press release, rewrite. - **Contractions everywhere** (*don't, won't, you're, I'd*). Strip them only on reading-first decks. - **"You" — never "the audience," "users," "people."** - **Numbers said naturally** ("a quarter" not "23.7%"). - Conviction-coded, listener-perspective, short sentences, specific nouns. - No filler ("Today I want to talk about…") — open with a take. - (Reading-first density mode exception: written voice is fine since no one will speak the slides — see `narrative-and-voice.md`.) ## Step 5 — Speaker notes Every slide gets 3–5 notes lines. Notes are *the full spoken talk* — write them the way you'd actually say them, pauses and asides included. Slide text is the headline; notes are the full thought. Notes structure: 1. **The opener** — what you say when the slide comes up 2. **The point** — the one idea this slide is making 3. **The supporting beat** — example, data, or color 4. **The transition** — how this connects to the next slide 5. **(Optional) The aside** — a quip or callback For title slides and section dividers, 2–3 notes is fine. ## Step 6 — Generate files Write to `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/`: 1. `layout.tsx` — Next.js layout with `<title>` metadata (use the deck title) 2. `page.tsx` — the slide deck Use the templates in `references/template.md` as the skeleton. The `<AUTHOR_HANDLE>` / `<AUTHOR_SITE>` tokens (close-slide footer) come from `${MAKERSKILLS_CONFIG:-$HOME/.config/makerskills}/slide-deck/identity.yaml` (keys: `handle`, `site`) if it exists. Otherwise ask the user for their handle and site once, offer to save them there, and use those. If they don't want a footer, drop the `<p>` entirely — never ship a deck with someone else's identity on it. `page.tsx` must: - Have `"use client"` at the top - Import `SlideDeck` and `Slide` type from `@/components/slides/slide-deck` - Import `SectionRange` from `@/components/slides/sections` (only if using sections) - Import the primitives the user uses from `@/components/slides/slide-primitives` - Declare `const slides: Slide[] = [...]` - (Optional) Declare `const sections: SectionRange[] = [...]` with 0-indexed `from`/`to` - Export default a component that returns `<SlideDeck slides={slides} sections={sections} />` ## Step 7 — Preview After writing the files, check if your slide-deck dev server is running. ```bash lsof -i :3000-3099 2>/dev/null | grep -E "LISTEN" | head ``` If a port is in use (portless-compatible — check `package.json` for the dev script naming): ```bash # Suggested URL pattern echo "Preview at: http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>" ``` If no dev server is running, tell the user: ```bash cd ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site} && npm run dev # then visit http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug> ``` Don't auto-start the dev server (might disrupt other work). ## Step 8 — Archive The archive lives in `${MAKERSKILLS_CONFIG:-$HOME/.config/makerskills}/slide-deck/archive/` (create the directory if missing). Never write the archive inside the skill's own folder — skill installs and upgrades re-sync from source and wipe anything saved there. **Migration:** if this skill's folder contains an old `references/decks-archive.md` with user entries, move it to `<archive dir>/INDEX.md` first. Append a one-liner to `<archive dir>/INDEX.md` (create if missing): ```markdown - 2026-06-17 — [<title>](${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/src/app/slides/<slug>/page.tsx) — <audience> — <one-line angle> ``` This compounds — future decks can grep "what talks have I done about X" to avoid repetition and find reusable patterns. --- ## Mode: update Modify an existing deck without breaking it. Risks: overflowing slides, exceeding density limits, breaking the section ranges. **Before modifying, check:** 1. Read the existing `page.tsx` 2. Count slides + identify the section ranges 3. For each modification, apply the right guard: | Change | Guard | |---|---| | **Adding bullets** | Max 6 per `BulletList` in reading-first; 3 in speaker-led. If exceeded → split into two slides. | | **Adding text** | If `<Body>` paragraph >2 sentences in speaker-led → split. >5 sentences in reading-first → split. | | **Adding images** | Images must fit in the 1920×1080 stage. If the slide already has heavy content, move image to its own slide. | | **Adding slides** | Update `sections` array — increment `from`/`to` for all sections after the insertion point. | | **Removing slides** | Decrement `from`/`to` for sections after the removal. Watch for orphaned `id` references. | | **Rewriting a slide** | Preserve the `id` (used for anchors). Only change `content` and `notes`. | **After modifying:** - Re-count slides and verify section ranges sum correctly - Spot-check the visual in the dev server before committing --- ## Mode: ppt Convert a legacy PPTX (client deck, conference template) into the user's React system. 1. **Extract content** via `python3` and `python-pptx`: ```bash pip install python-pptx 2>/dev/null python3 -c " from pptx import Presentation import json, sys p = Presentation(sys.argv[1]) out = [] for i, s in enumerate(p.slides): title = next((sh.text for sh in s.shapes if sh.has_text_frame and sh.shapes_element.tag.endswith('}sp') == False), '') texts = [sh.text for sh in s.shapes if sh.has_text_frame] notes = s.notes_slide.notes_text_frame.text if s.has_notes_slide else '' out.append({'i': i, 'texts': texts, 'notes': notes}) print(json.dumps(out, indent=2)) " "<path-to-pptx>" ``` See `references/ppt-conversion.md` for the full extraction + mapping recipe. 2. **Show the user the extracted summary** — slide titles, content excerpts, image count. Confirm before proceeding. 3. **Map to React primitives**: - First slide → title pattern (Eyebrow + h1 + Accent) - Content slides with bullets → `Heading + BulletList` - Comparison slides → `TwoCol` - Section breaks (title-only slides in PPTX) → section divider pattern - Speaker notes from PPTX → `notes` array on each slide 4. **Images** — copy referenced images from PPTX assets to `${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site}/public/slide-assets/<slug>/`, reference them in slides via `<img src="/slide-assets/<slug>/<filename>" />` inside the `content`. 5. **Write `page.tsx` + `layout.tsx`** per the new-deck flow (Step 6). 6. **Preserve the original PPTX** — never modify in place. Save under `~/Documents/slide-conversions/<slug>-from-pptx/` with the original file + extracted JSON for audit. --- ## Mode: export Snapshot a deck rendered in your slide-site dev server to portable HTML / PDF / Vercel URL. Output is brand-perfect because it's screenshots of your actual rendered React deck. **Output options:** - `html` — standalone HTML file with snapshots as inline `<img>`, keyboard nav (arrow keys) baked in - `pdf` — combined slide snapshots - `vercel` — push standalone HTML to a Vercel project for a shareable URL **Flow** (full details in `references/export.md`): 1. **Verify dev server**: confirm `${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>` loads. If not, prompt the user to `cd ${SLIDE_DECK_REPO:-$HOME/code/your-slide-deck-site} && npm run dev`. 2. **Count slides**: read `page.tsx`, count entries in the `slides` array. 3. **Run Playwright snapshot**: ```bash bash references/export.md script: snapshot-deck <slug> <count> ``` - Launches headless Chromium at 1920×1080 - Loads `http://${SLIDE_DECK_DEV_HOST:-localhost:3000}/slides/<slug>?present=1` (presenter mode hides chrome) - Sets `localStorage["slides:/slides/<slug>"] = "0"` to start at slide 0 - Loops: screenshot → keyboard ArrowRight → wait — for N slides - Saves PNGs to `~/Documents/slide-exports/<slug>-<YYYY-MM-DD>/slide-<n>.png` 4. **Combine** per output type: - `html` → wrap snapshots in a minimal HTML shell with arrow-key navigation - `pdf` → use `magick` (ImageMagick) or `img2pdf` to combine PNGs - `vercel` → `vercel deploy ~/Documents/slide-exports/<slug>-<date>/` 5. **Report** path / URL. **Caveats** (mention to the user): - Animations and presenter view are not preserved — exports are static snapshots. - For interactive demo, present the React version live; for sharing/PDF/portable, use exports. - Snapshots are 1920×1080 — high-quality on any device, but file size scales with slide count. --- ## Modes (quick invocations) | Invocation | Mode | Behavior | |---|---|---| | `/slide-deck new <topic>` | new | Full pipeline (Steps 1–8) | | `/slide-deck angles <topic>` | new | Stop at Step 2 (just the 3 angles) | | `/slide-deck outline <topic>` | new | Stop at Step 3 (outline only) | | `/slide-deck expand <slug>` | new | Skip to Step 4 from an existing outline | | `/slide-deck notes <slug>` | update | Rewrite speaker notes for an existing deck | | `/slide-deck rewrite <slug> <slide-id>` | update | Edit one slide | | `/slide-deck update <slug>` | update | General modification with overflow guards | | `/slide-deck ppt <pptx-path>` | ppt | Convert legacy PPTX → React deck | | `/slide-deck export <slug> html` | export | Snapshot to standalone HTML | | `/slide-deck export <slug> pdf` | export | Snapshot to PDF | | `/slide-deck export <slug> vercel` | export | Snapshot + Vercel deploy → shareable URL | | `/slide-deck preview <slug>` | — | Just check the deck URL | ## Composes with - `business-brainstorm` — if a pitch deck, brainstorm the offer first; deck draws from the brief - `decide` — for talks that hinge on a decision (e.g., "should I take VC money?"), `/decide` first - `deep-research` — for talks needing data the deck doesn't have yet - `second-brain` — pull relevant `[[wiki]]` pages as content sources; the deck can cite them - `watch-video` — turn a podcast episode, Loom, or talk recording into a deck outline (run in `visual` mode to also capture key moments + slides shown) - `jab-hook` — once delivered, the talk becomes promo angles for the BIP rotation - `frontend-skills` (external) — for non-branded HTML decks where your React deck repo isn't the home ## Notes on quality - **Show, don't tell — applied to narrative.** Present 3 different angles for the deck (not one polished draft); let the user pick. Committing to a narrative before the alternatives are surfaced produces decks that are workmanlike, not memorable. - **Density modes matter more than aesthetic.** A speaker-led deck has 3-word slides and 5-line speaker notes. A reading-first deck (leave-behind, async) reverses it. Same content, opposite artifact. Ask which mode before drafting. - **Slide text is *abbreviated spoken*.** Both pass the read-aloud test. If a slide reads like a memo, it's wrong. If speaker notes read like slides, they're wrong. - **Overflow guards on update mode.** Don't blindly cram more content into an existing slide — if the addition tips the slide over its target word count or line count, propose splitting the slide or trimming existing content. Silently overflowing produces cramped decks. - **Presenter-view stripped for exports.** Playwright snapshots the `?present=0` mode so the export is clean. Never export the presenter view. - **Brand-perfect > portable.** The React deck is the source of truth. HTML/PDF/Vercel exports are snapshots for sharing — animations, interactions, presenter view live only in the React version. - **1920×1080 default, 1280×720 for previews.** Full-res snapshots run 30-50 MB for a 20-slide deck; low-res halves that at minimal visual loss. - **Deploy is a real-money operation.** Export mode's `vercel` path prompts before deploying. Auto-renew is on for domains + Vercel projects.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.