| name | revealjs-presentation |
| description | Build interactive HTML slide presentations using Reveal.js + Chart.js. Use when asked to create a slide deck, pitch deck, presentation, or slideshow. Creates a single self-contained .html file with animations, charts, and polished UI. Can also publish presentations directly to zo.space as live page routes.
|
| compatibility | Created for Zo Computer |
| metadata | {"author":"skeletorjs.zo.computer","version":1} |
Reveal.js Presentation Skill
Build self-contained HTML slide decks using Reveal.js 5 + Chart.js 4. Every output is a single .html file that opens in any browser. Optionally publish to zo.space as a live page route.
Two Output Modes
1. Local HTML File (default)
Creates a single .html file saved to the user's workspace. No build step, no dependencies. Open in any browser.
2. zo.space Page Route (on request)
Publishes the presentation as a live page on the user's <handle>.zo.space. Uses the pre-installed reveal.js package + inline styles. Use when the user says "publish", "put it on zo.space", "make it live", or "share it online".
The publish script at scripts/publish.ts handles the conversion from local HTML to a zo.space-compatible React page route. Run it via:
bun /home/workspace/Skills/revealjs-presentation/scripts/publish.ts --file <path-to-html> --route <path> [--public]
When publishing to zo.space:
- Reveal.js is imported from the pre-installed package (
import Reveal from "reveal.js")
- Chart.js is loaded from CDN via dynamic script injection in useEffect
- All CSS is inlined in the component
- The route defaults to private unless
--public is passed
- After publishing, check
get_space_errors() for build issues
Core Stack (Local HTML)
| Library | CDN | Purpose |
|---|
| Reveal.js 5.1 | cdn.jsdelivr.net/npm/reveal.js@5.1.0 | Slide engine |
| Chart.js 4.4 | cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js | Charts |
| Google Fonts | fonts.googleapis.com | Typography |
Approved Extension Libraries
Reach for these when the core stack can't cover the requirement. All are CDN-loadable and UMD-compatible.
Data Visualization
| Need | Library | CDN |
|---|
| Custom charts | D3.js v7 | cdn.jsdelivr.net/npm/d3@7 |
| Animated counters | CountUp.js | cdn.jsdelivr.net/npm/countup.js@2 |
| Geographic maps | Leaflet.js | cdn.jsdelivr.net/npm/leaflet@1.9 |
| Flow diagrams | Mermaid.js | cdn.jsdelivr.net/npm/mermaid@10 |
Animation & Motion
| Need | Library | CDN |
|---|
| Complex sequences | GSAP | cdn.jsdelivr.net/npm/gsap@3 |
| Particle backgrounds | tsParticles | cdn.jsdelivr.net/npm/tsparticles@2 |
| Typewriter effect | Typed.js | cdn.jsdelivr.net/npm/typed.js@2 |
| Lottie animations | lottie-web | cdn.jsdelivr.net/npm/lottie-web@5 |
Content & Code
| Need | Library | CDN |
|---|
| Code highlighting | Prism.js | cdn.jsdelivr.net/npm/prismjs@1 |
| Math / LaTeX | MathJax 3 | cdn.jsdelivr.net/npm/mathjax@3 |
| Icons | Lucide | unpkg.com/lucide@latest/dist/umd/lucide.js |
Compatibility Checks for New Libraries
Before adding any library not listed above:
- Can CSS or existing stack handle it? Many effects don't need a library.
- UMD build on CDN? Must work via
<script src="..."> tag. ES module-only packages won't work.
- No conflict with Reveal.js? Watch for global keyboard/scroll listeners, body transform manipulation.
- Init after slide visible? Use
slidechanged event handler, not DOMContentLoaded.
- < 200KB gzipped? Check bundlephobia.com before adding.
File Structure (Local HTML)
Always output a single .html file:
<head>
fonts → reveal.css → theme → chart.js → <style>
</head>
<body>
SVG symbol defs (logos, icons)
<div class="reveal">
<div class="slides">
<section> x N slides
</div>
</div>
reveal.js script → init script → chart build functions
</body>
Boilerplate Shell
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>PRESENTATION TITLE</title>
<link href="https://fonts.googleapis.com/css2?family=YOUR+FONTS&display=swap" rel="stylesheet"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.css"/>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/theme/white.css"/>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
:root {
--bg: #FAFAF8;
--dk: #111111;
--dk2: #333333;
--gray: #888888;
:#YOUR_COLOR;
:;
:, system-ui, sans-serif;
:, , monospace;
}
{ : (--f-sans); }
{
: left;
: ;
: ;
}
{
: relative;
: ; : ;
: flex; : column;
: ;
: hidden;
: (--bg);
: (--dk);
: border-box;
}
> * { : relative; : ; }
{ : ; }
{ : fromLeft . ease forwards; }
{ : fromRight . ease forwards; }
{ : fromBelow . ease forwards; }
{ : fadeUp . ease forwards; }
{ : pop . ease forwards; }
{ :. }
{ :. }
{ :. }
{ :. }
{ :. }
{ :. }
fromLeft { {:;:(-)} {:;:none} }
fromRight { {:;:()} {:;:none} }
fromBelow { {:;:()} {:;:none} }
fadeUp { {:} {:} }
pop { {:;:(.)} {:;:()} }
{
: relative;
: ;
: ;
: ;
}
{
: absolute;
: ;
: ;
: ;
}
{ :; :; :; }
{ :; :; :; }
{ :; :; :; }
{
: (--f-mono);
: ;
: .;
: uppercase;
: (--accent);
: ;
}
{ : (--f-mono); }
{ :flex; :row; :; }
{ :flex; :column; :; }
{ :grid; :fr fr; :; }
{ :grid; :fr fr fr; :; }
{ :grid; :fr fr fr fr; :; }
{
: ;
: solid (--border);
: ;
: ;
}
{
: (--accent);
: ;
: none;
}
{
:inline-flex; :center;
: ; :;
:; :; :.;
: (,,,.);
}
Slide Anatomy
Every slide follows this pattern:
<section>
<div class="S">
<div class="stag pre af d1">Category</div>
<div class="h2 pre al d2">Headline with <span style="color:var(--accent)">accent</span></div>
<div class="pre af d3" style="color:var(--gray);font-size:16px;margin-bottom:20px;">
Supporting text.
</div>
<div class="g2 pre af d4" style="flex:1;min-height:0;">
</div>
</div>
</section>
Layout Patterns
2-Column Split
<div class="row" style="flex:1;gap:32px;">
<div class="col" style="flex:1;"></div>
<div class="col" style="flex:1.2;"></div>
</div>
Card Grid (3-up)
<div class="g3" style="flex:1;min-height:0;">
<div class="card pre ap d3">...</div>
<div class="card pre ap d4">...</div>
<div class="card pre ap d5">...</div>
</div>
Hero Stat
<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:4px;">
<div style="font-size:64px;font-weight:900;color:var(--accent);">$5.2B</div>
<div style="font-size:18px;color:var(--gray);">market</div>
</div>
CSS Treemap
<div style="display:flex;flex-direction:column;gap:4px;flex:1;">
<div style="display:flex;gap:4px;flex:34;">
<div style="flex:1;background:#COLOR;border-radius:6px;padding:10px;color:#fff;">
<div style="font-size:11px;opacity:.7;">Category</div>
<div style="font-weight:700;">34%</div>
</div>
</div>
</div>
Numbered Steps / Timeline
<div style="display:flex;gap:0;flex:1;position:relative;">
<div style="position:absolute;top:28px;left:28px;right:28px;height:2px;background:var(--border);z-index:0;"></div>
<div style="flex:1;display:flex;flex-direction:column;align-items:center;text-align:center;position:relative;z-index:1;">
<div style="width:56px;height:56px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px;flex-shrink:0;">1</div>
<div style="font-weight:600;margin-top:10px;font-size:14px;">Step Title</div>
<div style="font-size:12px;color:var(--gray);margin-top:4px;">Description</div>
</div>
</div>
Comparison Table (CSS grid)
<div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:1px;background:var(--border);border-radius:10px;overflow:hidden;">
<div style="background:var(--dk);color:#fff;padding:10px 14px;font-size:12px;font-weight:700;">Feature</div>
<div style="background:var(--dk);color:#fff;padding:10px 14px;font-size:12px;font-weight:700;text-align:center;">Option A</div>
<div style="background:var(--dk);color:var(--accent);padding:10px 14px;font-size:12px;font-weight:700;text-align:center;">Option B</div>
<div style="background:#fff;padding:9px 14px;font-size:13px;">Feature name</div>
<div style="background:#fff;padding:9px 14px;font-size:13px;text-align:center;color:var(--gray);">No</div>
<div style="background:#fff;padding:9px 14px;font-size:13px;text-align:center;color:var(--accent);font-weight:600;">Yes</div>
</div>
Chart.js Recipes
Bar Chart
function buildBarChart() {
const ctx = document.getElementById('barChart').getContext('2d');
new Chart(ctx, {
type: 'bar',
data: {
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
datasets: [{
label: 'Revenue',
data: [42, 68, 55, 89, 104, 127],
backgroundColor: 'YOUR_COLOR',
borderRadius: 6,
borderSkipped: false,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { grid: { display: false }, border: { display: false } },
: { : { : }, : { : } }
}
}
});
}
Line Chart
function buildLineChart() {
const ctx = document.getElementById('lineChart').getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
datasets: [{
label: 'Series A',
data: [30, 55, 80, 120],
borderColor: 'YOUR_COLOR_1',
backgroundColor: 'YOUR_COLOR_1_20',
borderWidth: 2.5,
pointRadius: 4,
tension: 0.35,
fill: true,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: { grid: { display: false } },
y: { grid: { color: '#f0f0f0' } }
}
}
});
}
Doughnut
function buildDoughnut() {
const ctx = document.getElementById('doughnutChart').getContext('2d');
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['A', 'B', 'C'],
datasets: [{
data: [40, 30, 30],
backgroundColor: ['#C1','#C2','#C3'],
borderWidth: 0,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
cutout: '65%',
}
});
}
Canvas HTML:
<div class="cw" style="min-height:220px;">
<canvas id="UNIQUE_CHART_ID"></canvas>
</div>
Register in chartMap:
const chartMap = { 3: buildFunnelChart, 7: buildLineChart };
Animation Rules
- Add
.pre to any element to start invisible
- Add direction:
.al (left), .ar (right), .ab (below), .af (fade), .ap (pop)
- Add delay:
.d1 through .d6
- Init script removes
.pre when slide becomes active
Critical Gotchas
- Chart height must be explicit.
.cw wrappers need min-height. Chart.js reads container height; 0px = invisible.
- Dead functions = errors. Every
chartMap entry needs a matching <canvas id>.
- Don't reuse canvas IDs. Each chart needs a unique ID.
- SVG logos: define once, use everywhere with
<symbol> and <use href="#id">.
- Reveal.js default is 960x700. For widescreen:
width: 1280, height: 720.
Slide Type Catalog
| Type | Best For |
|---|
| Hero / Title | Opening, closing |
| Stat Showcase | Key metrics |
| Problem / Opportunity | Setting context |
| Feature Grid | Capability overview |
| Comparison | Competitive positioning |
| Chart | Data storytelling |
| Process / Steps | How it works |
| Timeline | Roadmap |
| Quote / Testimonial | Social proof |
| Closing / CTA | Next steps |
Publishing to zo.space
When the user wants to publish a presentation to zo.space:
- First create the local HTML file as normal
- Run the publish script:
bun /home/workspace/Skills/revealjs-presentation/scripts/publish.ts \
--file /home/workspace/Inbox/my-deck.html \
--route /presentations/my-deck \
--public
- The script extracts slides HTML, CSS variables, custom styles, chart functions, and SVG symbols from the HTML file
- It generates a React page route component that:
- Imports Reveal.js from the pre-installed package
- Loads Chart.js from CDN dynamically if charts are present
- Inlines all custom CSS
- Initializes Reveal.js in a useEffect
- Handles animations and chart building
- The route is deployed via
update_space_route
- Check
get_space_errors() after publishing
The published presentation will be live at https://<handle>.zo.space<route>.
Manual zo.space Publishing (without the script)
If you need more control, you can manually create a page route. The pattern:
import { useEffect, useRef } from "react";
import Reveal from "reveal.js";
export default function Presentation() {
const deckRef = useRef<HTMLDivElement>(null);
const revealRef = useRef<any>(null);
useEffect(() => {
const revealCss = document.createElement("link");
revealCss.rel = "stylesheet";
revealCss.href = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/reveal.css";
document.head.appendChild(revealCss);
const themeCss = document.createElement("link");
themeCss.rel = "stylesheet";
themeCss.href = "https://cdn.jsdelivr.net/npm/reveal.js@5.1.0/dist/theme/white.css";
document.head.appendChild(themeCss);
themeCss.onload = () => {
if (deckRef.current && !revealRef.) {
deck = (deckRef., {
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
});
deck.();
revealRef. = deck;
}
};
{
revealRef.?.();
revealCss.();
themeCss.();
};
}, []);
(
);
}
Quick-Start Checklist
Before writing slides:
When writing each slide:
After all slides: