Claude Skill

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

LLM Mart · 0 points · 7 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download coreyhaines31-makerskills-skills_slide-deck-1868b81.zip · 22 KB
Part of coreyhaines31/makerskills — 18 skills

Install

skills CLI npx skills add https://github.com/coreyhaines31/makerskills/tree/main/skills/slide-deck
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install coreyhaines31-makerskills@llmmart
Git 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.

  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.

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:

  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:

    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 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.
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.

No comments yet.

Reviews (0)

No reviews yet.

Related