first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: simple-deck
|
||||
description: |
|
||||
Single-file horizontal-swipe HTML deck. Built by copying the seed
|
||||
`assets/template.html` (which carries the proven 5-rule iframe nav script)
|
||||
and pasting slide layouts from `references/layouts.md`. Pitch decks,
|
||||
product overviews, study material — when you don't need the magazine
|
||||
aesthetic of `magazine-web-ppt`.
|
||||
triggers:
|
||||
- "deck"
|
||||
- "slides"
|
||||
- "ppt"
|
||||
- "presentation"
|
||||
- "幻灯"
|
||||
- "ppt 模板"
|
||||
od:
|
||||
mode: deck
|
||||
scenario: product
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: true
|
||||
sections: [color, typography, layout, components]
|
||||
---
|
||||
|
||||
# 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 Ask
|
||||
```
|
||||
|
||||
A healthy sequence has:
|
||||
- No 3+ same theme in a row
|
||||
- ≥ 1 `hero dark` AND ≥ 1 `hero 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:
|
||||
|
||||
```bash
|
||||
grep 'class="slide' index.html
|
||||
```
|
||||
|
||||
Read 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`). Bare `class="slide"` = regression.
|
||||
- **No 3+ same theme in a row.**
|
||||
- **Display = serif via `var(--font-display)`.** `.h-hero` / `.h-xl` / `.h-md` already 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.**
|
||||
@@ -0,0 +1,353 @@
|
||||
<!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 scroller() {
|
||||
if (document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
||||
return document.scrollingElement || document.documentElement;
|
||||
}
|
||||
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);
|
||||
scroller().scrollTo({ left: next * window.innerWidth, behavior: 'smooth' });
|
||||
}
|
||||
function syncFromScroll() {
|
||||
var i = Math.round(scroller().scrollLeft / window.innerWidth);
|
||||
if (i !== active && i >= 0 && i < slides.length) setActive(i);
|
||||
}
|
||||
function onKey(e) {
|
||||
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);
|
||||
scroller().scrollTo({ left: saved * window.innerWidth, behavior: 'instant' });
|
||||
} else {
|
||||
setActive(0);
|
||||
}
|
||||
} catch (_) { setActive(0); }
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,141 @@
|
||||
<!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 scroller() {
|
||||
if (document.body.scrollWidth > document.body.clientWidth + 1) return document.body;
|
||||
return document.scrollingElement || document.documentElement;
|
||||
}
|
||||
|
||||
function go(i) {
|
||||
const next = Math.max(0, Math.min(slides.length - 1, i));
|
||||
active = next;
|
||||
counter.textContent = (next + 1) + ' / ' + slides.length;
|
||||
scroller().scrollTo({ left: next * window.innerWidth, behavior: 'smooth' });
|
||||
}
|
||||
function syncFromScroll() {
|
||||
const i = Math.round(scroller().scrollLeft / window.innerWidth);
|
||||
if (i !== active && i >= 0 && i < slides.length) {
|
||||
active = i;
|
||||
counter.textContent = (i + 1) + ' / ' + slides.length;
|
||||
}
|
||||
}
|
||||
function onKey(e) {
|
||||
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>
|
||||
@@ -0,0 +1,55 @@
|
||||
# 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 bare `class="slide"`. No bare `class="slide hero"`.
|
||||
- [ ] **No 3+ same-theme slides in a row.** Mentally list the classes from slide 1 to N — if you see `light light light` anywhere, 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-md` and `.quote-text` all 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()` with `document.body`. Don't drop one of the dual capture-phase listeners. Don't use `scrollIntoView()`. (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: 56ch` enforces 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-num` already does; `.meta` does).
|
||||
- [ ] **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 `localStorage` save/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.html
|
||||
```
|
||||
|
||||
Read the class list as a single sequence. The healthy patterns look like:
|
||||
|
||||
- `hero light` `light` `hero dark` `light` `dark` `hero light` `light` `hero dark`
|
||||
- `hero light` `light` `light` `dark` `hero light` `dark` `hero dark`
|
||||
|
||||
Bad patterns:
|
||||
|
||||
- `light light light light light light` — flat
|
||||
- `dark dark dark dark dark dark` — heavy
|
||||
- `hero hero hero hero` — no rest
|
||||
|
||||
If your sequence is bad, swap a few middle slides to rebalance.
|
||||
@@ -0,0 +1,201 @@
|
||||
# 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 surface
|
||||
- `dark` — inverted, fg-on-bg
|
||||
- `hero 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 dark` AND at least one `hero light`.
|
||||
- A `dark` slide 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 often `hero dark` or `hero light`.
|
||||
|
||||
Before emitting, run mentally: list every slide's class. If you see `light × 5 in a row`, change one to `dark`.
|
||||
|
||||
## Class inventory
|
||||
|
||||
> `slide` `light` `dark` `hero` `center` `eyebrow` `h-hero` `h-xl` `h-md` `lead` `meta` `stat-num` `unit` `stat-caption` `quote-mark` `quote-text` `quote-author` `pt-grid` `pt` `pipeline` `step` `nb` `ba-grid` `ba-col` `ba-label` `ph-img` `wide` `tall`
|
||||
|
||||
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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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>`.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
|
||||
```html
|
||||
<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.
|
||||
Reference in New Issue
Block a user