
Simple Deck
- 246 installs
- 83.7k repo stars
- Updated August 5, 2026
- nexu-io/open-design
simple-deck is an Open Design agent skill that scaffolds a single-file horizontal-swipe HTML pitch or explainer deck from a seed template and layout library so developers can test positioning and narrative flow before fu
About
simple-deck is an Open Design skill for single-file horizontal-swipe HTML decks. It copies assets/template.html—which includes a proven 5-rule iframe navigation script handling scroller detection, capture-phase listeners, auto-focus, and position persistence—then pastes slide layouts from references/layouts.md. The library ships eight layouts with theme rhythm rules requiring light, dark, or hero classes on every slide, serif display typography via CSS variables, and data-screen-label tags per slide. Developers map six :root variables from the active DESIGN.md design system, replace bracketed copy with real content, and run references/checklist.md before delivery. Reach for simple-deck for pitch decks, product overviews, or study material when the deck should match the host brand rather than import a standalone visual theme like replit-deck or magazine-web-ppt.
- minimal pitch decks
- fast narrative scaffold
- stakeholder previews
- positioning tests
- low-friction slides
Simple Deck by the numbers
- 246 all-time installs (skills.sh)
- +1 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #212 of 688 Office & Documents skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/nexu-io/open-design --skill simple-deckAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 246 |
|---|---|
| repo stars | ★ 83.7k |
| Last updated | August 5, 2026 |
| Repository | nexu-io/open-design ↗ |
How do you scaffold a horizontal HTML pitch deck?
Quickly scaffold a minimal pitch or explainer deck to test positioning, narrative flow, and stakeholder buy-in before investing in full visual design.
Who is it for?
Developers and designers who need a fast brand-aligned HTML deck prototype before investing in full slide design tooling.
Skip if: Presentations requiring magazine WebGL aesthetics, Replit Slides themes, or multi-file PPTX export pipelines instead of a single HTML deck.
When should I use this skill?
A developer asks for a quick pitch deck, product overview slides, or horizontal-swipe HTML presentation bound to DESIGN.md tokens.
What you get
Single-file index.html deck with themed slides, iframe-safe navigation script, and design-system-bound CSS variables.
- Single-file index.html horizontal deck
- Themed slides with data-screen-label tags
By the numbers
- Includes 8 slide layouts in references/layouts.md
- Ships a 5-rule iframe navigation script in assets/template.html
- Maps six :root CSS variables from DESIGN.md tokens
Files
Simple Deck Skill
Produce a single-file horizontal-swipe HTML deck using the seed and layout library.
Resource map
simple-deck/
├── SKILL.md ← you're reading this
├── assets/
│ └── template.html ← seed: tokens + slide primitives + proven nav script (READ FIRST)
└── references/
├── layouts.md ← 8 paste-ready slide layouts + theme-rhythm rules
└── checklist.md ← P0/P1/P2 self-review (rhythm spot-check at bottom)Workflow
Step 0 — Pre-flight
1. Read `assets/template.html` end-to-end through the <style> block AND the <script> block. The script solves five iframe-specific bugs (real scroller detection, dual capture-phase listeners, auto-focus, no scrollIntoView, position persistence) — do not rewrite it. 2. Read `references/layouts.md` so you know the 8 layouts. Pay special attention to the "Theme rhythm" section — it's the rule that prevents the deck from feeling sleepy. 3. Read the active DESIGN.md — map its tokens to the six :root variables in the seed.
Step 1 — Copy the seed
Copy assets/template.html to the project root as index.html. Replace the six :root variables with the active design system's tokens. Replace the page <title>.
Step 2 — Decide slide count + theme rhythm BEFORE writing any slide
Default: 6 slides unless the brief says otherwise.
| Audience / format | Slides |
|---|---|
| Product overview / lightning talk (5–10 min) | 6 |
| Pitch deck (15 min) | 8–10 |
| Investor update / longer talk (20–30 min) | 12–18 |
Then write out the rhythm before any HTML — for example, 8 slides:
01 hero light center Cover
02 light Problem
03 hero dark center Big stat
04 light Three points
05 dark Pipeline
06 hero light center Quote
07 light Before / after
08 hero dark center AskA healthy sequence has:
- No 3+ same theme in a row
- ≥ 1
hero darkAND ≥ 1hero light(for 8+ slides) - Alternating breath every 3–4 slides
Show this rhythm sketch to the user before writing slide HTML — they can redirect cheaply.
Step 3 — Paste and fill
For each planned slide, copy the matching <section> from layouts.md into the body. Replace bracketed text with real, specific copy. No filler / no lorem. If a slide feels empty, the layout is wrong — pick a different one.
Tag each slide with data-screen-label="01 Cover", "02 Problem", etc., in the order you wrote them. (The seed's first three slides already do this — extend the pattern.)
Step 4 — Self-check
Run through references/checklist.md. The "Theme rhythm spot-check" at the end is non-negotiable:
grep 'class="slide' index.htmlRead the resulting class list. If you see light × 4 in a row, swap one to dark. If no hero dark exists in an 8+ slide deck, promote one big-stat or closing slide.
Step 5 — Emit the artifact
<artifact identifier="deck-slug" type="text/html" title="Deck Title">
<!doctype html>
<html>...</html>
</artifact>One sentence before the artifact. Stop after </artifact>.
Hard rules
- Theme class on every slide (
light|dark|hero light|hero dark). Bareclass="slide"= regression. - No 3+ same theme in a row.
- Display = serif via `var(--font-display)`.
.h-hero/.h-xl/.h-mdalready enforce. - One accent per slide, used at most twice.
- Don't rewrite the nav script. It's proven.
- No `scrollIntoView()`. Breaks iframe.
- `data-screen-label` on every slide.
<!doctype html>
<!--
OD simple-deck seed.
Single-file horizontal-swipe HTML deck. Each `<section class="slide">`
is one slide; the body uses CSS scroll-snap to lock to slide boundaries.
Keyboard nav (← / → / Space / Home / End) is wired in via the script at
the bottom — DO NOT replace it, it solves five iframe-specific problems
that the naive version silently breaks (see comments in the script).
Theme tokens at the top mirror the web-prototype seed; bind them to the
active DESIGN.md and stop. A deck has TWO surfaces — `.slide.light` and
`.slide.dark` — that swap fg / bg without touching the accent. Use both;
alternating creates the rhythm that prevents visual fatigue.
-->
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>[REPLACE] Deck title · subtitle</title>
<style>
:root {
--bg: #fafaf7;
--surface: #ffffff;
--fg: #1a1916;
--muted: #6b6964;
--border: #e8e5df;
--accent: #c96442;
--accent-soft: color-mix(in oklch, var(--accent) 14%, transparent);
--font-display: 'Iowan Old Style', 'Charter', Georgia, serif;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
--font-mono: ui-monospace, 'JetBrains Mono', 'SF Mono', Menlo, monospace;
}
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
background: var(--bg);
color: var(--fg);
font-family: var(--font-body);
font-size: 18px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
display: flex;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
body::-webkit-scrollbar { display: none; }
p { text-wrap: pretty; }
h1, h2, h3 { text-wrap: balance; }
/* ─── slide surface ─────────────────────────────────────────────── */
.slide {
flex: 0 0 100vw;
width: 100vw;
height: 100vh;
scroll-snap-align: start;
padding: clamp(48px, 7vw, 96px) clamp(48px, 8vw, 112px);
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
overflow: hidden;
}
.slide.light { background: var(--bg); color: var(--fg); }
.slide.dark { background: var(--fg); color: var(--bg); }
.slide.dark .muted { color: rgba(250,250,247,0.7); }
.slide.dark .border { border-color: rgba(250,250,247,0.18); }
.slide.hero { padding-block: clamp(64px, 9vw, 128px); }
/* center alignment variant — for cover, big-stat, big-quote */
.slide.center { align-items: center; text-align: center; justify-content: center; }
.slide.center .body { margin-inline: auto; }
/* ─── eyebrow / kicker ──────────────────────────────────────────── */
.eyebrow {
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent);
margin: 0 0 28px;
}
/* ─── type ──────────────────────────────────────────────────────── */
.h-hero {
font-family: var(--font-display);
font-size: clamp(56px, 8vw, 112px);
line-height: 1.02;
letter-spacing: -0.025em;
margin: 0 0 20px;
max-width: 16ch;
}
.h-xl {
font-family: var(--font-display);
font-size: clamp(40px, 5vw, 64px);
line-height: 1.1;
letter-spacing: -0.02em;
margin: 0 0 20px;
max-width: 20ch;
}
.h-md {
font-family: var(--font-display);
font-size: clamp(28px, 3vw, 36px);
line-height: 1.2;
letter-spacing: -0.015em;
margin: 0 0 16px;
}
.lead {
font-size: clamp(20px, 1.6vw, 24px);
line-height: 1.55;
color: var(--muted);
max-width: 56ch;
margin: 0;
}
.slide.dark .lead { color: rgba(250,250,247,0.78); }
.meta { font-family: var(--font-mono); font-size: 13px; color: var(--muted); }
/* ─── big-stat layout ───────────────────────────────────────────── */
.stat-num {
font-family: var(--font-display);
font-size: clamp(140px, 22vw, 280px);
line-height: 0.9;
letter-spacing: -0.04em;
color: var(--accent);
font-weight: 600;
margin: 0 0 16px;
}
.stat-num .unit { font-size: 0.4em; opacity: 0.85; margin-left: 4px; }
.stat-caption {
font-size: clamp(20px, 1.7vw, 26px);
color: var(--muted);
max-width: 26ch;
margin: 0;
}
/* ─── big-quote layout ──────────────────────────────────────────── */
.quote-mark {
font-family: var(--font-display);
font-size: clamp(140px, 16vw, 200px);
line-height: 0.6;
color: var(--accent);
opacity: 0.18;
margin: 0 0 -32px;
}
.quote-text {
font-family: var(--font-display);
font-size: clamp(28px, 3vw, 44px);
line-height: 1.3;
letter-spacing: -0.01em;
max-width: 28ch;
margin: 0 0 28px;
}
.quote-author { font-size: 14px; color: var(--muted); }
/* ─── 3-column point layout ─────────────────────────────────────── */
.pt-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32px;
margin-top: 40px;
}
.pt {
border-top: 2px solid var(--accent);
padding-top: 16px;
}
.pt h3 { font-size: 19px; font-weight: 500; margin: 0 0 8px; letter-spacing: -0.005em; }
.pt p { color: var(--muted); margin: 0; font-size: 16px; line-height: 1.5; }
.slide.dark .pt p { color: rgba(250,250,247,0.7); }
/* ─── pipeline (numbered steps) ─────────────────────────────────── */
.pipeline {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
margin-top: 40px;
}
.step .nb { font-family: var(--font-mono); font-size: 13px; color: var(--accent); letter-spacing: 0.06em; }
.step h3 { font-family: var(--font-display); font-size: 22px; font-weight: 500; margin: 6px 0 6px; line-height: 1.2; }
.step p { color: var(--muted); margin: 0; font-size: 14px; line-height: 1.5; }
/* ─── before/after ──────────────────────────────────────────────── */
.ba-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 64px; margin-top: 32px; }
.ba-col .ba-label { font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0 0 12px; }
.ba-col h3 { font-family: var(--font-display); font-size: 32px; line-height: 1.15; margin: 0 0 16px; max-width: 16ch; }
.ba-col p { color: var(--muted); font-size: 17px; line-height: 1.5; margin: 0; }
/* ─── image placeholder ─────────────────────────────────────────── */
.ph-img {
background:
linear-gradient(135deg, var(--accent-soft), color-mix(in oklch, var(--fg) 6%, transparent)),
var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
aspect-ratio: 16 / 10;
display: grid; place-items: center;
color: var(--muted);
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.04em;
}
.slide.dark .ph-img { border-color: rgba(250,250,247,0.18); color: rgba(250,250,247,0.5); }
.ph-img.wide { aspect-ratio: 16 / 9; }
.ph-img.tall { aspect-ratio: 3 / 4; }
/* ─── chrome (counter, hint, progress) — fixed, never scrolls ──── */
.deck-counter {
position: fixed;
bottom: 24px; right: 32px;
font-family: var(--font-mono);
font-size: 12px;
color: var(--fg);
background: var(--surface);
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--border);
letter-spacing: 0.04em;
z-index: 10;
}
.deck-hint {
position: fixed;
bottom: 24px; left: 32px;
font-family: var(--font-mono);
font-size: 11px;
color: var(--muted);
letter-spacing: 0.04em;
z-index: 10;
}
.deck-progress {
position: fixed;
top: 0; left: 0;
height: 3px;
background: var(--accent);
width: 0;
z-index: 10;
transition: width 0.18s ease;
}
</style>
</head>
<body>
<!--
┌─────────────────────────────────────────────────────────────────┐
│ PASTE SLIDES FROM references/layouts.md HERE. │
│ ► Each `<section class="slide">` must include a class from: │
│ light | dark | hero light | hero dark │
│ ► No 3+ same-theme in a row. │
│ ► For 8+ slides: ≥ 1 `hero dark` AND ≥ 1 `hero light`. │
│ ► End with a clear CTA / takeaway slide. │
└─────────────────────────────────────────────────────────────────┘
-->
<section class="slide hero light center" data-screen-label="01 Cover">
<div class="eyebrow">[REPLACE] Eyebrow · context · date</div>
<h1 class="h-hero">[REPLACE] One sharp sentence.</h1>
<p class="lead">[REPLACE] One subhead — concrete, not corporate.</p>
</section>
<section class="slide light" data-screen-label="02 Body">
<p class="eyebrow">[REPLACE] Section eyebrow</p>
<h2 class="h-xl">[REPLACE] Body slide headline.</h2>
<p class="lead">[REPLACE] Two sentences explaining the idea — no more.</p>
</section>
<section class="slide hero dark center" data-screen-label="03 Closing">
<div class="eyebrow">[REPLACE] Eyebrow</div>
<h2 class="h-hero">[REPLACE] The take-away.</h2>
</section>
<!-- chrome -->
<div class="deck-progress" id="deck-progress" aria-hidden></div>
<div class="deck-counter" id="deck-counter">1 / 3</div>
<div class="deck-hint">← / → · scroll · swipe</div>
<script>
/*
Five hard rules for deck nav inside an iframe (the OD preview is one).
The naive `document.body.scrollLeft` pattern silently fails: clicks
register, but keyboard does nothing, and the counter freezes at "1 / N"
while the user is on slide 6.
1. Detect the real scroller — body OR documentElement, depending.
2. Listen for scroll on BOTH window and document, capture phase.
3. Listen for keydown on BOTH window and document, capture phase.
4. Auto-focus body so arrow keys work without an upfront click.
5. Never use Element.scrollIntoView — it can yank the host page.
*/
(function () {
var slides = document.querySelectorAll('.slide');
var counter = document.getElementById('deck-counter');
var progress = document.getElementById('deck-progress');
var KEY = 'od-deck-pos';
var active = 0;
function scrollContainers() {
return [document.scrollingElement, document.documentElement, document.body]
.filter(Boolean)
.filter(function (el, idx, arr) { return arr.indexOf(el) === idx; });
}
function overflowX(el) {
if (!el) return 0;
return Math.max(0, (el.scrollWidth || 0) - (el.clientWidth || 0));
}
function scrollLeftOf(el) {
if (!el) return 0;
try { return Number(el.scrollLeft) || 0; } catch (_) { return 0; }
}
function activeScrollLeft() {
var candidates = scrollContainers();
var best = 0;
for (var i = 0; i < candidates.length; i++) {
var left = scrollLeftOf(candidates[i]);
if (Math.abs(left) > Math.abs(best)) best = left;
}
return best;
}
function scroller() {
var candidates = scrollContainers();
var best = candidates[0] || document.documentElement;
var bestScore = -1;
for (var i = 0; i < candidates.length; i++) {
var el = candidates[i];
var score = overflowX(el) + Math.abs(scrollLeftOf(el)) * 2;
if (score > bestScore) {
best = el;
bestScore = score;
}
}
return best;
}
function scrollToLeft(left, behavior) {
var candidates = scrollContainers();
for (var i = 0; i < candidates.length; i++) {
try { candidates[i].scrollLeft = left; } catch (_) {}
try { candidates[i].scrollTo({ left: left, behavior: behavior || 'smooth' }); } catch (_) {}
}
}
function setActive(i) {
active = i;
if (counter) counter.textContent = (i + 1) + ' / ' + slides.length;
if (progress) progress.style.width = (((i + 1) / slides.length) * 100) + '%';
try { localStorage.setItem(KEY, String(i)); } catch (_) {}
}
function go(i) {
var next = Math.max(0, Math.min(slides.length - 1, i));
setActive(next);
scrollToLeft(next * window.innerWidth, 'smooth');
}
function syncFromScroll() {
var i = Math.round(activeScrollLeft() / window.innerWidth);
if (i !== active && i >= 0 && i < slides.length) setActive(i);
}
function onKey(e) {
if (e.defaultPrevented) return;
var t = e.target;
if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA')) return;
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); go(active + 1); }
else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); go(active - 1); }
else if (e.key === 'Home') { e.preventDefault(); go(0); }
else if (e.key === 'End') { e.preventDefault(); go(slides.length - 1); }
}
// listen on both surfaces, capture phase
window.addEventListener('keydown', onKey, true);
document.addEventListener('keydown', onKey, true);
document.addEventListener('scroll', syncFromScroll, { passive: true, capture: true });
window.addEventListener('scroll', syncFromScroll, { passive: true });
// auto-focus
document.body.setAttribute('tabindex', '-1');
document.body.style.outline = 'none';
function focusDeck() { try { window.focus(); document.body.focus({ preventScroll: true }); } catch (_) {} }
document.addEventListener('mousedown', focusDeck);
window.addEventListener('load', focusDeck);
focusDeck();
// restore last-seen position
try {
var saved = parseInt(localStorage.getItem(KEY) || '0', 10);
if (!isNaN(saved) && saved >= 0 && saved < slides.length) {
setActive(saved);
scrollToLeft(saved * window.innerWidth, 'instant');
} else {
setActive(0);
}
} catch (_) { setActive(0); }
})();
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Filebase · Investor deck — Q2 2026</title>
<style>
:root {
--bg: #fafaf9; --fg: #1c1b1a; --muted: #6b6964; --accent: #c96442; --surface: #ffffff;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
background: var(--bg);
color: var(--fg);
font: 18px/1.5 -apple-system, system-ui, sans-serif;
display: flex;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
body::-webkit-scrollbar { display: none; }
.slide {
flex: 0 0 100vw;
height: 100vh;
scroll-snap-align: start;
padding: 80px 96px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
}
.slide.title { background: var(--fg); color: var(--bg); }
.eyebrow { font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); margin-bottom: 28px; }
.slide h1 { font-size: clamp(48px, 7vw, 96px); line-height: 1.05; letter-spacing: -0.025em; margin: 0 0 20px; max-width: 16ch; }
.slide h2 { font-size: clamp(32px, 4vw, 48px); letter-spacing: -0.015em; margin: 0 0 20px; max-width: 20ch; }
.slide .body { font-size: 22px; color: var(--muted); max-width: 56ch; }
.slide.title .body { color: rgba(250,250,249,0.7); }
.slide.big-stat .number { font-size: clamp(120px, 22vw, 280px); line-height: 0.9; letter-spacing: -0.04em; color: var(--accent); margin-bottom: 16px; font-weight: 600; }
.slide.big-stat .caption { font-size: 24px; color: var(--muted); max-width: 24ch; }
.quote-mark { font-family: Georgia, serif; font-size: 200px; line-height: 0.7; color: var(--accent); opacity: 0.18; margin-bottom: -40px; }
.quote-text { font-family: Georgia, serif; font-size: 36px; line-height: 1.3; max-width: 26ch; margin: 0 0 28px; }
.quote-author { font-size: 14px; color: var(--muted); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; margin-top: 40px; }
.grid-3 .pt { border-top: 2px solid var(--accent); padding-top: 16px; }
.grid-3 .pt .h { font-size: 18px; font-weight: 500; margin: 0 0 8px; }
.grid-3 .pt .p { color: var(--muted); margin: 0; font-size: 16px; }
.counter { position: fixed; bottom: 24px; right: 32px; font-family: ui-monospace, monospace; font-size: 12px; color: var(--muted); background: var(--surface); padding: 4px 10px; border-radius: 999px; border: 1px solid #e6e4e0; }
.hint { position: fixed; bottom: 24px; left: 32px; font-size: 11px; color: var(--muted); }
</style>
</head>
<body>
<section class="slide title" data-od-id="slide-1">
<div class="eyebrow" style="color:#c96442;">Filebase · Series B · Q2 2026</div>
<h1>The bandwidth bill is the bug.</h1>
<p class="body">A sync engine that ships only what changed. Backed by 3,184 paying teams.</p>
</section>
<section class="slide" data-od-id="slide-2">
<div class="eyebrow">Problem</div>
<h2>Every other tool re-uploads the whole file.</h2>
<p class="body">Edit one frame in a 4 GB Final Cut project; today's tools sync all 4 GB. The video, post-production, and design industries are eating multi-thousand-dollar bandwidth bills they shouldn't be.</p>
</section>
<section class="slide big-stat" data-od-id="slide-3">
<div class="number">38×</div>
<div class="caption">less data moved over the wire vs. naive sync, on real customer workloads.</div>
</section>
<section class="slide" data-od-id="slide-4">
<div class="eyebrow">Why now</div>
<h2>Three shifts make this market real.</h2>
<div class="grid-3">
<div class="pt"><h3 class="h">Remote post-production</h3><p class="p">Editors don't sit in one room any more. Cloud sync went from convenient to load-bearing.</p></div>
<div class="pt"><h3 class="h">AI workflows</h3><p class="p">Diffusion checkpoints are 7 GB. Engineers iterate on them daily. Existing tools choke.</p></div>
<div class="pt"><h3 class="h">Bandwidth pricing</h3><p class="p">Egress costs 4× what it did in 2022. Storage is cheap; movement is expensive.</p></div>
</div>
</section>
<section class="slide" data-od-id="slide-5">
<div class="quote-mark">"</div>
<p class="quote-text">Filebase pays for itself in the first month. We were going to hire a dedicated DevOps person to babysit our sync — instead we just switched.</p>
<p class="quote-author">— Mira Hassan, CTO at Northwind Studios</p>
</section>
<section class="slide title" data-od-id="slide-6">
<div class="eyebrow" style="color:#c96442;">Ask</div>
<h1>$22M to ship the next sync engine.</h1>
<p class="body">18-month runway, hire 14, expand to enterprise on-prem.</p>
</section>
<div class="counter" id="counter">1 / 6</div>
<div class="hint">← / → to navigate</div>
<script>
const slides = document.querySelectorAll('.slide');
const counter = document.getElementById('counter');
let active = 0;
// Detect the real scroller — when body has `display: flex` + `overflow-x: auto`
// the scroller can be body OR documentElement depending on the host (in
// particular, the OD srcdoc iframe). Pick whichever actually overflows.
function scrollContainers() {
return [document.scrollingElement, document.documentElement, document.body]
.filter(Boolean)
.filter((el, idx, arr) => arr.indexOf(el) === idx);
}
function overflowX(el) {
if (!el) return 0;
return Math.max(0, (el.scrollWidth || 0) - (el.clientWidth || 0));
}
function scrollLeftOf(el) {
if (!el) return 0;
try { return Number(el.scrollLeft) || 0; } catch (_) { return 0; }
}
function activeScrollLeft() {
return scrollContainers().reduce((best, el) => {
const left = scrollLeftOf(el);
return Math.abs(left) > Math.abs(best) ? left : best;
}, 0);
}
function scroller() {
return scrollContainers().reduce((best, el) => {
const bestScore = overflowX(best) + Math.abs(scrollLeftOf(best)) * 2;
const score = overflowX(el) + Math.abs(scrollLeftOf(el)) * 2;
return score > bestScore ? el : best;
}, scrollContainers()[0] || document.documentElement);
}
function scrollToLeft(left, behavior) {
for (const el of scrollContainers()) {
try { el.scrollLeft = left; } catch (_) {}
try { el.scrollTo({ left, behavior: behavior || 'smooth' }); } catch (_) {}
}
}
function go(i) {
const next = Math.max(0, Math.min(slides.length - 1, i));
active = next;
counter.textContent = (next + 1) + ' / ' + slides.length;
scrollToLeft(next * window.innerWidth, 'smooth');
}
function syncFromScroll() {
const i = Math.round(activeScrollLeft() / window.innerWidth);
if (i !== active && i >= 0 && i < slides.length) {
active = i;
counter.textContent = (i + 1) + ' / ' + slides.length;
}
}
function onKey(e) {
if (e.defaultPrevented) return;
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) return;
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); go(active + 1); }
else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); go(active - 1); }
else if (e.key === 'Home') { e.preventDefault(); go(0); }
else if (e.key === 'End') { e.preventDefault(); go(slides.length - 1); }
}
// Listen on both window and document in capture phase so the handler
// fires regardless of which element holds focus inside the iframe.
window.addEventListener('keydown', onKey, true);
document.addEventListener('keydown', onKey, true);
// And listen for scroll on both surfaces — same reason.
document.addEventListener('scroll', syncFromScroll, { passive: true, capture: true });
window.addEventListener('scroll', syncFromScroll, { passive: true });
// Auto-focus body so arrow keys work without a click.
document.body.setAttribute('tabindex', '-1');
document.body.style.outline = 'none';
function focusDeck() { try { window.focus(); document.body.focus({ preventScroll: true }); } catch (_) {} }
document.addEventListener('mousedown', focusDeck);
window.addEventListener('load', focusDeck);
focusDeck();
</script>
</body>
</html>
Simple deck checklist
Run before emitting <artifact>. P0 must pass.
P0 — must pass
- [ ] Every `<section class="slide">` has a theme class. Each is exactly one of:
light,dark,hero light,hero dark. No bareclass="slide". No bareclass="slide hero". - [ ] No 3+ same-theme slides in a row. Mentally list the classes from slide 1 to N — if you see
light light lightanywhere, change the middle one. - [ ] For 8+ slides: at least one `hero dark` AND at least one `hero light`. A long all-light deck is sleepy; a long all-dark deck is heavy.
- [ ] Display headlines use `var(--font-display)` (serif).
.h-hero,.h-xl,.h-mdand.quote-textall enforce this — don't override. - [ ] No raw hex outside `:root`. Every color is
var(--bg)/--fg/--muted/--border/--accent/--surface. Grep#[0-9a-fA-F]{3,8}outside:root{}should return nothing. - [ ] Accent appears at most twice on any single slide. On stat slides, the number itself is the only accent. Don't also color the eyebrow + a button + a border.
- [ ] The 5-rule nav script is intact. Don't replace
scroller()withdocument.body. Don't drop one of the dual capture-phase listeners. Don't usescrollIntoView(). (The seed has the working version — leave it.) - [ ] No `scrollIntoView()` calls. Breaks iframe boundaries.
- [ ] `data-screen-label` on every slide (e.g.
"01 Cover","05 Big stat"). Used by chat for "edit slide 5". - [ ] No invented metrics. Numbers come from the brief or a real source. "10× faster" / "99.9% uptime" without source = remove.
- [ ] No emoji icons / no purple gradients / no rounded boxes with left-border accent. Anti-slop trio.
P1 — should pass
- [ ] Cover is `hero light center`. Inverting cover-to-dark works only when the entire deck is dark.
- [ ] Cover h1 ≤ 8 words. A long cover headline is the writing's job, not the design's.
- [ ] Body lead text under 56ch.
max-width: 56chenforces this — don't override. - [ ] Big-stat slides have one number, not three. If you have 3 numbers, give them 3 slides.
- [ ] One quote per deck. Two pull-quote slides feel like a brochure; one feels like a punctuation mark.
- [ ] Closing slide is decisive. A clear ask, a takeaway sentence, a date — not a "thank you".
- [ ] Numerics in mono. Stats, prices, version numbers, dates use
font-family: var(--font-mono)(the.stat-numalready does;.metadoes). - [ ] At 1280×800 and 1440×900, no overflow. Test by setting the browser to those sizes; nothing clips.
P2 — nice to have
- [ ] Position persists across refresh (the seed's
localStoragesave/restore handles this). - [ ] Top progress bar fills as you advance (already in seed).
- [ ] Counter pill is visible at all times (already in seed).
Theme rhythm spot-check
After you finish, run:
grep 'class="slide' index.htmlRead the class list as a single sequence. The healthy patterns look like:
hero lightlighthero darklightdarkhero lightlighthero darkhero lightlightlightdarkhero lightdarkhero dark
Bad patterns:
light light light light light light— flatdark dark dark dark dark dark— heavyhero hero hero hero— no rest
If your sequence is bad, swap a few middle slides to rebalance.
Simple deck slide layouts
8 paste-ready slide skeletons. Drop into <body> of assets/template.html. Don't write slides from scratch — pick the closest layout, paste, swap copy.
Pre-flight
1. Read `assets/template.html` end-to-end — every class below is defined in its <style> block. The fixed counter, progress bar, hint, and the 5-rule nav script at the bottom are already wired up; do not re-implement them. 2. Plan the slide list AND theme rhythm before pasting any slide. See "Theme rhythm" below — this is the single biggest determinant of whether the deck feels alive or sleepy. 3. Read the active DESIGN.md — map its tokens to the six :root variables in the seed.
Theme rhythm — the rule that prevents 6-slide sleep
Every <section class="slide"> MUST include exactly one of:
light— default white-paper surfacedark— inverted, fg-on-bghero light— same as light + extra padding (for cover, big stat, big quote)hero dark— same as dark + extra padding
Rules:
- No 3+ same-theme slides in a row.
light light light→ boring. - For decks with 8+ slides: at least one
hero darkAND at least onehero light. - A
darkslide every 3–4 slides creates the "breath" that makes the next light slide hit harder. - The cover is almost always
hero light. The closing is oftenhero darkorhero light.
Before emitting, run mentally: list every slide's class. If you see light × 5 in a row, change one to dark.
Class inventory
slidelightdarkherocentereyebrowh-heroh-xlh-mdleadmetastat-numunitstat-captionquote-markquote-textquote-authorpt-gridptpipelinestepnbba-gridba-colba-labelph-imgwidetall
If you reach for a class not on this list, define it in the seed's <style> first.
---
Layout 1 — Cover (slide 1)
hero light center. One eyebrow with date/context, one big serif headline (≤ 8 words for the punch), one lead sentence.
<section class="slide hero light center" data-screen-label="01 Cover">
<div class="eyebrow">Filebase · Series B · Q2 2026</div>
<h1 class="h-hero">The bandwidth bill is the bug.</h1>
<p class="lead">A sync engine that ships only what changed. Backed by 3,184 paying teams.</p>
</section>Layout 2 — Body slide (eyebrow + headline + lead)
The workhorse. Use 3–6× per deck. Vary light / dark for rhythm.
<section class="slide light" data-screen-label="04 Why now">
<p class="eyebrow">Why now</p>
<h2 class="h-xl">Three shifts make this market real.</h2>
<p class="lead">Remote post-production. AI workflows. Bandwidth pricing up 4× since 2022. Storage is cheap; movement is expensive.</p>
</section>Layout 3 — Big stat (data billboard)
hero light center or hero dark center. One number. Don't put 3 numbers on one slide — split into 3 stat slides.
<section class="slide hero dark center" data-screen-label="05 Big stat">
<div class="stat-num">38<span class="unit">×</span></div>
<p class="stat-caption">less data moved over the wire vs. naive sync, on real customer workloads.</p>
</section>Layout 4 — Three-point row
A small headline above three rule-topped points. Each point ≤ 2 sentences.
<section class="slide light" data-screen-label="04 Why now">
<p class="eyebrow">Why now</p>
<h2 class="h-xl">Three shifts make this market real.</h2>
<div class="pt-grid">
<div class="pt">
<h3>Remote post-production</h3>
<p>Editors don't sit in one room any more. Cloud sync went from convenient to load-bearing.</p>
</div>
<div class="pt">
<h3>AI workflows</h3>
<p>Diffusion checkpoints are 7 GB. Engineers iterate on them daily. Existing tools choke.</p>
</div>
<div class="pt">
<h3>Bandwidth pricing</h3>
<p>Egress costs 4× what it did in 2022. Storage is cheap; movement is expensive.</p>
</div>
</div>
</section>Layout 5 — Pipeline (numbered steps)
Workflow / process / how-it-works. Up to 4 steps; if you need more, split across two slides.
<section class="slide dark" data-screen-label="06 Pipeline">
<p class="eyebrow">How it works</p>
<h2 class="h-md">Four passes, end to end.</h2>
<div class="pipeline">
<div class="step">
<span class="nb">01</span>
<h3>Watch</h3>
<p>FS events from kernel, debounced 50ms.</p>
</div>
<div class="step">
<span class="nb">02</span>
<h3>Chunk</h3>
<p>Content-defined splitting, ~64KB target.</p>
</div>
<div class="step">
<span class="nb">03</span>
<h3>Diff</h3>
<p>Bloom-filtered hash compare against remote.</p>
</div>
<div class="step">
<span class="nb">04</span>
<h3>Ship</h3>
<p>Only the chunks the remote doesn't have.</p>
</div>
</div>
</section>Layout 6 — Big quote / pull quote
hero light center. One quote, one attribution. Italic-feel via the serif display, not actual <em>.
<section class="slide hero light center" data-screen-label="07 Quote">
<div class="quote-mark">"</div>
<p class="quote-text">Filebase pays for itself in the first month. We were going to hire a dedicated DevOps person — instead we just switched.</p>
<p class="quote-author">— Mira Hassan, CTO at Northwind Studios</p>
</section>Layout 7 — Before / after (comparison)
Two columns, same shape, contrasting state. Don't decorate the columns — the contrast comes from copy and from picking one column to tint with the accent.
<section class="slide light" data-screen-label="08 Before / after">
<p class="eyebrow">The shift</p>
<h2 class="h-md">From whole-file sync to chunk-level sync.</h2>
<div class="ba-grid">
<div class="ba-col">
<p class="ba-label">Before · 2022</p>
<h3>Edit one frame, ship the whole 4 GB project.</h3>
<p>$1,800 / month bandwidth bill on a single Final Cut workflow. Editors waiting 12 minutes per save.</p>
</div>
<div class="ba-col">
<p class="ba-label" style="color: var(--accent);">After · 2026</p>
<h3>Edit one frame, ship 240 KB.</h3>
<p>$200 / month on the same workflow. Save-to-remote completes inside the editor's auto-save window.</p>
</div>
</div>
</section>Layout 8 — Closing / CTA
hero dark center or hero light center. One sentence on the ask, one supporting line. The audience leaves remembering this.
<section class="slide hero dark center" data-screen-label="09 Ask">
<div class="eyebrow">Ask</div>
<h2 class="h-hero">$22M to ship the next sync engine.</h2>
<p class="lead">18-month runway, hire 14, expand to enterprise on-prem.</p>
</section>---
Default arcs
6-slide pitch (the minimum): 1. hero light center — Cover (Layout 1) 2. light — Problem body (Layout 2) 3. hero dark center — Big stat (Layout 3) 4. light — Three points (Layout 4) 5. hero light center— Quote (Layout 6) 6. hero dark center — Ask (Layout 8)
10-slide narrative: 1. hero light center — Cover 2. light — Problem 3. hero dark center — Big stat 1 4. light — Three points 5. dark — Pipeline (Layout 5) 6. hero light center— Quote 7. light — Before / after (Layout 7) 8. hero dark center — Big stat 2 9. light — Team / metrics 10. hero dark center— Ask
After laying out, mentally read the class list — light dark light dark should show alternation, not blocks of the same theme.
Related skills
FAQ
How does simple-deck build an HTML deck?
simple-deck copies assets/template.html to index.html, maps six :root variables from DESIGN.md, and inserts slides from eight layouts in references/layouts.md. The bundled 5-rule iframe navigation script must not be rewritten because it fixes scroller detection and position persi
When should developers choose simple-deck over other Open Design decks?
Developers should choose simple-deck when they need a plain, brand-aligned deck bound to the project DESIGN.md tokens. Pick magazine-web-ppt or replit-deck only when the brief explicitly requires those distinct visual systems instead of the host design system.