Use this skill to generate a full interactive pitch deck from a markdown file or document. The user provides their presentation content in markdown, and this skill generates a complete Next.js application with slide components, navigation, speaker notes, search, exports (PDF + PPTX), light/dark theme toggle, an executive one-pager, and an executive email.
- Node.js 18+
- GitHub CLI (
gh) installed and authenticated
# 1. Create a new Next.js project with shadcn/ui
npx create-next-app@latest my-pitch-deck --typescript --tailwind --eslint --app --src-dir=false
cd my-pitch-deck
# 2. Initialize shadcn/ui
npx shadcn@latest init
# 3. Place your markdown content file in the root
# (see "Input Format" below for structure)
# 4. Use Claude to generate the deck
# Pass this skill file + your markdown to Claude:
claude --skill ./CLAUDE-PITCH-DECK-SKILL.md "Generate a pitch deck from ./my-presentation.md"
# 5. Install additional dependencies
npm install html2canvas jspdf pptxgenjs
# 6. Run the dev server
npm run dev
# 7. Push to GitHub
gh repo create my-pitch-deck --public --push --source=.The user provides a markdown file structured with ## Slide N -- Title headings. Each slide should contain:
# [Project Name] Pitch Deck
## Slide 1 -- Title Slide
**[Project Name]**
Tagline or subtitle here
- Team: Person A, Person B
- Contact: email@company.com
## Slide 2 -- The Problem
**[Section Label]**
> Key talking point or thesis statement
- Bullet point 1
- Bullet point 2
- Bullet point 3
## Slide 3 -- The Solution
...continue for each slide...Parse the markdown and map each slide to one of these layout patterns:
| Slide Type | When to Use | Layout Pattern |
|---|---|---|
| Title | Opening slide | Centered, large title + subtitle, decorative grid background, accent glow |
| Overview | Executive summary | Left-aligned stats/metrics + right-aligned key points |
| Stat Cards | Market data, metrics | Grid of cards with large numbers and labels |
| Problem | Pain points | Cards with colored labels + descriptions |
| Timeline | How it works, process | Numbered vertical timeline with connector lines |
| Architecture | Technical layers | Stacked horizontal bars showing layers |
| Comparison Table | Competitive landscape | Table with check/partial/x indicators + legend |
| Grid Cards | Features, differentiators | 2-3 column grid of bordered cards |
| Partners/Logos | Traction, customers | Status badges (Active/Proposed) + logo grid |
| Personas | Target audience | Table with role, description, success metrics |
| Roadmap | Timeline milestones | Horizontal cards with phase labels |
| Closing | CTA / Ask | Centered title + links to one-pager and email |
app/
layout.tsx # Root layout with fonts (Inter + Space Grotesk)
page.tsx # Renders <DeckNavigator />
globals.css # Design tokens (dark + light themes)
one-pager/
page.tsx # Executive one-pager (print-friendly)
executive-email/
page.tsx # Copyable executive email
components/
deck/
slide-layout.tsx # SlideLayout, SlideLabel, SlideTitle, SlideSubtitle
deck-navigator.tsx # Main deck shell: navigation, keyboard, exports, search
slide-search.tsx # Cmd+K search overlay with keyword index
speaker-notes.ts # Record<number, string> of per-slide notes
theme-toggle.tsx # Light/dark toggle with localStorage persistence
slide-01-title.tsx # Each slide is its own component
slide-02-*.tsx
...
one-pager-download.tsx # PDF download button for the one-pager
import { cn } from "@/lib/utils"
interface SlideLayoutProps {
children: React.ReactNode
className?: string
}
export function SlideLayout({ children, className }: SlideLayoutProps) {
return (
<div className={cn(
"relative flex h-screen w-full flex-col justify-center overflow-hidden px-10 py-8 md:px-20 lg:px-28",
className
)}>
{children}
</div>
)
}
export function SlideLabel({ children }: { children: React.ReactNode }) {
return (
<span className="mb-4 inline-block text-xs font-semibold uppercase tracking-[0.2em] text-primary">
{children}
</span>
)
}
export function SlideTitle({ children, className }: { children: React.ReactNode; className?: string }) {
return (
<h1 className={cn(
"text-balance font-serif text-4xl font-bold leading-tight tracking-tight text-foreground md:text-5xl lg:text-6xl",
className
)}>
{children}
</h1>
)
}
export function SlideSubtitle({ children }: { children: React.ReactNode }) {
return (
<p className="mt-4 max-w-2xl text-pretty text-lg leading-relaxed text-muted-foreground md:text-xl">
{children}
</p>
)
}The DeckNavigator component provides:
- Keyboard navigation: Arrow keys, spacebar for next/prev
- Cmd+K search: Fuzzy search across slide titles and keywords
- PDF export: Uses
html2canvas+jspdfto capture each slide - PPTX export: Uses
html2canvas+pptxgenjsto generate PowerPoint - Theme toggle: Light/dark mode via CSS class toggle
- Speaker notes: Hover tooltip in top-right corner per slide
- Slide indicators: Dot navigation at bottom center
- Slide counter: "X / Y" display
Key implementation patterns:
"use client"
const slides = [Slide01, Slide02, ...] // Import all slide components
export function DeckNavigator() {
const [current, setCurrent] = useState(0)
const CurrentSlide = slides[current]
return (
<div className="relative h-screen w-full overflow-hidden bg-background">
<div ref={slideRef}><CurrentSlide /></div>
{/* Bottom nav bar with prev/next/search/export/theme */}
</div>
)
}Each slide gets an entry with keywords for fuzzy matching:
const slideIndex = [
{
index: 0,
title: "Project Title",
label: "Title",
keywords: ["project name", "tagline", "team members"],
},
// ...one entry per slide
]export const speakerNotes: Record<number, string> = {
0: "Open with confidence. Set the frame...",
1: "Executive summary slide. Key points are...",
// ...one entry per slide
}Uses CSS class toggle on <html> element. Dark is default. Persists to localStorage.
Dark theme (default -- :root):
--background: 220 15% 6%; /* Near-black */
--foreground: 210 20% 95%; /* Near-white */
--card: 220 15% 9%; /* Slightly lighter than bg */
--primary: 207 90% 54%; /* Blue */
--accent: 168 70% 45%; /* Teal/green */
--secondary: 220 14% 14%; /* Dark gray */
--muted-foreground: 215 15% 55%; /* Mid gray */
--border: 220 13% 18%; /* Subtle border */Light theme (.light class):
--background: 210 20% 98%; /* Near-white */
--foreground: 220 15% 10%; /* Near-black */
--card: 0 0% 100%; /* Pure white */
--primary: 207 90% 44%; /* Deeper blue */
--accent: 168 70% 35%; /* Deeper teal */
--secondary: 215 20% 93%; /* Light gray */
--muted-foreground: 215 15% 45%; /* Mid gray */
--border: 215 20% 88%; /* Light border */- Headings font: Space Grotesk (mapped to
font-serifin Tailwind) - Body font: Inter (mapped to
font-sansin Tailwind) - Both loaded via
next/font/googleinlayout.tsx
fontFamily: {
sans: ['var(--font-inter)', 'system-ui', 'sans-serif'],
serif: ['var(--font-space-grotesk)', 'system-ui', 'sans-serif'],
},<SlideLayout className="items-center justify-center text-center">
{/* Decorative grid overlay */}
<div className="pointer-events-none absolute inset-0 opacity-[0.03]">
<svg width="100%" height="100%">
<defs>
<pattern id="grid" width="60" height="60" patternUnits="userSpaceOnUse">
<path d="M 60 0 L 0 0 0 60" fill="none" stroke="currentColor" strokeWidth="1" />
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#grid)" />
</svg>
</div>
{/* Accent glow */}
<div className="pointer-events-none absolute left-1/2 top-1/2 h-[600px] w-[600px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary/5 blur-[120px]" />
<div className="relative z-10 flex flex-col items-center gap-6">
<h1 className="text-balance font-serif text-5xl font-bold">
Main Title <br /><span className="text-primary">Accent Line</span>
</h1>
<p className="text-lg text-muted-foreground">Subtitle / team info</p>
{/* Protocol badges */}
<div className="mt-8 flex gap-3">
{["Tag1", "Tag2", "Tag3"].map(t => (
<div key={t} className="rounded-full border border-border bg-secondary px-4 py-1.5 text-xs font-medium text-secondary-foreground">{t}</div>
))}
</div>
</div>
</SlideLayout><SlideLayout>
<SlideLabel>The Problem</SlideLabel>
<SlideTitle>Title Here</SlideTitle>
<div className="mt-8 flex flex-col gap-3">
{challenges.map(item => (
<div key={item.label} className="flex gap-3 rounded-lg border border-border bg-card p-4">
<span className="shrink-0 text-xs font-bold text-accent">{item.label}</span>
<span className="text-sm text-muted-foreground">{item.desc}</span>
</div>
))}
</div>
</SlideLayout><SlideLayout>
<SlideLabel>How It Works</SlideLabel>
<SlideTitle>Step-by-Step Process</SlideTitle>
<div className="mt-12 flex flex-col gap-0">
{steps.map((step, i) => (
<div key={step.num} className="flex items-stretch gap-6">
<div className="flex flex-col items-center">
<div className="flex h-10 w-10 items-center justify-center rounded-full border border-primary/30 bg-primary/10 font-serif text-sm font-bold text-primary">
{step.num}
</div>
{i < steps.length - 1 && <div className="w-px flex-1 bg-border" />}
</div>
<div className="pb-8">
<h3 className="font-serif text-lg font-semibold text-foreground">{step.title}</h3>
<p className="mt-1 max-w-md text-sm text-muted-foreground">{step.description}</p>
</div>
</div>
))}
</div>
</SlideLayout><SlideLayout>
<SlideLabel>Competitive Landscape</SlideLabel>
<SlideTitle>Comparison Title</SlideTitle>
<div className="mt-8 overflow-hidden rounded-xl border border-border">
<table className="w-full text-left">
<thead>
<tr className="border-b border-border bg-secondary/50">
<th className="p-4 font-serif text-sm font-semibold">Platform</th>
{features.map(f => <th key={f} className="p-4 text-xs font-medium text-muted-foreground">{f}</th>)}
</tr>
</thead>
<tbody>
{competitors.map(c => (
<tr key={c.name} className={`border-b border-border ${c.highlight ? "bg-primary/5" : ""}`}>
<td className={`p-4 font-serif text-sm font-semibold ${c.highlight ? "text-primary" : "text-foreground"}`}>{c.name}</td>
{c.scores.map((s, i) => (
<td key={features[i]} className="p-4 text-center">
{/* true = green check, "partial" = amber circle, false = gray dash */}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
{/* Legend: Full / Partial / Not available */}
</SlideLayout><SlideLayout>
<SlideLabel>Section Label</SlideLabel>
<SlideTitle>Grid Title</SlideTitle>
<div className="mt-10 grid grid-cols-2 gap-4 lg:grid-cols-3">
{items.map(item => (
<div key={item.title} className="rounded-xl border border-border bg-card p-6">
<h3 className="font-serif text-base font-semibold text-foreground">{item.title}</h3>
<p className="mt-2 text-sm text-muted-foreground">{item.desc}</p>
</div>
))}
</div>
</SlideLayout><SlideLayout>
<SlideLabel>Traction</SlideLabel>
<SlideTitle>Design Partners</SlideTitle>
<div className="mt-10 flex gap-12">
<div className="flex-1">
<h3 className="text-xs font-semibold uppercase tracking-[0.15em] text-muted-foreground">Internal</h3>
<div className="mt-4 flex flex-col gap-2">
{partners.map(p => (
<div key={p.name} className="flex items-center justify-between rounded-lg border border-border bg-secondary/30 px-5 py-3.5">
<span className="font-serif text-sm font-semibold">{p.name}</span>
<span className={`rounded-full px-3 py-0.5 text-[10px] font-semibold uppercase ${
p.status === "Active" ? "bg-accent/15 text-accent" : "bg-muted-foreground/10 text-muted-foreground"
}`}>{p.status}</span>
</div>
))}
</div>
</div>
</div>
</SlideLayout><SlideLayout>
<SlideLabel>Architecture</SlideLabel>
<SlideTitle>Technical Architecture</SlideTitle>
<div className="mt-10 flex flex-col gap-2">
{layers.map(layer => (
<div key={layer.name} className="flex items-center gap-3 rounded-lg border border-border bg-card px-6 py-4">
<span className="w-48 shrink-0 font-serif text-sm font-bold">{layer.name}</span>
<span className="text-sm text-muted-foreground">{layer.items}</span>
</div>
))}
</div>
</SlideLayout><SlideLayout>
<SlideLabel>Target Audience</SlideLabel>
<SlideTitle>Who We Are Building For</SlideTitle>
<div className="mt-8 overflow-hidden rounded-xl border border-border">
<table className="w-full text-left">
<thead>
<tr className="border-b border-border bg-secondary/50">
<th className="p-4 text-xs font-semibold">Role</th>
<th className="p-4 text-xs font-medium text-muted-foreground">Description</th>
<th className="p-4 text-xs font-medium text-muted-foreground">Success Metrics</th>
</tr>
</thead>
<tbody>
{personas.map(p => (
<tr key={p.role} className="border-b border-border last:border-b-0">
<td className="p-4"><span className="font-serif text-sm font-bold text-foreground">{p.role}</span><br /><span className="text-xs text-muted-foreground">{p.subtitle}</span></td>
<td className="p-4 text-xs text-muted-foreground">{p.desc}</td>
<td className="p-4 text-xs text-muted-foreground">{p.metrics}</td>
</tr>
))}
</tbody>
</table>
</div>
</SlideLayout>When a user provides a markdown file, follow these steps:
- Extract each
## Slide N -- Titleblock - Identify the slide type from keywords (problem, solution, architecture, competitive, roadmap, etc.)
- Extract bullet points, stats, and structured data
app/globals.css-- Copy the dark + light theme tokens aboveapp/layout.tsx-- Set up Inter + Space Grotesk fonts, metadata from the project nametailwind.config.ts-- Add the font family and color token mappingsapp/page.tsx-- Simply renders<DeckNavigator />
For each slide in the markdown:
- Create
components/deck/slide-{NN}-{slug}.tsx - Map the content to the appropriate pattern from the Pattern Library
- Export a named function component (e.g.,
Slide01Title)
slide-layout.tsx-- The four layout primitivesdeck-navigator.tsx-- Import all slides, wire up navigation, keyboard, exports, search, notes, themeslide-search.tsx-- Generate keyword index from slide contentspeaker-notes.ts-- Generate 2-3 sentence speaking notes per slide from the markdown contenttheme-toggle.tsx-- Light/dark toggle component
app/one-pager/page.tsx-- Two-column executive summary pulling key content from all slidesapp/executive-email/page.tsx-- Copyable email summarizing the deck narrative
npm install html2canvas jspdf pptxgenjsUsers can customize these aspects:
| Aspect | How |
|---|---|
| Brand color | Change --primary HSL values in globals.css |
| Accent color | Change --accent HSL values in globals.css |
| Fonts | Swap Inter/Space Grotesk in layout.tsx and tailwind.config.ts |
| Slide count | Add/remove slide components and update the slides array in deck-navigator.tsx |
| Speaker notes | Edit speaker-notes.ts |
| Search keywords | Edit slideIndex array in slide-search.tsx |
| Export filename | Change the filename string in generatePDF / generatePPTX functions |
{
"html2canvas": "^1.4.1",
"jspdf": "^4.1.0",
"pptxgenjs": "^4.0.1",
"next": "^16",
"react": "^19",
"tailwindcss": "^3.4",
"tailwindcss-animate": "^1.0.7"
}I have a markdown file with my presentation content. Please generate a full
interactive pitch deck following the CLAUDE-PITCH-DECK-SKILL.md patterns.
Here is my content:
[paste markdown or attach file]
Generate:
1. All slide components mapped to the right patterns
2. The deck navigator with keyboard nav, search, PDF/PPTX export
3. Speaker notes for each slide
4. An executive one-pager
5. An executive email
6. Light/dark theme support