Build interactive, presentation-grade slide decks as live SPAs—not static PDFs. Vite + React 19 + TanStack Router, 1920x1080 native resolution, WebGPU shader backgrounds, Framer Motion orchestration, collapsible sidebar navigation, keyboard controls, an optional access gate, and deploy to Cloudflare Workers / Vercel / Netlify.
When to Use This Skill
- Live presentations (investor pitches, product demos, conference talks)
- Interaction-rich decks with hover states, content swaps, tooltips
- WebGPU shader backgrounds for dramatic cover slides
- Decks that need an access gate (client-side; see Access Gate section for its limits)
- Keyboard-navigated presentations with sidebar navigation
Domains Supported
| Domain | Use Cases |
|---|---|
| Business & Finance | KPI dashboards, revenue charts, growth trends, pricing |
| Healthcare | Patient metrics, clinical outcomes, treatment timelines |
| Wellness & Coaching | Transformation journeys, milestone celebrations, quotes |
| AI/ML Research | Model architecture, training metrics, STT/TTS pipelines |
Quick Start Workflow
1. Audit Reference Screenshots
Review existing example screenshots for aesthetic consistency before designing.
open ~/.claude/skills/overwatch-slidedeck/assets/examples/
15 reference screenshots demonstrate the canonical aesthetic: dark backgrounds, orange accent (#ff6e41), Playfair Display headings, interactive hover states, WebGPU shader covers.
2. Scaffold New Project
cp -r ~/.claude/skills/overwatch-slidedeck/assets/scaffold/ ./my-deck
cd my-deck
npm install
npm run dev # Opens at http://localhost:5173
# Navigate to /deck/1
3. Review Iconography Options
| Domain | Key Icons |
|---|---|
| AI/ML | Brain, Cpu, Database, Layers, Network, Waveform |
| Business | TrendingUp, DollarSign, PieChart, Target, Users |
| Healthcare | Heart, Activity, Stethoscope, ShieldCheck |
| Parenting | Baby, Users, HandHeart, Shield, Brain, Puzzle |
| Spirituality | Compass, Flame, Mountain, Sunrise, Lightbulb |
Icon libraries (not included in scaffold—install as needed):
- Lucide (1,500+):
lucide-react— https://lucide.dev/icons - Tabler (5,900+):
@tabler/icons-react— https://tabler.io/icons - Phosphor (7,000+):
@phosphor-icons/react— https://phosphoricons.com
4. Plan Content Structure
- Define sections — Group content into 4-8 logical sections
- Outline slides — 3-5 slides per section
- Identify slide types — Cover, social proof, split text, feature grid, timeline, quote, CLI demo, etc. (14 types available—see
references/slide-templates.md) - Gather assets — Screenshots, diagrams, logos, expert photos
- Select icons — Map icons to sections/concepts
What separates a hand-crafted deck from AI slop. VCs and executives now read dozens of generated decks a week and can spot them instantly — same stock layouts, same buzzword density, same fabricated polish. The failure modes to design against:
- No unsourced numbers. "47% growth" with no traceable source is the signature tell of a generated deck. Every statistic on a slide comes from the research phase (step 6) with a source the presenter can name. If the number can't be sourced, cut the slide, not the sourcing.
- Headlines carry the insight. Title the data slide "Churn dropped 31% after the onboarding fix," never "Churn Data." A slide whose headline could sit on any company's deck says nothing.
- Specificity over vocabulary. "Reviews 1,324 documents in 40 seconds" beats "leverages cutting-edge AI to transform document workflows." When a sentence would survive on a competitor's deck unchanged, rewrite it.
- One motion grammar per deck. Pick one entrance vocabulary (which AnimatedItem variants, one duration scale, one easing) and hold it across every slide — motion consistency is what makes 20 slides read as one designed object rather than a template assembly.
- Decks travel without the presenter. Boards forward slides; executives read them on phones at night. Every slide must be self-explanatory — hover-revealed content needs its key point visible before the hover.
5. Brand Color Extraction
Extract and map brand colors from the client/company site before building slides.
# Scrape the client/company site for brand identity
python3 ~/.claude/skills/firecrawl/scripts/firecrawl_api.py scrape "https://client-site.com" --formats branding
# Or take a screenshot for visual reference
agent-browser open https://client-site.com
agent-browser screenshot /tmp/brand-reference.png
Override the primary accent in src/styles/globals.css:
:root {
--color-orange: #your-brand-color;
--color-orange-muted: #your-darker-variant;
}
Anti-patterns:
- Never use bright reds (
#DC2626,#E11D48)—they read as error/danger - Never use saturated yellows (
#fbbf24) on light backgrounds—they disappear - Darken to amber-800/900 range (
#92400E,#B45309) for readability
6. Research & Content Gathering
Available research tools (separate skills/CLIs):
| Tool | Skill/CLI | Purpose |
|---|---|---|
| Firecrawl | firecrawl CLI or Firecrawl skill |
Web scraping, convert URLs to markdown |
| Exa Search | exa-search skill |
AI-powered neural search, code examples |
| Reddit JSON | Native curl (no auth) | User feedback, pain points, discussions |
# Reddit JSON API (no auth required)
curl "https://www.reddit.com/r/{SUBREDDIT}/search.json?q={QUERY}&limit=25&sort=relevance"
# Exa Search (AI-powered)
exa-search "{domain} best practices" --type article
# Firecrawl - competitor decks, methodology pages
firecrawl scrape https://example.com/methodology
7. Build Slides
Each slide lives in src/slides/ and uses the SlideWrapper:
import { SlideWrapper } from "../components/layout/SlideWrapper";
import { Headline } from "../components/layout/Headline";
import { BodyText } from "../components/layout/BodyText";
export default function ProblemSlide() {
return (
<SlideWrapper mode="dark">
<Headline>The Problem</Headline>
<BodyText className="mt-8">Your content here</BodyText>
</SlideWrapper>
);
}
Register each slide in src/config.ts:
export const slides: SlideEntry[] = [
{ id: "cover", fileKey: "01-cover", title: "Cover", shortTitle: "Cover" },
{ id: "problem", fileKey: "02-problem", title: "The Problem", shortTitle: "Problem" },
];
const slideModules = {
"01-cover": () => import("./slides/01-cover"),
"02-problem": () => import("./slides/02-problem"),
};
Slide modes: dark (deep charcoal, default), white (off-white, text-heavy analytical), orange (accent background, emphasis/quotes). Most slides use dark; white for analytical content; orange sparingly.
Slide transitions: the shell animates between slides via AnimatePresence, direction-aware (forward enters from the right, backward from the left). Deck-level default in config.transition (none | fade | slide | scale, default fade) with config.transitionDuration in ms; per-slide overrides via the same fields on a SlideEntry. One transition type per deck is the rule — per-slide overrides exist for section dividers and the closing slide, not variety. prefers-reduced-motion and ?static=1 both force none.
Speaker notes: notes?: string on any SlideEntry (or notes: in the YAML spec). Notes render only in presenter mode — the audience window never shows them.
8. Design QA Checklist
Before deploying, verify with agent-browser screenshots at http://localhost:5173:
| Check | Requirement |
|---|---|
| ✅ Slide modes | SlideWrapper mode matches content type (dark/white/orange) |
| ✅ Sidebar | Auto-collapses after 3s, hover expands, nav indicator tracks |
| ✅ Shader fallback | WebGPU cover degrades to animated CSS gradient |
| ✅ Keyboard nav | ArrowRight/Space (next), ArrowLeft (prev), Home/End |
| ✅ Access gate | If configured, blocks without correct password |
| ✅ Typography | Playfair Display for headlines, Inter for body, IBM Plex Mono for labels |
| ✅ Hover states | HoverLift, GlowBorder, ExpandableCard respond correctly |
| ✅ Animations | StaggeredAnimation entrance on each slide, no layout thrash |
| ✅ Mobile block | Shows "Desktop Required" below 375px viewport |
| ✅ Slide counter | Bottom-right counter shows correct 01/NN |
| ✅ Transitions | Forward/backward navigation animates in opposite directions; one grammar deck-wide |
| ✅ Reduced motion | With prefers-reduced-motion, transitions and auto-cycling stop, content renders final-state |
| ✅ Presenter sync | /presenter/1 shows notes + next slide; navigation syncs both windows |
| ✅ Deep links | Invalid /deck/N clamps to a valid slide; deployed deep links don't 404 |
# Visual verification workflow — slides change by URL, not scroll
npm run dev &
agent-browser open http://localhost:5173/deck/1
agent-browser screenshot /tmp/slide-01.png
agent-browser open http://localhost:5173/deck/2
agent-browser screenshot /tmp/slide-02.png
9. Present
Open /presenter/1 in a second window (or second screen) alongside the audience deck:
- Current slide, next-slide preview, speaker notes, elapsed timer, slide counter
- Keyboard navigation works in either window; a
BroadcastChannel('overwatch-deck')keeps both in sync — navigate in the presenter, the audience follows, and vice versa - Same-origin windows only (two tabs/windows of the same deployment); for remote presenting, share the audience window over the call and drive from the presenter
10. Export (PNG / PDF)
The deck stays live-first; export is derived output for board packets and email:
cd ~/.claude/skills/overwatch-slidedeck/scripts && npm install # once: playwright, pdf-lib, yaml
npm run dev & # deck must be serving
node ~/.claude/skills/overwatch-slidedeck/scripts/export-deck.mjs --url http://localhost:5173 --out ./export --pdf
Each slide renders at 1920x1080 with ?static=1 and reduced-motion emulation so entrances land in their final state — no mid-animation frames. --pdf merges the PNGs into deck.pdf. Shader covers export as their current frame; expect the live deck to look better than its export, by design.
11. Deploy
# Build static SPA
npm run build
# Output: dist/
# Deploy to Cloudflare Workers (wrangler.jsonc included in scaffold)
npx wrangler deploy
# Deploy to Vercel (vercel.json included)
npx vercel
# Deploy to Netlify (public/_redirects included)
npx netlify deploy --prod
SPA rewrites for all three hosts ship in the scaffold, so /deck/7 opened directly on a deployed URL resolves instead of 404ing.
Component Library
Layout Components (10)
| Component | Import | Purpose |
|---|---|---|
SlideWrapper |
layout/SlideWrapper |
Full-slide container with mode prop (dark/white/orange) |
Headline |
layout/Headline |
140px display title |
SubHeadline |
layout/SubHeadline |
72px secondary title |
Eyebrow |
layout/Eyebrow |
Small caps category label |
BodyText |
layout/BodyText |
Body copy (sm/md/lg) |
MonoLabel |
layout/MonoLabel |
Monospace label (sm/md/lg) |
Divider |
layout/Divider |
Configurable hr (thin/medium/thick) |
SplitLayout |
layout/SplitLayout |
Two-column with ratio (1:1, 2:1, 1:2, 3:2, 2:3) |
CenterLayout |
layout/CenterLayout |
Centered flex container |
GridLayout |
layout/GridLayout |
2/3/4 column grid |
Interaction Components (18)
| Component | Import | Purpose |
|---|---|---|
AnimatedItem |
interactions/AnimatedItem |
Entrance variants: fade/slideUp/slideLeft/scale |
StaggeredAnimation |
interactions/StaggeredAnimation |
Parent container with stagger timing |
HoverLift |
interactions/HoverLift |
Hover elevation (sm/md/lg) |
GlowBorder |
interactions/GlowBorder |
Mouse-tracking gradient border |
ExpandableCard |
interactions/ExpandableCard |
Click-to-expand with layout animation |
Accordion |
interactions/Accordion |
Collapsible sections |
TabGroup |
interactions/TabGroup |
Tabbed content panels |
QuoteRotator |
interactions/QuoteRotator |
Auto-cycling quotes with dot indicators |
ContentRotator |
interactions/ContentRotator |
Auto-cycling arbitrary ReactNode children with dots |
SocialProofCard |
interactions/SocialProofCard |
Platform-styled testimonial (twitter/linkedin/testimonial) |
TerminalTyper |
interactions/TerminalTyper |
Typewriter CLI demo with macOS terminal chrome |
TimelineConnector |
interactions/TimelineConnector |
Horizontal roadmap with animated SVG connectors |
InfiniteScrollTicker |
interactions/InfiniteScrollTicker |
Vertical marquee with gradient masks |
ProgressBar |
interactions/ProgressBar |
Animated horizontal fill bar with label |
RevealCaption |
interactions/RevealCaption |
Hover caption overlay |
Tooltip |
interactions/Tooltip |
Position-aware tooltip |
PulseIndicator |
interactions/PulseIndicator |
Pulsing dot + expanding ring |
Skeleton |
interactions/Skeleton |
Loading placeholder |
Graphics Components (4)
| Component | Import | Purpose |
|---|---|---|
WebGPUCanvas |
graphics/WebGPUCanvas |
WebGPU shader host + CSS gradient fallback |
ParticleField |
graphics/ParticleField |
Floating particle animation |
NetworkGraph |
graphics/NetworkGraph |
Pulsing node-ring visualization |
SVGRadarChart |
graphics/SVGRadarChart |
Zero-dependency SVG radar chart with pathLength animation |
Utility Hooks (3)
| Hook | Import | Purpose |
|---|---|---|
useAutoCycle |
hooks/useAutoCycle |
Generic auto-advancing timer: [currentItem, index, setIndex]. Pauses while the tab is hidden; no-ops under reduced motion |
useTypewriter |
hooks/useTypewriter |
Character-by-character text reveal: { displayText, isComplete }. Pauses while hidden; renders full text under reduced motion |
useReducedMotion |
hooks/useReducedMotion |
Live prefers-reduced-motion subscription — every animated component reads it |
Every auto-cycling and entrance component honors prefers-reduced-motion (final state, no cycling) and interactive components (TabGroup, Accordion, Tooltip) carry ARIA roles and keyboard navigation. Global keyboard shortcuts ignore keypresses on interactive targets.
Navigation
- Sidebar: Auto-collapses after 3s, hover to expand, spring-animated (
stiffness: 400, damping: 30) - Keyboard: ArrowRight/Space (next), ArrowLeft (prev), Home/End
- URL-based:
/deck/1,/deck/2, etc. via TanStack Router - Preloading: Adjacent slides (n-1, n+1, n+2) are preloaded for instant navigation
Access Gate
Set config.auth.password in src/config.ts:
auth: { password: "your-password" } // Empty string = no gate
Supports ?pw=your-password URL param for direct access. State persists via sessionStorage.
This is an access gate, not security: it's client-side, so the password and all slide content ship in the JavaScript bundle to anyone who requests the URL. It keeps casual link-forwards from opening the deck — the right tool for "don't let this circulate ahead of the meeting." For a deck that genuinely cannot leak (unannounced financials, M&A), put real authorization in front of the assets — Cloudflare Access or Vercel deployment protection — and keep the gate for UX.
Data-Driven Authoring
For faster deck creation, provide a YAML spec file describing each slide's type, content, and mode:
node ~/.claude/skills/overwatch-slidedeck/scripts/init-deck-from-spec.mjs deck-spec.yaml ./my-deck
The script copies the scaffold, generates config.ts, and creates empty slide files. Fill in each slide using the spec + references/slide-templates.md.
See references/deck-schema.md for the full YAML schema covering all 14 slide types.
Custom Asset Generation
Generate custom icons and graphics matching the Overwatch aesthetic:
Pipeline: Nano Banana Pro → ImageMagick → Potrace → SVG Cleanup
# Generate an icon matching the dark/orange aesthetic
nano-banana-pro "Minimalist neural network icon, flat design,
3 solid colors, orange #ff6e41 on dark #0c0c0e, geometric"
# Vectorize
magick output.png -posterize 4 -colors 4 processed.png
potrace processed.pbm -s -o icon.svg
# Optimize
svgo icon.svg -o icon-optimized.svg
Required tools:
nano-banana-proskill (Gemini 3 Pro image generation)- ImageMagick (
brew install imagemagick) - Potrace (
brew install potrace) - SVGO (
npm install -g svgo)
Reference Documentation
| File | Purpose |
|---|---|
references/design-system.md |
Color tokens, typography, dimensions, 3 slide modes |
references/interactions.md |
Animation patterns, timing, 18 interaction components + the useAutoCycle/useTypewriter hooks |
references/shaders.md |
WebGPU setup, WGSL syntax, custom shaders, fallback |
references/slide-templates.md |
14 slide type templates with component composition |
references/advanced-patterns.md |
6 domain-specific patterns (waterfall, carousel, strikethrough, dual-layer shader) |
references/deck-schema.md |
YAML schema for data-driven deck authoring |
Tips
- One idea per slide — keep content density low; Overwatch decks are visual, not textual
- Slide modes matter — dark for most content, white for analytical/text-heavy, orange for emphasis moments
- Import from
"motion/react"— not"framer-motion"(the package ismotionv12+) - Shader fallback — always test with WebGPU disabled; the CSS gradient fallback must look intentional
- Test keyboard nav — ArrowRight/Space/ArrowLeft/Home/End should work on every slide
- Preload wisely — the scaffold preloads n-1, n+1, n+2; adjust in
routes/deck.$slide.tsxif needed - Deploy early — test on Cloudflare/Vercel before the presentation; local dev can mask font loading issues
Related Skills
- aldea-slidedeck — static single-file HTML decks (Blueprint Mode). If the deck ships as a file rather than a URL, use aldea.
- conductor-motion — self-contained behavioral animation demos (typewriter, progress, streaming text). Borrow its timing constants for in-slide motion; don't rebuild its patterns as React components.
- component-gallery — pattern research for slide content (real product UI, tool-call displays, dashboards). Query it before inventing an interface mock.
- minoan-frontend-design — creative direction and typography once the deck structure is set.