From presentation-chef
Generates self-contained Apple Keynote-style HTML presentations from markdown, text descriptions, topics, or files, with cinematic animations and glassmorphism design.
How this skill is triggered — by the user, by Claude, or both
Slash command
/presentation-chef:presentation-chefThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate cinematic, Apple Keynote-style HTML presentations from any content. Single self-contained .html file with zero dependencies.
Generate cinematic, Apple Keynote-style HTML presentations from any content. Single self-contained .html file with zero dependencies.
You MUST gather all inputs interactively using AskUserQuestion before generating any HTML. Do NOT skip the input gathering phase. Do NOT generate a presentation without knowing the content, theme, and design preferences.Follow these steps exactly:
Before starting Step 1, create tasks to give the user visibility into the full workflow:
TaskCreate:
subject: "Gather content source"
description: "Ask the user for their presentation content — paste, file, or topic"
activeForm: "Gathering content source"
TaskCreate:
subject: "Select theme & customize design"
description: "Analyze content, recommend a theme, and gather design customization preferences"
activeForm: "Selecting theme and design preferences"
TaskCreate:
subject: "Plan slide structure"
description: "Map content to slide types and confirm the structure with user"
activeForm: "Planning slide structure"
TaskCreate:
subject: "Build presentation"
description: "Generate the complete HTML presentation with slides, navigation, and effects"
activeForm: "Building presentation"
Update each task to in_progress when starting that phase, and completed when done:
TaskUpdate: Mark "Gather content source" → in_progress
Ask the user for their content source using AskUserQuestion:
AskUserQuestion:
question: "What content should I turn into a presentation?"
header: "Content"
options:
- label: "Paste or describe it"
description: "I'll type/paste content or describe what I want in the presentation"
- label: "Read from a file"
description: "I have a markdown file, text file, or other document to convert"
- label: "Generate from topic"
description: "Give me a topic and I'll create both content and design"
multiSelect: false
If the user selects "Read from a file", ask for the file path and use the Read tool to get the content. If the user selects "Generate from topic", ask what the topic is and what audience/purpose it serves. If the user pastes content, analyze it to understand its structure.
TaskUpdate: Mark "Gather content source" → completed
TaskUpdate: Mark "Select theme & customize design" → in_progress
After receiving content, analyze it to determine:
Then present theme options interactively:
AskUserQuestion:
question: "What visual theme should the presentation use?"
header: "Theme"
options:
- label: "Dark Cinematic (Recommended)"
description: "[AI reasoning why this fits the content]. Pure black bg, ambient orbs, glassmorphism cards, Apple blue/purple accents"
markdown: |
DARK CINEMATIC
──────────────────────────────
Background: #000000 (pure black)
Text: #ffffff / #86868b
Accent 1: #0071e3 (Apple blue)
Accent 2: #a855f7 (purple)
Warm: #ff6723 (orange)
Success: #30d158 (green)
Effects: Ambient orbs, grain overlay
Glassmorphism cards
Gradient text on headlines
Font: SF Pro / system sans-serif
Animations: Cinematic (900ms, spring easing)
- label: "Light Minimal"
description: "Clean, Chronicle-style white canvas with bold typography and single accent color"
markdown: |
LIGHT MINIMAL
──────────────────────────────
Background: #ffffff / #fafafa
Text: #1d1d1f / #86868b
Accent: #0071e3 (or content-matched)
Border: #e5e5e5
Effects: Subtle shadows, no orbs
Clean card borders
Understated elegance
Font: Geist / clean geometric sans
Animations: Smooth (600ms, ease-out)
- label: "Warm Editorial"
description: "Magazine-style warmth with serif typography and rich earth tones"
markdown: |
WARM EDITORIAL
──────────────────────────────
Background: #1a1210 / #f5efe8
Text: #f5efe8 / #a89585
Accent: #e8734a (coral)
Secondary: #c4956a (warm gold)
Effects: Warm ambient glow
Paper-like texture
Editorial layouts
Font: Playfair Display + Source Sans
Animations: Elegant (800ms, smooth)
- label: "Neon Cyberpunk"
description: "High-energy tech aesthetic with neon accents on deep dark backgrounds"
markdown: |
NEON CYBERPUNK
──────────────────────────────
Background: #0a0a1a / #0d1117
Text: #e0e0ff / #6b7280
Accent 1: #00f0ff (cyan)
Accent 2: #ff00aa (magenta)
Glow: #7c3aed (purple)
Effects: Neon glow borders
Scanline overlay
Matrix-style grid bg
Font: JetBrains Mono + Space Grotesk
Animations: Snappy (500ms, sharp easing)
multiSelect: false
IMPORTANT: Customize the recommendation text based on the actual content analyzed. Don't use generic descriptions. For example: "Since this is a startup pitch, Dark Cinematic will make your numbers feel impactful and your product feel premium."
Ask about specific design preferences:
AskUserQuestion:
question: "Any design customizations?"
header: "Customize"
options:
- label: "Use defaults"
description: "The theme preset looks great as-is, no changes needed"
- label: "Custom accent color"
description: "I want to use a specific brand color as the primary accent"
- label: "Custom fonts"
description: "I want specific fonts (e.g., Google Fonts or system fonts)"
- label: "Multiple customizations"
description: "I have specific requirements for colors, fonts, or effects"
multiSelect: false
If the user wants customizations, ask follow-up questions one at a time about each customization.
TaskUpdate: Mark "Select theme & customize design" → completed
TaskUpdate: Mark "Plan slide structure" → in_progress
Analyze the content and map it to the appropriate slide types. Present the slide plan:
AskUserQuestion:
question: "Here's my proposed slide structure. Does this look right?"
header: "Structure"
options:
- label: "Looks perfect"
description: "Generate the presentation with this structure"
- label: "Adjust order"
description: "I want to reorder some slides"
- label: "Add/remove slides"
description: "I want to add or remove specific slides"
- label: "Different approach"
description: "I want a different overall structure"
multiSelect: false
Before asking, present the slide plan as a numbered list in your message text. Example:
Slide 1: Hero — "Company Name" + tagline
Slide 2: Stats — 3 key metrics with count-up
Slide 3: Chapter — "The Problem"
Slide 4: Content Card — Problem description
...
TaskUpdate: Mark "Plan slide structure" → completed
AskUserQuestion:
question: "Where should I save the presentation?"
header: "Output"
options:
- label: "Current directory"
description: "Save as presentation.html in the current working directory"
- label: "Custom path"
description: "I'll specify the exact file path and name"
- label: "Desktop"
description: "Save to ~/Desktop/ for easy access"
multiSelect: false
When entering this step, mark the "Build presentation" high-level task as in_progress.
Based on the confirmed slide plan, create granular build tasks. Group slides into batches of 3-4:
TaskCreate:
subject: "Set up HTML structure & CSS theme"
description: "Create HTML skeleton, CSS reset, theme variables, effects (grain/orbs), slide system, reveal animations, typography, responsive breakpoints, print styles"
activeForm: "Setting up document structure and CSS"
# For each batch of 3-4 slides, create a task:
TaskCreate:
subject: "Build slides [X]-[Y]: [slide types]"
description: "Generate HTML for: Slide X ([type] — [title]), Slide X+1 ([type] — [title]), ..."
activeForm: "Building slides [X] through [Y]"
# Example for a 12-slide presentation:
# "Build slides 1-4: Hero, Stats, Chapter, Content Card"
# "Build slides 5-8: Feature Grid, Timeline, Comparison, Quote"
# "Build slides 9-12: Data, Split Screen, List, CTA"
TaskCreate:
subject: "Add navigation, controls & speaker notes"
description: "Progress bar, nav dots, keyboard hints, speaker notes sidebar with talking points for every slide, controls bar"
activeForm: "Adding navigation and speaker notes"
TaskCreate:
subject: "Wire up JavaScript engine"
description: "Slide navigation, reveal animations, count-up, data bars, keyboard/mouse/touch handlers, parallax, loading screen, notes toggle, PDF export"
activeForm: "Wiring up JavaScript engine"
TaskCreate:
subject: "Run quality checklist & save file"
description: "Verify all 23 quality checks pass, then write the final .html file"
activeForm: "Running quality checklist and saving"
Work through each task sequentially. For every task:
TaskUpdate → in_progress before you start working on itTaskUpdate → completed when doneSlide batch details: When building a slide batch, generate the HTML for all slides in that batch. Each slide must include:
<div> with correct data-slide indexreveal-element with sequential delaysAfter all build tasks are complete, assemble the full HTML document in this order:
<style> open)<body> open, loading screen, grain overlay (if dark theme)<div class="slide-container"> with all slide batches in order<script> with full JS engineUse the Write tool to save the assembled HTML to the confirmed output path.
Mark the "Build presentation" high-level task as completed, then mark "Run quality checklist & save file" as completed.
Tell the user the file path and suggest opening it in a browser.
When generating the HTML presentation, follow these specifications exactly.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>[Presentation Title]</title>
<style>/* ALL CSS INLINE */</style>
</head>
<body>
<!-- Loading Screen -->
<!-- Grain Overlay (if dark theme) -->
<!-- Progress Bar -->
<!-- Navigation Dots -->
<!-- Keyboard Hint -->
<!-- Speaker Notes Sidebar -->
<!-- Controls Bar (Notes T, PDF P) -->
<!-- Slide Container -->
<script>/* ALL JS INLINE */</script>
</body>
</html>
Everything must be inline. Zero external dependencies. No CDN links. No Google Fonts links (use system font stacks or embed @font-face with base64 only if absolutely necessary for a specific requested font).
*, *::before, *::after {
margin: 0; padding: 0; box-sizing: border-box;
}
:root {
/* Theme tokens - set these per theme */
--bg-primary: #000000;
--bg-secondary: #0a1628;
--text-primary: #ffffff;
--text-secondary: #86868b;
--text-muted: #6e6e73;
--accent-1: #0071e3;
--accent-1-light: #2997ff;
--accent-2-start: #6e3aff;
--accent-2-end: #a855f7;
--accent-warm: #ff6723;
--accent-pink: #ff375f;
--accent-green: #30d158;
--accent-teal: #64d2ff;
--gray-100: #f5f5f7;
--gray-200: #d2d2d7;
--gray-400: #86868b;
--gray-600: #6e6e73;
--gray-800: #1d1d1f;
--card-bg: rgba(255, 255, 255, 0.04);
--card-border: rgba(255, 255, 255, 0.08);
--card-blur: 40px;
--transition-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
--transition-spring: cubic-bezier(0.175, 0.885, 0.32, 1.275);
--transition-cinematic: cubic-bezier(0.16, 1, 0.3, 1);
--slide-duration: 900ms;
--reveal-stagger: 150ms;
}
html { scroll-behavior: auto; overflow: hidden; background: var(--bg-primary); }
body {
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Inter', 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
overflow: hidden;
height: 100vh;
width: 100vw;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
For light themes, override:
:root {
--bg-primary: #ffffff;
--bg-secondary: #fafafa;
--text-primary: #1d1d1f;
--text-secondary: #86868b;
--text-muted: #aeaeb2;
--card-bg: rgba(0, 0, 0, 0.03);
--card-border: rgba(0, 0, 0, 0.08);
/* Adjust accent colors for contrast on light */
}
.grain-overlay {
position: fixed;
top: -50%; left: -50%;
width: 200%; height: 200%;
pointer-events: none;
z-index: 9998;
opacity: 0.035;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)'/%3E%3C/svg%3E");
animation: grainShift 0.5s steps(3) infinite;
}
@keyframes grainShift {
0% { transform: translate(0, 0); }
33% { transform: translate(-2px, 1px); }
66% { transform: translate(1px, -1px); }
100% { transform: translate(0, 0); }
}
.ambient-orb {
position: absolute;
border-radius: 50%;
filter: blur(120px);
opacity: 0.15;
pointer-events: none;
animation: orbFloat 12s ease-in-out infinite alternate;
}
@keyframes orbFloat {
0% { transform: translate(0, 0) scale(1); }
100% { transform: translate(30px, -20px) scale(1.1); }
}
Create 2-3 orbs per slide with different colors matching the theme. Vary sizes (400-600px), positions, animation delays.
.hero-glow {
position: absolute;
width: 600px; height: 600px;
border-radius: 50%;
background: radial-gradient(circle, rgba(accent1, 0.08) 0%, rgba(accent2, 0.04) 40%, transparent 70%);
pointer-events: none;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
animation: glowPulse 6s ease-in-out infinite alternate;
}
.slide-container {
position: fixed; inset: 0;
width: 100vw; height: 100vh;
}
.slide {
position: absolute; inset: 0;
width: 100%; height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
opacity: 0;
visibility: hidden;
transform: scale(0.95) translateY(30px);
transition: opacity var(--slide-duration) var(--transition-cinematic),
transform var(--slide-duration) var(--transition-cinematic),
visibility 0s linear var(--slide-duration);
will-change: opacity, transform;
overflow-y: auto;
overflow-x: hidden;
}
.slide.active {
opacity: 1;
visibility: visible;
transform: scale(1) translateY(0);
transition: opacity var(--slide-duration) var(--transition-cinematic),
transform var(--slide-duration) var(--transition-cinematic),
visibility 0s linear 0s;
}
.slide.exiting-up {
opacity: 0; visibility: hidden;
transform: scale(1.05) translateY(-50px);
}
.slide.exiting-down {
opacity: 0; visibility: hidden;
transform: scale(0.9) translateY(50px);
}
For dark themes, create gradient backgrounds per slide:
.slide--dark { background: var(--bg-primary); }
.slide--gradient-blue { background: linear-gradient(145deg, #000 0%, #0a1628 40%, #0d2137 70%, #0a0a2e 100%); }
.slide--gradient-purple { background: linear-gradient(145deg, #000 0%, #1a0a2e 40%, #2d1b4e 60%, #0a0a2e 100%); }
.slide--gradient-warm { background: linear-gradient(145deg, #000 0%, #2e1a0a 40%, #3d1f0f 60%, #1a0a00 100%); }
For light themes, use subtle gradients:
.slide--light { background: var(--bg-primary); }
.slide--gradient-soft { background: linear-gradient(145deg, #fff 0%, #f8f9fa 50%, #f0f0f5 100%); }
.reveal-element {
opacity: 0;
transform: translateY(40px);
transition: opacity 0.8s var(--transition-cinematic),
transform 0.8s var(--transition-cinematic);
}
.reveal-element.revealed {
opacity: 1; transform: translateY(0);
}
/* Staggered delays */
.reveal-element[data-delay="1"] { transition-delay: 0.15s; }
.reveal-element[data-delay="2"] { transition-delay: 0.3s; }
.reveal-element[data-delay="3"] { transition-delay: 0.45s; }
/* Continue up to data-delay="12" at 0.15s increments */
Wrap every content element in a <div class="reveal-element" data-delay="N"> with sequential delay values per slide.
<div class="slide slide--dark active" data-slide="0">
<div class="hero-glow"></div>
<div class="reveal-element" data-delay="0">
<h1 class="hero-name">[Title]</h1>
</div>
<div class="reveal-element" data-delay="2">
<p class="hero-subtitle">[Subtitle with separator spans]</p>
</div>
<div class="reveal-element" data-delay="4">
<div class="divider-line"></div>
</div>
<div class="reveal-element" data-delay="5">
<p class="hero-location">[Location or tagline]</p>
</div>
</div>
Typography:
.hero-name {
font-size: clamp(48px, 10vw, 120px);
font-weight: 700;
letter-spacing: -0.03em;
line-height: 1;
text-align: center;
background: linear-gradient(135deg, var(--text-primary) 0%, var(--gray-200) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: clamp(18px, 3vw, 32px);
font-weight: 400;
color: var(--text-secondary);
text-align: center;
margin-top: 24px;
letter-spacing: 0.02em;
}
<div class="slide slide--gradient-blue" data-slide="N">
<div class="ambient-orb ambient-orb--accent1"></div>
<div class="reveal-element" data-delay="0">
<p class="section-label">[Label text]</p>
</div>
<div class="stats-grid">
<div class="stat-item reveal-element" data-delay="1">
<div class="stat-number mega-number--accent1"><span class="count-up" data-target="[N]">0</span>[suffix]</div>
<div class="stat-label">[Label]</div>
</div>
<!-- Repeat for each stat (2-4 stats ideal) -->
</div>
</div>
Stats grid: grid-template-columns: repeat(2, 1fr), gap 48px 64px, max-width 900px.
Mega numbers: font-size: clamp(48px, 8vw, 96px), weight 800, gradient text with matching glow.
Count-up JS animates from 0 to target with ease-out cubic easing over 1200ms.
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="ambient-orb"></div>
<div class="reveal-element" data-delay="0">
<p class="section-label">[Small label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[Big title with gradient text]</h2>
</div>
<div class="reveal-element" data-delay="2">
<p class="section-subtitle">[Supporting text, max 2 lines]</p>
</div>
</div>
Section title: font-size: clamp(36px, 7vw, 96px), weight 700, tight tracking.
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="ambient-orb"></div>
<div class="reveal-element" data-delay="0">
<p class="section-label">[Context label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title" style="font-size: clamp(28px, 5vw, 56px);">[Title]</h2>
</div>
<div class="experience-card reveal-element" data-delay="2">
<div class="card-title">[Title]</div>
<div class="card-subtitle">[Subtitle in accent color]</div>
<div class="card-meta">[Date/location/metadata]</div>
<div class="card-body">
<ul>
<li>[Point 1]</li>
<li>[Point 2]</li>
</ul>
</div>
</div>
</div>
Card: background: var(--card-bg), backdrop-filter: blur(var(--card-blur)), border: 1px solid var(--card-border), border-radius: 24px, padding clamp(32px, 4vw, 56px).
Top highlight: ::before pseudo-element with gradient line.
Bullet points: custom dot (6px circle in accent color) via ::before.
<div class="slide slide--gradient-deep" data-slide="N">
<div class="reveal-element" data-delay="0">
<p class="section-label">[Label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[Title]</h2>
</div>
<div class="tech-grid">
<div class="tech-chip tech-chip--[category] reveal-element" data-delay="2">
[Item name]
<span class="tech-category-label">[Category]</span>
</div>
<!-- Repeat -->
</div>
</div>
Grid: repeat(auto-fit, minmax(120px, 1fr)), gap 16px. Chips have hover lift + scale, radial gradient glow on hover.
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="reveal-element" data-delay="0">
<p class="section-label">[Label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[Title]</h2>
</div>
<div class="timeline">
<div class="timeline-item reveal-element" data-delay="2">
<div class="timeline-marker"></div>
<div class="timeline-content">
<div class="timeline-date">[Date]</div>
<div class="timeline-title">[Title]</div>
<div class="timeline-desc">[Description]</div>
</div>
</div>
<!-- Repeat -->
</div>
</div>
Timeline: vertical line (2px, accent gradient) with circular markers (12px). Content cards to the right. Alternating layout on desktop.
.timeline {
position: relative;
max-width: 800px;
width: 100%;
margin-top: 32px;
padding-left: 40px;
}
.timeline::before {
content: '';
position: absolute;
left: 16px;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, var(--accent-1), var(--accent-2-end));
}
.timeline-item {
position: relative;
margin-bottom: 32px;
padding-left: 24px;
}
.timeline-marker {
position: absolute;
left: -32px;
top: 6px;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--accent-1);
border: 2px solid var(--bg-primary);
box-shadow: 0 0 0 4px rgba(var(--accent-1-rgb), 0.2);
}
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="reveal-element" data-delay="0">
<p class="section-label">[Label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[Title]</h2>
</div>
<div class="comparison-grid">
<div class="comparison-col reveal-element" data-delay="2">
<h3 class="comparison-heading">[Side A]</h3>
<ul class="comparison-list">
<li>[Point]</li>
</ul>
</div>
<div class="comparison-divider reveal-element" data-delay="3"></div>
<div class="comparison-col reveal-element" data-delay="4">
<h3 class="comparison-heading">[Side B]</h3>
<ul class="comparison-list">
<li>[Point]</li>
</ul>
</div>
</div>
</div>
Two-column grid with vertical divider. Cards have frosted glass background.
<div class="slide slide--dark" data-slide="N">
<div class="hero-glow" style="opacity: 0.3;"></div>
<div class="reveal-element" data-delay="0">
<blockquote class="quote-text">"[Quote text]"</blockquote>
</div>
<div class="reveal-element" data-delay="2">
<p class="quote-attribution">— [Name], [Title]</p>
</div>
</div>
Quote: font-size: clamp(24px, 4vw, 48px), weight 400, italic, line-height 1.4. Center aligned with max-width 800px.
<div class="slide slide--image" data-slide="N" style="background: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.8)), url('[base64 or path]') center/cover;">
<div class="reveal-element" data-delay="0">
<p class="section-label">[Label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[Overlay text]</h2>
</div>
</div>
Note: For truly self-contained files, images should be base64-encoded inline if the user provides them. Otherwise use descriptive gradient backgrounds as visual stand-ins.
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="reveal-element" data-delay="0">
<p class="section-label">[Label]</p>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[Title]</h2>
</div>
<div class="data-bars">
<div class="data-bar-item reveal-element" data-delay="2">
<div class="data-bar-label">[Label]</div>
<div class="data-bar-track">
<div class="data-bar-fill" style="--bar-width: [N]%;" data-value="[N]%"></div>
</div>
</div>
<!-- Repeat -->
</div>
</div>
CSS-only animated bars. Fill animates width from 0 to --bar-width on reveal. Gradient fill matching theme accent.
.data-bar-track {
height: 8px;
background: var(--card-bg);
border-radius: 4px;
overflow: hidden;
flex: 1;
}
.data-bar-fill {
height: 100%;
width: 0;
background: linear-gradient(90deg, var(--accent-1), var(--accent-2-end));
border-radius: 4px;
transition: width 1.2s var(--transition-cinematic);
}
.data-bar-fill.revealed { width: var(--bar-width); }
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="split-layout">
<div class="split-text reveal-element" data-delay="0">
<p class="section-label">[Label]</p>
<h2 class="section-title" style="text-align: left; font-size: clamp(28px, 5vw, 56px);">[Title]</h2>
<p class="section-subtitle" style="text-align: left;">[Description]</p>
</div>
<div class="split-visual reveal-element" data-delay="2">
<!-- Visual content: card, graphic, or placeholder -->
</div>
</div>
</div>
.split-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 64px;
max-width: 1100px;
width: 100%;
align-items: center;
}
@media (max-width: 768px) {
.split-layout { grid-template-columns: 1fr; gap: 32px; }
}
<div class="slide slide--dark" data-slide="N">
<div class="reveal-element" data-delay="0">
<h2 class="section-title">[Title]</h2>
</div>
<div class="achievements-list">
<div class="achievement-item reveal-element" data-delay="1">
<div class="achievement-icon">[emoji or icon]</div>
<div class="achievement-content">
<div class="achievement-title">[Title]</div>
<div class="achievement-desc">[Description]</div>
</div>
</div>
<!-- Repeat -->
</div>
</div>
Icon container: 48x48, rounded 12px, frosted glass bg. Flex row with 20px gap.
<div class="slide slide--gradient-[color]" data-slide="N">
<div class="reveal-element" data-delay="0">
<div class="available-badge">
<span class="available-pulse"></span>
[Status text]
</div>
</div>
<div class="reveal-element" data-delay="1">
<h2 class="section-title">[CTA headline]</h2>
</div>
<div class="reveal-element" data-delay="2">
<p class="section-subtitle">[Supporting text]</p>
</div>
<div class="contact-links reveal-element" data-delay="3">
<a href="[url]" class="contact-link contact-link--primary">
<svg>...</svg> [Label]
</a>
<a href="[url]" class="contact-link">
<svg>...</svg> [Label]
</a>
</div>
</div>
Pill-shaped links: border-radius: 100px, frosted glass bg, hover lift.
Available badge: green pulse animation, pill shape, green tint bg.
.progress-bar {
position: fixed; top: 0; left: 0;
height: 2px;
background: linear-gradient(90deg, var(--accent-1), var(--accent-2-end));
z-index: 200;
transition: width 0.6s var(--transition-cinematic);
box-shadow: 0 0 10px rgba(accent1, 0.5);
}
Fixed right side, vertical, 8px circles. Active dot: white, scale 1.4, glow shadow. Hover shows label via ::before content.
Fixed bottom center. Shows arrow keys and space. Hidden class after first interaction. Bounce arrow animation.
The JS engine handles:
goToSlide(index, direction) with transition locking.revealed class on active slide elementsrequestAnimationFrame with ease-out cubictoggleNotes() opens/closes sidebar, updateNotes() syncs content to current slideexportPdf() snaps all count-up numbers to data-target values and data bars to full width before calling window.print(), ensuring animated values render correctly in the PDFKey constants:
const TOTAL_SLIDES = [number];
const TRANSITION_DURATION = 900;
const DEBOUNCE_MS = 80;
const COUNT_DURATION = 1200;
const WHEEL_THRESHOLD = 50;
Use an IIFE wrapper. No external dependencies. All event listeners with proper passive flags.
@media (hover: none)Every presentation MUST include a speaker notes sidebar. The sidebar:
T key or clicking the Notes buttonrgba(10,10,20,0.92) bg, backdrop-filter: blur(30px), 340px wide)<div class="note-content" data-note="N"> for each slide with talking points.note-content.active matching current slide is shown (display block/none)transform: translateX(170px) scale(0.88)left: 340pxbody.notes-open classTalking points content: Write 2-3 paragraphs per slide with:
.note-tip callout for delivery tips (when to pause, make eye contact, etc.)Important: Call updateNotes() inside goToSlide() when notesOpen is true.
Fixed bottom-right, two buttons:
<div class="controls-bar">
<button class="control-btn" id="btnNotes">
<svg><!-- document icon --></svg> Notes <span class="key-badge">T</span>
</button>
<button class="control-btn" id="btnPdf">
<svg><!-- download icon --></svg> PDF <span class="key-badge">P</span>
</button>
</div>
Buttons: border-radius: 10px, frosted glass bg, 12px font. Key badge: 20x20 rounded square.
Hide controls bar on mobile (@media (max-width: 480px) { .controls-bar { display: none; } }).
Every presentation MUST include PDF export via window.print() with a print stylesheet.
Critical: Count-up number fix. Before printing, the exportPdf() function MUST:
.count-up elements to their data-target values (set textContent = getAttribute('data-target')).revealed class to ALL .data-bar-fill elements so bars show at full widthwindow.print() after a 100ms delay to let CSS settlefunction exportPdf() {
document.querySelectorAll('.count-up').forEach(function(el) {
el.textContent = el.getAttribute('data-target');
});
document.querySelectorAll('.data-bar-fill').forEach(function(el) {
el.classList.add('revealed');
});
var wasNotesOpen = notesOpen;
if (notesOpen) toggleNotes();
setTimeout(function() {
window.print();
if (wasNotesOpen) setTimeout(toggleNotes, 300);
}, 100);
}
Print stylesheet (@media print):
@media print {
*, *::before, *::after { animation: none !important; transition: none !important; }
html, body { overflow: visible !important; height: auto !important; width: auto !important; }
.loading-screen, .grain-overlay, .progress-bar, .nav-dots,
.keyboard-hint, .controls-bar, .speaker-notes,
.ambient-orb, .hero-glow { display: none !important; }
.slide-container { position: static !important; transform: none !important; }
.slide {
position: relative !important;
opacity: 1 !important; visibility: visible !important; transform: none !important;
width: 100% !important; height: 100vh !important;
page-break-after: always; break-after: page;
page-break-inside: avoid; break-inside: avoid;
overflow: hidden !important;
}
.slide:last-child { page-break-after: auto; }
.reveal-element { opacity: 1 !important; transform: none !important; }
.data-bar-fill { width: var(--bar-width) !important; }
}
@page { size: landscape; margin: 0; }
| Element | Size | Weight | Tracking |
|---|---|---|---|
| Hero name | clamp(48px, 10vw, 120px) | 700 | -0.03em |
| Section title | clamp(36px, 7vw, 96px) | 700 | -0.03em |
| Mega number | clamp(64px, 15vw, 180px) | 800 | -0.04em |
| Section label | clamp(14px, 1.5vw, 18px) | 600 | 0.15em, uppercase |
| Body text | clamp(14px, 1.8vw, 18px) | 400 | normal |
| Subtitle | clamp(16px, 2.5vw, 28px) | 400 | normal |
| Chip text | clamp(12px, 1.4vw, 15px) | 500 | normal |
Before outputting the final HTML, verify:
<style> tag<script> tagreveal-element with sequential delaysTOTAL_SLIDES constant matches actual slide countdata-slide attributes are sequential from 0data-target set::before top highlight gradienttouchmove preventDefault) is includedwill-change hints on slide elements.note-content[data-note="N"] matches a slide (0-indexed)exportPdf() snaps count-up values to targets before window.print()@media print stylesheet hides UI chrome and shows all slides as pages@page { size: landscape; margin: 0; } is setupdateNotes() is called in goToSlide() when notes are openInclude these inline SVGs as needed:
<path d="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"/><path d="M19 3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14m-.5 15.5v-5.3a3.26 3.26 0 0 0-3.26-3.26c-.85 0-1.84.52-2.32 1.3v-1.11h-2.79v8.37h2.79v-4.93c0-.77.62-1.4 1.39-1.4a1.4 1.4 0 0 1 1.4 1.4v4.93h2.79M6.88 8.56a1.68 1.68 0 0 0 1.68-1.68c0-.93-.75-1.69-1.68-1.69a1.69 1.69 0 0 0-1.69 1.69c0 .93.76 1.68 1.69 1.68m1.39 9.94v-8.37H5.5v8.37h2.77z"/><path d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34-.46-1.16-1.11-1.47-1.11-1.47-.91-.62.07-.6.07-.6 1 .07 1.53 1.03 1.53 1.03.87 1.52 2.34 1.07 2.91.83.09-.65.35-1.09.63-1.34-2.22-.25-4.55-1.11-4.55-4.92 0-1.11.38-2 1.03-2.71-.1-.25-.45-1.29.1-2.64 0 0 .84-.27 2.75 1.02.79-.22 1.65-.33 2.5-.33.85 0 1.71.11 2.5.33 1.91-1.29 2.75-1.02 2.75-1.02.55 1.35.2 2.39.1 2.64.65.71 1.03 1.6 1.03 2.71 0 3.82-2.34 4.66-4.57 4.91.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2z"/><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z"/>All SVGs use viewBox="0 0 24 24" and fill="currentColor" at 18x18px.
When the user selects a theme, apply these complete variable sets:
--bg-primary: #000000
--text-primary: #ffffff
--text-secondary: #86868b
--accent-1: #0071e3
--accent-1-light: #2997ff
--accent-2-start: #6e3aff
--accent-2-end: #a855f7
--accent-warm: #ff6723
--accent-green: #30d158
--card-bg: rgba(255, 255, 255, 0.04)
--card-border: rgba(255, 255, 255, 0.08)
Font: -apple-system, BlinkMacSystemFont, SF Pro, system
Effects: grain overlay, ambient orbs, hero glow
--bg-primary: #ffffff
--bg-secondary: #fafafa
--text-primary: #1d1d1f
--text-secondary: #86868b
--text-muted: #aeaeb2
--accent-1: #0071e3
--card-bg: rgba(0, 0, 0, 0.03)
--card-border: rgba(0, 0, 0, 0.08)
Font: 'Geist', -apple-system, system
Effects: subtle shadows, no grain, no orbs
--bg-primary: #1a1210
--text-primary: #f5efe8
--text-secondary: #a89585
--accent-1: #e8734a
--accent-2-end: #c4956a
--card-bg: rgba(245, 239, 232, 0.04)
--card-border: rgba(245, 239, 232, 0.08)
Font: 'Playfair Display', Georgia, serif (headings) + 'Source Sans 3', system (body)
Effects: warm grain, warm orbs, paper texture
--bg-primary: #0a0a1a
--text-primary: #e0e0ff
--text-secondary: #6b7280
--accent-1: #00f0ff
--accent-2-end: #ff00aa
--accent-warm: #7c3aed
--card-bg: rgba(0, 240, 255, 0.04)
--card-border: rgba(0, 240, 255, 0.12)
Font: 'JetBrains Mono', monospace (headings) + 'Space Grotesk', system (body)
Effects: neon glow borders, scanline overlay, grid background
--bg-primary: #faf8f5
--text-primary: #2d2a26
--text-secondary: #7a7470
--accent-1: #2d6a4f
--accent-2-end: #b07d62
--card-bg: rgba(45, 106, 79, 0.04)
--card-border: rgba(45, 106, 79, 0.1)
Font: 'DM Sans', system (headings + body)
Effects: no grain, organic blob shapes instead of orbs, warm shadows
When analyzing content, use these heuristics to suggest themes and slide types:
| Content Type | Suggested Theme | Typical Slides |
|---|---|---|
| Startup pitch | Dark Cinematic | Hero, Stats, Chapter, Content Card, Feature Grid, CTA |
| Portfolio | Dark Cinematic or Light Minimal | Hero, Stats, Chapter, Content Card x N, Feature Grid, List, CTA |
| Product launch | Neon Cyberpunk or Dark Cinematic | Hero, Chapter, Split, Feature Grid, Comparison, Stats, CTA |
| Educational | Light Minimal or Earth Organic | Hero, Chapter, Content Card, Data, Timeline, List, CTA |
| Report / research | Light Minimal | Hero, Stats, Data, Content Card, Comparison, List, CTA |
| Personal story | Warm Editorial | Hero, Timeline, Quote, Content Card, List, CTA |
| Company overview | Dark Cinematic | Hero, Stats, Chapter, Content Card, Feature Grid, Timeline, CTA |
Always tell the user WHY you're recommending a specific theme. Be specific about the content, not generic.
Structure slides to follow a story arc:
Each slide should communicate ONE idea. If a slide has too much content, split it into multiple slides.
Keep total slide count between 8-15 for optimal pacing. Fewer than 8 feels thin; more than 15 feels long.
npx claudepluginhub sacredvoid/presentation-chef --plugin presentation-chefGenerates zero-dependency, animation-rich HTML presentations from scratch or by converting PPT/PPTX files. Ensures exact viewport fit for browser-based slide decks with distinctive designs.
Creates reveal.js presentations with themes, multi-column layouts, code highlighting, animations, speaker notes, and custom styling. Generates HTML + CSS with no build step. Use for slides, decks, or slideshows.
Generates self-contained HTML or React slide decks for fullscreen presentations from ideas, outlines, existing content, or documents.