README.zh-CN.md
# html-ppt · HTML PPT 工作室
> 一款专业级的 AgentSkill,让 AI 做出真正能打的 HTML 演示文稿。
> **36 套主题**、**15 套完整 deck 模板**、**31 种页面布局**、**47 个动效**
> (27 个 CSS + 20 个 Canvas FX),加上全新的 **演讲者模式** —— 像素级
> 完美预览 + 逐字稿提词器 + 计时器。纯静态 HTML/CSS/JS,无需构建。
**作者:** lewis <sudolewis@gmail.com>
**协议:** MIT
**English docs:** [README.md](README.md)

> 一行命令装好 **36 主题 × 20 Canvas FX × 31 布局 × 15 完整 deck + 演讲者模式**。
> 上图里的每一个预览都是真实的 iframe 加载真实模板文件 —— 不是截图,不是色卡。
## 🎤 演讲者模式(全新)
在任何 deck 里按 `S` 键,弹出一个独立的演讲者窗口,包含 4 个**可拖拽、
可调整大小的磁吸卡片**:当前页预览、下一页预览、逐字稿、计时器。两个窗口
通过 `BroadcastChannel` 双向同步翻页。

**为什么预览是像素级完美的:** 每个卡片是一个 `<iframe>`,加载的是**同一
份 deck HTML 文件**,只是 URL 多了 `?preview=N` 参数。runtime 检测到这个
参数后,只渲染第 N 页并隐藏所有 chrome —— 所以预览使用**和观众视图完全相
同的 CSS、主题、字体、viewport**,颜色和排版保证 100% 一致。
**丝滑翻页(零闪烁):** 翻页时演讲者窗口通过 `postMessage({type:'preview-goto',
idx:N})` 通知 iframe,iframe 只是切换 `.is-active` class —— **不重新加载、
不白屏、不闪烁**。
**逐字稿 3 条铁律:**
1. **提示信号,不是讲稿** — 关键词加粗,过渡句独立成段
2. **每页 150–300 字** — 约 2–3 分钟/页的节奏
3. **用口语,不用书面语** — "所以" 不是 "因此","这个" 不是 "该"
详见 [`references/presenter-mode.md`](references/presenter-mode.md),或直接复制
`templates/full-decks/presenter-mode-reveal/` 这个现成模板 —— 每一页都带完整
150–300 字的示例逐字稿。
## 一行命令安装
```bash
npx skills add https://github.com/lewislulu/html-ppt-skill
```
装好后,任何支持 AgentSkill 的 agent(Claude Code / Codex / Cursor / OpenClaw 等)
都能用这套能力做 PPT。对 agent 说:
> "做一份 8 页的技术分享 slides,用 cyberpunk 主题"
> "把这段 outline 变成投资人 pitch deck"
> "做一个小红书图文,9 张,白底柔和风"
> "做一份带演讲者模式的产品分享,我想要有逐字稿"
## Skill 内容一览
| | 数量 | 位置 |
|---|---|---|
| 🎤 **演讲者模式** | **新增** | `S` 键 / `?preview=N` |
| 🎨 **主题** | **36** | `assets/themes/*.css` |
| 📑 **完整 deck 模板** | **15** | `templates/full-decks/<name>/` |
| 🧩 **单页布局** | **31** | `templates/single-page/*.html` |
| ✨ **CSS 动画** | **27** | `assets/animations/animations.css` |
| 💥 **Canvas FX 动画** | **20** | `assets/animations/fx/*.js` |
| 🖼️ **Showcase deck** | 4 | `templates/*-showcase.html` |
| 📸 **验证截图** | 56 | `scripts/verify-output/` |
### 36 套主题
`minimal-white`、`editorial-serif`、`soft-pastel`、`sharp-mono`、`arctic-cool`、
`sunset-warm`、`catppuccin-latte`、`catppuccin-mocha`、`dracula`、`tokyo-night`、
`nord`、`solarized-light`、`gruvbox-dark`、`rose-pine`、`neo-brutalism`、
`glassmorphism`、`bauhaus`、`swiss-grid`、`terminal-green`、`xiaohongshu-white`、
`rainbow-gradient`、`aurora`、`blueprint`、`memphis-pop`、`cyberpunk-neon`、
`y2k-chrome`、`retro-tv`、`japanese-minimal`、`vaporwave`、`midcentury`、
`corporate-clean`、`academic-paper`、`news-broadcast`、`pitch-deck-vc`、
`magazine-bold`、`engineering-whiteprint`

每个主题都是一份纯 CSS token 文件 —— 只需要换一行 `<link>` 就能给整份 deck
换皮。在 `templates/theme-showcase.html` 里可以浏览全部(每一页用独立 iframe
渲染,避免样式互相污染)。

### 15 套完整 deck 模板
8 个从真实作品提炼的视觉语言,7 个通用场景脚手架:
**提炼款**
- `xhs-white-editorial` — 小红书白底杂志风
- `graphify-dark-graph` — 暗底 + 力导向知识图谱
- `knowledge-arch-blueprint` — 蓝图 / 架构图风
- `hermes-cyber-terminal` — 终端 cyberpunk 风
- `obsidian-claude-gradient` — 紫色渐变卡
- `testing-safety-alert` — 红 / 琥珀警示风
- `xhs-pastel-card` — 柔和马卡龙图文
- `dir-key-nav-minimal` — 方向键极简
**场景款**
- `pitch-deck` — 投资人 pitch
- `product-launch` — 产品发布会
- `tech-sharing` — 技术分享
- `weekly-report` — 周报
- `xhs-post` — 小红书图文(9 页 3:4)
- `course-module` — 教学模块
- **`presenter-mode-reveal`** 🎤 — 完整分享模板,**每一页都带 150-300 字
的示例逐字稿**,围绕 `S` 键演讲者模式专门设计
每个模板都是自包含的文件夹,用 scoped `.tpl-<name>` CSS,所以多个模板可以
同时加载不会互相污染。在 `templates/full-decks-index.html` 可以看全套 gallery。

### 31 种单页布局
cover · toc · section-divider · bullets · two-column · three-column ·
big-quote · stat-highlight · kpi-grid · table · code · diff · terminal ·
flow-diagram · timeline · roadmap · mindmap · comparison · pros-cons ·
todo-checklist · gantt · image-hero · image-grid · chart-bar · chart-line ·
chart-pie · chart-radar · arch-diagram · process-steps · cta · thanks
每个布局都带真实的示例数据,拖进 deck 立即看得到效果。

*大 iframe 直接加载 `templates/single-page/<name>.html` 文件,每 2.8 秒
自动切换到下一个布局。*

### 27 个 CSS 动画 + 20 个 Canvas FX
**CSS 动画(轻量)** — 方向性淡入、`rise-in`、`zoom-pop`、`blur-in`、
`glitch-in`、`typewriter`(打字机)、`neon-glow`(霓虹光晕)、
`shimmer-sweep`(流光)、`gradient-flow`(渐变流动)、`stagger-list`
(列表错开入场)、`counter-up`(数字滚动)、`path-draw`(路径绘制)、
`morph-shape`、`parallax-tilt`、`card-flip-3d`、`cube-rotate-3d`、
`page-turn-3d`、`perspective-zoom`、`marquee-scroll`、`kenburns`、
`ripple-reveal`、`spotlight`、…
**Canvas FX(电影级)** — `particle-burst`(粒子爆发)、`confetti-cannon`
(彩带)、`firework`(烟花)、`starfield`(星空)、`matrix-rain`
(代码雨)、`knowledge-graph`(力导向知识图谱)、`neural-net`(神经网络
脉冲)、`constellation`(星座连线)、`orbit-ring`(轨道环)、
`galaxy-swirl`(星系漩涡)、`word-cascade`、`letter-explode`、
`chain-react`、`magnetic-field`、`data-stream`、`gradient-blob`、
`sparkle-trail`、`shockwave`、`typewriter-multi`、`counter-explosion`。
每一个都是手写的 canvas 模块,进入 slide 时由 `fx-runtime.js` 自动初始化。
## 快速开始(手动 / 安装后 / git clone 后)
```bash
# 从 base 模板新建一个 deck
./scripts/new-deck.sh my-talk
# 浏览所有内容
open templates/theme-showcase.html # 全部 36 主题(iframe 隔离)
open templates/layout-showcase.html # 全部 31 布局
open templates/animation-showcase.html # 全部 47 动效
open templates/full-decks-index.html # 全部 15 个完整 deck
# 用 headless Chrome 导出 PNG
./scripts/render.sh templates/theme-showcase.html
./scripts/render.sh examples/my-talk/index.html 12
```
## 键盘快捷键
```
← → Space PgUp PgDn Home End 翻页
F 全屏
S 打开演讲者窗口(磁吸卡片模式)
N 底部 notes 抽屉
R 重置计时器(演讲者窗口内)
O slide 总览网格
T 切换主题(自动同步到演讲者窗口)
A 在当前 slide 循环演示一个动画
#/N (URL) 深链到第 N 页
?preview=N (URL) 预览模式(只显示单页,隐藏 chrome)
```
## 项目结构
```
html-ppt-skill/
├── SKILL.md agent 入口
├── README.md 英文 README
├── README.zh-CN.md 本文件
├── references/ 详细文档
│ ├── themes.md 36 主题 + 使用场景
│ ├── layouts.md 31 布局
│ ├── animations.md 27 CSS + 20 FX 目录
│ ├── full-decks.md 15 完整 deck 模板
│ ├── presenter-mode.md 🎤 演讲者模式 + 逐字稿指南
│ └── authoring-guide.md 完整工作流
├── assets/
│ ├── base.css 共享 tokens + 基础组件
│ ├── fonts.css web 字体引入
│ ├── runtime.js 键盘导航 + 演讲者模式 + 总览
│ ├── themes/*.css 36 主题 token 文件
│ └── animations/
│ ├── animations.css 27 个命名 CSS 动画
│ ├── fx-runtime.js 进入 slide 自动初始化 [data-fx]
│ └── fx/*.js 20 个 Canvas FX 模块
├── templates/
│ ├── deck.html 最小起步模板
│ ├── theme-showcase.html iframe 隔离的主题 tour
│ ├── layout-showcase.html 全部 31 布局
│ ├── animation-showcase.html 47 动画 slide
│ ├── full-decks-index.html 15 deck gallery
│ ├── full-decks/<name>/ 15 个 scoped 多页 deck 模板
│ └── single-page/*.html 31 个布局文件(带示例数据)
├── scripts/
│ ├── new-deck.sh 脚手架
│ ├── render.sh headless Chrome → PNG
│ └── verify-output/ 56 张自测截图
└── examples/demo-deck/ 完整可运行的示例 deck
```
## 设计理念
- **Token 驱动的设计系统。** 所有颜色、圆角、阴影、字体决策都在
`assets/base.css` + 当前主题文件里。改一个变量,整份 deck 优雅地重排。
- **Iframe 隔离预览。** 主题 / 布局 / 完整 deck 的 showcase 都用 `<iframe>`,
确保每个预览都是真实、独立的渲染结果。
- **零构建。** 纯静态 HTML/CSS/JS。只有 webfont / highlight.js / chart.js
(可选) 走 CDN。
- **资深设计师的默认值。** 字号规律、间距节奏、渐变、卡片处理都有态度 ——
绝不是 "PowerPoint 2006" 那种味道。
- **中英双语一等公民。** 预导入了 Noto Sans SC / Noto Serif SC。
## 协议
MIT © 2026 lewis <sudolewis@gmail.com>
README.md
# html-ppt — HTML PPT Studio
> A world-class AgentSkill for producing professional HTML presentations in
> **36 themes**, **15 full-deck templates**, **31 page layouts**,
> **47 animations** (27 CSS + 20 canvas FX), and a **true presenter mode**
> with pixel-perfect previews + speaker script + timer — all pure static
> HTML/CSS/JS, no build step.
**Author:** lewis <sudolewis@gmail.com>
**License:** MIT
**中文文档:** [README.zh-CN.md](README.zh-CN.md)

> One command installs **36 themes × 20 canvas FX × 31 layouts × 15 full decks + presenter mode**. Every preview above is a live iframe of a real template file rendering inside the deck — no screenshots, no mock-ups.
## 🎤 Presenter Mode (new!)
Press `S` on any deck to pop open a dedicated presenter window with four
draggable, resizable **magnetic cards**: current slide, next slide preview,
speaker script (逐字稿), and timer. Two windows stay in sync via
`BroadcastChannel`.

**Why previews are pixel-perfect:** each card is an `<iframe>` that loads the
same deck HTML with a `?preview=N` query param. The runtime detects this and
renders only slide N with no chrome — so the preview uses the **same CSS,
theme, fonts and viewport** as the audience view. Colors and layout are
guaranteed identical.
**Smooth (no-reload) navigation:** on slide change, the presenter window
sends `postMessage({type:'preview-goto', idx:N})` to each iframe. The iframe
just toggles `.is-active` between slides — **no reload, no flicker**.
**Speaker script rules (3 golden):**
1. **Prompt signals, not lines to read** — bold the keywords, separate
transition sentences into their own paragraphs
2. **150–300 words per slide** — that's the ~2–3 min/page pace
3. **Write it like you speak** — conversational, not written prose
See [`references/presenter-mode.md`](references/presenter-mode.md) for the
full authoring guide, or copy the ready-made template at
`templates/full-decks/presenter-mode-reveal/` which ships with full 150-300
word speaker scripts on every slide.
## Install (one command)
```bash
npx skills add https://github.com/lewislulu/html-ppt-skill
```
That registers the skill with your agent runtime. After install, any agent
that supports AgentSkills can author presentations by asking things like:
> "做一份 8 页的技术分享 slides,用 cyberpunk 主题"
> "turn this outline into a pitch deck"
> "做一个小红书图文,9 张,白底柔和风"
## What's in the box
| | Count | Where |
|---|---|---|
| 🎤 **Presenter mode** | **NEW** | `S` key / `?preview=N` |
| 🎨 **Themes** | **36** | `assets/themes/*.css` |
| 📑 **Full-deck templates** | **15** | `templates/full-decks/<name>/` |
| 🧩 **Single-page layouts** | **31** | `templates/single-page/*.html` |
| ✨ **CSS animations** | **27** | `assets/animations/animations.css` |
| 💥 **Canvas FX animations** | **20** | `assets/animations/fx/*.js` |
| 🖼️ **Showcase decks** | 4 | `templates/*-showcase.html` |
| 📸 **Verification screenshots** | 56 | `scripts/verify-output/` |
### 36 Themes
`minimal-white`, `editorial-serif`, `soft-pastel`, `sharp-mono`, `arctic-cool`,
`sunset-warm`, `catppuccin-latte`, `catppuccin-mocha`, `dracula`, `tokyo-night`,
`nord`, `solarized-light`, `gruvbox-dark`, `rose-pine`, `neo-brutalism`,
`glassmorphism`, `bauhaus`, `swiss-grid`, `terminal-green`, `xiaohongshu-white`,
`rainbow-gradient`, `aurora`, `blueprint`, `memphis-pop`, `cyberpunk-neon`,
`y2k-chrome`, `retro-tv`, `japanese-minimal`, `vaporwave`, `midcentury`,
`corporate-clean`, `academic-paper`, `news-broadcast`, `pitch-deck-vc`,
`magazine-bold`, `engineering-whiteprint`.

Each is a pure CSS-tokens file — swap one `<link>` to reskin the entire deck.
Browse them all in `templates/theme-showcase.html` (each slide rendered in an
isolated iframe so theme ≠ theme is visually guaranteed).

### 15 Full-deck templates
Eight extracted from real-world decks, seven generic scenario scaffolds:
**Extracted looks**
- `xhs-white-editorial` — 小红书白底杂志风
- `graphify-dark-graph` — 暗底 + 力导向知识图谱
- `knowledge-arch-blueprint` — 蓝图 / 架构图风
- `hermes-cyber-terminal` — 终端 cyberpunk
- `obsidian-claude-gradient` — 紫色渐变卡
- `testing-safety-alert` — 红 / 琥珀警示风
- `xhs-pastel-card` — 柔和马卡龙图文
- `dir-key-nav-minimal` — 方向键极简
**Scenario decks**
- `pitch-deck`, `product-launch`, `tech-sharing`, `weekly-report`,
`xhs-post` (9-slide 3:4), `course-module`,
**`presenter-mode-reveal`** 🎤 — complete talk template with full 150-300
word speaker scripts on every slide, designed around the `S` key presenter mode
Each is a self-contained folder with scoped `.tpl-<name>` CSS so multiple
decks can be previewed side-by-side without collisions. Browse the full
gallery in `templates/full-decks-index.html`.

### 31 Single-page layouts
cover · toc · section-divider · bullets · two-column · three-column ·
big-quote · stat-highlight · kpi-grid · table · code · diff · terminal ·
flow-diagram · timeline · roadmap · mindmap · comparison · pros-cons ·
todo-checklist · gantt · image-hero · image-grid · chart-bar · chart-line ·
chart-pie · chart-radar · arch-diagram · process-steps · cta · thanks
Every layout ships with realistic demo data so you can drop it into a deck
and immediately see it render.

*The big iframe is loading `templates/single-page/<name>.html` directly and cycling through all 31 layouts every 2.8 seconds.*

### 27 CSS animations + 20 Canvas FX
**CSS (lightweight)** — directional fades, `rise-in`, `zoom-pop`, `blur-in`,
`glitch-in`, `typewriter`, `neon-glow`, `shimmer-sweep`, `gradient-flow`,
`stagger-list`, `counter-up`, `path-draw`, `morph-shape`, `parallax-tilt`,
`card-flip-3d`, `cube-rotate-3d`, `page-turn-3d`, `perspective-zoom`,
`marquee-scroll`, `kenburns`, `ripple-reveal`, `spotlight`, …
**Canvas FX (cinematic)** — `particle-burst`, `confetti-cannon`, `firework`,
`starfield`, `matrix-rain`, `knowledge-graph` (force-directed physics),
`neural-net` (signal pulses), `constellation`, `orbit-ring`, `galaxy-swirl`,
`word-cascade`, `letter-explode`, `chain-react`, `magnetic-field`,
`data-stream`, `gradient-blob`, `sparkle-trail`, `shockwave`,
`typewriter-multi`, `counter-explosion`. Each is a real hand-rolled canvas
module auto-initialised on slide enter via `fx-runtime.js`.
## Quick start (manual, after install or git clone)
```bash
# Scaffold a new deck from the base template
./scripts/new-deck.sh my-talk
# Browse everything
open templates/theme-showcase.html # all 36 themes (iframe-isolated)
open templates/layout-showcase.html # all 31 layouts
open templates/animation-showcase.html # all 47 animations
open templates/full-decks-index.html # all 14 full decks
# Render any template to PNG via headless Chrome
./scripts/render.sh templates/theme-showcase.html
./scripts/render.sh examples/my-talk/index.html 12
```
## Keyboard cheat sheet
```
← → Space PgUp PgDn Home End navigate
F fullscreen
S open presenter window (magnetic cards)
N quick notes drawer (bottom)
R reset timer (in presenter window)
O slide overview grid
T cycle themes (syncs to presenter)
A cycle a demo animation on current slide
#/N (URL) deep-link to slide N
?preview=N (URL) preview-only mode (single slide, no chrome)
```
## Project structure
```
html-ppt-skill/
├── SKILL.md agent-facing dispatcher
├── README.md this file
├── references/ detailed catalogs
│ ├── themes.md 36 themes with when-to-use
│ ├── layouts.md 31 layout types
│ ├── animations.md 27 CSS + 20 FX catalog
│ ├── full-decks.md 14 full-deck templates
│ └── authoring-guide.md full workflow
├── assets/
│ ├── base.css shared tokens + primitives
│ ├── fonts.css webfont imports
│ ├── runtime.js keyboard + presenter + overview
│ ├── themes/*.css 36 theme token files
│ └── animations/
│ ├── animations.css 27 named CSS animations
│ ├── fx-runtime.js auto-init [data-fx] on slide enter
│ └── fx/*.js 20 canvas FX modules
├── templates/
│ ├── deck.html minimal starter
│ ├── theme-showcase.html iframe-isolated theme tour
│ ├── layout-showcase.html all 31 layouts
│ ├── animation-showcase.html 47 animation slides
│ ├── full-decks-index.html 14-deck gallery
│ ├── full-decks/<name>/ 14 scoped multi-slide decks
│ └── single-page/*.html 31 layout files with demo data
├── scripts/
│ ├── new-deck.sh scaffold
│ ├── render.sh headless Chrome → PNG
│ └── verify-output/ 56 self-test screenshots
└── examples/demo-deck/ complete working deck
```
## Philosophy
- **Token-driven design system.** All color, radius, shadow, font decisions
live in `assets/base.css` + the current theme file. Change one variable,
the whole deck reflows tastefully.
- **Iframe isolation for previews.** Theme / layout / full-deck showcases all
use `<iframe>` per slide so each preview is a real, independent render.
- **Zero build.** Pure static HTML/CSS/JS. CDN only for webfonts, highlight.js
and chart.js (optional).
- **Senior-designer defaults.** Opinionated type scale, spacing rhythm,
gradients and card treatments — no "Corporate PowerPoint 2006" vibes.
- **Chinese + English first-class.** Noto Sans SC / Noto Serif SC pre-imported.
## License
MIT © 2026 lewis <sudolewis@gmail.com>.
assets/animations/fx/chain-react.js
(function(){
window.HPX = window.HPX || {};
window.HPX['chain-react'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
const N = 8;
const stop = U.loop((t) => {
ctx.clearRect(0,0,k.w,k.h);
const cy = k.h/2;
const pad = 60;
const dx = (k.w - pad*2)/(N-1);
const period = 2.4;
const phase = (t % period) / period; // 0..1
for (let i=0;i<N;i++){
const x = pad + i*dx;
const my = i/(N-1);
const d = Math.abs(phase - my);
const pulse = Math.max(0, 1 - d*6);
const r = 18 + pulse*18;
// glow
const g = ctx.createRadialGradient(x,cy,0,x,cy,r*2);
g.addColorStop(0, `rgba(124,92,255,${0.4*pulse})`);
g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = g;
ctx.fillRect(x-r*2, cy-r*2, r*4, r*4);
// circle
ctx.fillStyle = pulse>0.1 ? ac2 : ac;
ctx.beginPath(); ctx.arc(x,cy,r,0,Math.PI*2); ctx.fill();
ctx.strokeStyle='rgba(255,255,255,0.4)'; ctx.lineWidth=2;
ctx.stroke();
// connectors
if (i<N-1){
ctx.strokeStyle='rgba(200,200,230,0.3)'; ctx.lineWidth=2;
ctx.beginPath(); ctx.moveTo(x+r,cy); ctx.lineTo(x+dx-r,cy); ctx.stroke();
}
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/_util.js
/* html-ppt fx :: shared helpers */
(function(){
window.HPX = window.HPX || {};
const U = window.HPX._u = {};
U.css = (el, name, fb) => {
const v = getComputedStyle(el).getPropertyValue(name).trim();
return v || fb;
};
U.accent = (el, fb) => U.css(el, '--accent', fb || '#7c5cff');
U.accent2 = (el, fb) => U.css(el, '--accent-2', fb || '#22d3ee');
U.text = (el, fb) => U.css(el, '--text-1', fb || '#eaeaf2');
U.palette = (el) => [
U.accent(el, '#7c5cff'),
U.accent2(el, '#22d3ee'),
U.css(el, '--ok', '#22c55e'),
U.css(el, '--warn', '#f59e0b'),
U.css(el, '--danger', '#ef4444'),
];
U.canvas = (el) => {
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
const c = document.createElement('canvas');
c.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;display:block;';
el.appendChild(c);
const ctx = c.getContext('2d');
let w = 0, h = 0, dpr = Math.max(1, Math.min(2, window.devicePixelRatio||1));
const fit = () => {
const r = el.getBoundingClientRect();
w = Math.max(1, r.width|0);
h = Math.max(1, r.height|0);
c.width = (w*dpr)|0;
c.height = (h*dpr)|0;
ctx.setTransform(dpr,0,0,dpr,0,0);
};
fit();
const ro = new ResizeObserver(fit);
ro.observe(el);
return {
c, ctx,
get w(){return w;}, get h(){return h;}, get dpr(){return dpr;},
destroy(){
try{ro.disconnect();}catch(e){}
if (c.parentNode) c.parentNode.removeChild(c);
}
};
};
U.loop = (fn) => {
let raf = 0, stopped = false, t0 = performance.now();
const tick = (t) => {
if (stopped) return;
fn((t - t0)/1000);
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);
return () => { stopped = true; cancelAnimationFrame(raf); };
};
U.rand = (a,b) => a + Math.random()*(b-a);
})();
assets/animations/fx/constellation.js
(function(){
window.HPX = window.HPX || {};
window.HPX['constellation'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const ac = U.accent(el,'#9fb4ff');
const N = 70;
let pts = [];
const seed = () => {
pts = Array.from({length:N}, () => ({
x: Math.random()*k.w, y: Math.random()*k.h,
vx: U.rand(-0.3,0.3), vy: U.rand(-0.3,0.3)
}));
};
seed();
let lw=k.w, lh=k.h;
const stop = U.loop(() => {
if (k.w!==lw||k.h!==lh){ seed(); lw=k.w; lh=k.h; }
ctx.clearRect(0,0,k.w,k.h);
for (const p of pts){
p.x += p.vx; p.y += p.vy;
if (p.x<0||p.x>k.w) p.vx*=-1;
if (p.y<0||p.y>k.h) p.vy*=-1;
}
for (let i=0;i<N;i++){
for (let j=i+1;j<N;j++){
const a=pts[i], b=pts[j];
const d = Math.hypot(a.x-b.x, a.y-b.y);
if (d < 150){
ctx.globalAlpha = 1 - d/150;
ctx.strokeStyle = ac; ctx.lineWidth=1;
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
}
}
}
ctx.globalAlpha = 1;
ctx.fillStyle = ac;
for (const p of pts){
ctx.beginPath(); ctx.arc(p.x,p.y,1.8,0,Math.PI*2); ctx.fill();
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/data-stream.js
(function(){
window.HPX = window.HPX || {};
window.HPX['data-stream'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const ac = U.accent(el,'#22d3ee'), ac2 = U.accent2(el,'#7c5cff');
const rows = [];
const rh = 22;
const genRow = (y) => ({
y, dir: Math.random()<0.5?-1:1,
speed: U.rand(30, 90),
offset: Math.random()*2000,
text: Array.from({length:120}, () => {
const r = Math.random();
if (r<0.3) return Math.random()<0.5?'0':'1';
if (r<0.6) return '0x' + Math.floor(Math.random()*256).toString(16).padStart(2,'0');
return Math.random().toString(16).slice(2,6);
}).join(' ')
});
const init = () => {
rows.length = 0;
const n = Math.ceil(k.h/rh);
for (let i=0;i<n;i++) rows.push(genRow(i*rh + rh*0.7));
};
init();
let lh = k.h;
const stop = U.loop((t) => {
if (k.h!==lh){ init(); lh=k.h; }
ctx.fillStyle = 'rgba(5,8,14,0.35)';
ctx.fillRect(0,0,k.w,k.h);
ctx.font = '13px ui-monospace,Menlo,monospace';
for (let i=0;i<rows.length;i++){
const r = rows[i];
const x = r.dir>0
? ((t*r.speed + r.offset) % (k.w+400)) - 400
: k.w - (((t*r.speed + r.offset) % (k.w+400)) - 400);
ctx.fillStyle = (i%3===0)?ac:ac2;
ctx.globalAlpha = 0.65 + (i%2)*0.3;
ctx.fillText(r.text, x, r.y);
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); } };
};
})();
.clawscan-allow
# Security scan allowlist for html-ppt-skill
# These patterns are false positives from template content, not actual threats.
# Path traversal: templates reference shared assets via relative paths
# e.g. templates/full-decks/weekly-report/ → ../../../assets/
# This is the correct relative path to the skill root assets directory.
traversal:templates/full-decks/*/index.html
# Destructive commands: testing-safety-alert template displays forbidden
# commands as text examples in a security policy demo slide.
# They are HTML content, not executable code.
destructive:templates/full-decks/testing-safety-alert/index.html
LICENSE
MIT License
Copyright (c) 2026 lewis <sudolewis@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
assets/animations/fx-runtime.js
/* html-ppt :: fx-runtime.js
* Canvas FX autoloader + lifecycle manager.
* - Dynamically loads all fx modules listed in FX_LIST
* - Initializes [data-fx] elements when their slide becomes active
* - Calls handle.stop() when the slide leaves
*/
(function(){
'use strict';
const FX_LIST = [
'_util',
'particle-burst','confetti-cannon','firework','starfield','matrix-rain',
'knowledge-graph','neural-net','constellation','orbit-ring','galaxy-swirl',
'word-cascade','letter-explode','chain-react','magnetic-field','data-stream',
'gradient-blob','sparkle-trail','shockwave','typewriter-multi','counter-explosion'
];
// Resolve base path of this script so it works from any page location.
const myScript = document.currentScript || (function(){
const all = document.getElementsByTagName('script');
for (const s of all){ if (s.src && s.src.indexOf('fx-runtime.js')>-1) return s; }
return null;
})();
const base = myScript ? myScript.src.replace(/fx-runtime\.js.*$/, 'fx/') : 'assets/animations/fx/';
let loaded = 0;
const total = FX_LIST.length;
const ready = new Promise((resolve) => {
if (!total) return resolve();
FX_LIST.forEach((name) => {
const s = document.createElement('script');
s.src = base + name + '.js';
s.async = false;
s.onload = s.onerror = () => { if (++loaded >= total) resolve(); };
document.head.appendChild(s);
});
});
window.__hpxActive = window.__hpxActive || new Map();
function initFxIn(root){
if (!window.HPX) return;
const els = root.querySelectorAll('[data-fx]');
els.forEach((el) => {
if (window.__hpxActive.has(el)) return;
const name = el.getAttribute('data-fx');
const fn = window.HPX[name];
if (typeof fn !== 'function') return;
try {
const handle = fn(el, {}) || { stop(){} };
window.__hpxActive.set(el, handle);
} catch(e){ console.warn('[hpx-fx]', name, e); }
});
}
function stopFxIn(root){
const els = root.querySelectorAll('[data-fx]');
els.forEach((el) => {
const h = window.__hpxActive.get(el);
if (h && typeof h.stop === 'function'){
try{ h.stop(); }catch(e){}
}
window.__hpxActive.delete(el);
});
}
function reinitFxIn(root){
stopFxIn(root);
initFxIn(root);
}
window.__hpxReinit = reinitFxIn;
function boot(){
ready.then(() => {
const active = document.querySelector('.slide.is-active') || document.querySelector('.slide');
if (active) initFxIn(active);
// Watch all slides for class changes
const slides = document.querySelectorAll('.slide');
slides.forEach((sl) => {
const mo = new MutationObserver((muts) => {
for (const m of muts){
if (m.attributeName === 'class'){
if (sl.classList.contains('is-active')) initFxIn(sl);
else stopFxIn(sl);
}
}
});
mo.observe(sl, { attributes: true, attributeFilter: ['class'] });
});
});
}
if (document.readyState === 'loading'){
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
})();
assets/animations/animations.css
/* html-ppt :: animations.css
* Apply by adding class="anim-<name>" or data-anim="<name>".
* Durations are deliberately snappy; tweak --anim-dur per element.
*/
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
/* ---------- FADE DIRECTIONALS ---------- */
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
100%{opacity:1;transform:none}}
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
/* ---------- TYPEWRITER ---------- */
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
@keyframes kf-type{to{width:100%}}
@keyframes kf-caret{50%{border-color:transparent}}
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
.anim-shimmer-sweep{position:relative;overflow:hidden}
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
@keyframes kf-shimmer{to{transform:translateX(100%)}}
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
animation:kf-gradflow 4s linear infinite}
@keyframes kf-gradflow{to{background-position:300% 0}}
/* ---------- STAGGER LIST ---------- */
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
.counter{font-variant-numeric:tabular-nums}
/* ---------- SVG PATH DRAW ---------- */
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
@keyframes kf-draw{to{stroke-dashoffset:0}}
/* ---------- PARALLAX TILT (hover) ---------- */
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
/* ---------- CARD FLIP 3D ---------- */
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
to{transform:perspective(1200px) rotateY(0);opacity:1}}
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
/* ---------- CUBE ROTATE 3D ---------- */
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
/* ---------- PAGE TURN 3D ---------- */
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
to{transform:perspective(1600px) rotateY(0);opacity:1}}
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
/* ---------- PERSPECTIVE ZOOM ---------- */
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
to{opacity:1;transform:none}}
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
/* ---------- MARQUEE SCROLL ---------- */
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
/* ---------- KEN BURNS ---------- */
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
.anim-confetti-burst{position:relative}
.anim-confetti-burst::before,.anim-confetti-burst::after{
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
background:var(--accent);box-shadow:
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
/* ---------- SPOTLIGHT ---------- */
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
/* ---------- MORPH SHAPE (SVG) ---------- */
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
/* ---------- RIPPLE REVEAL ---------- */
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
/* reduced motion */
@media (prefers-reduced-motion: reduce){
[class*="anim-"]{animation:none!important;transition:none!important}
}
assets/animations/fx/knowledge-graph.js
(function(){
window.HPX = window.HPX || {};
window.HPX['knowledge-graph'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
const tx = U.text(el, '#e7e7ef');
const labels = ['AI','ML','LLM','Graph','Node','Edge','Claude','GPT','RAG','Vector',
'Embed','Neural','Agent','Tool','Memory','Logic','Data','Train','Infer','Token',
'Prompt','Chain','Plan','Skill','Cloud','Edge','GPU','Code','Task','Flow'];
const N = 28;
const nodes = Array.from({length:N}, (_,i) => ({
x: U.rand(40, 300), y: U.rand(40, 200),
vx: 0, vy: 0, label: labels[i%labels.length],
c: pal[i%pal.length]
}));
const edges = [];
const made = new Set();
while (edges.length < 50){
const a = (Math.random()*N)|0, b = (Math.random()*N)|0;
if (a===b) continue;
const key = a<b ? a+'-'+b : b+'-'+a;
if (made.has(key)) continue;
made.add(key); edges.push([a,b]);
}
const stop = U.loop(() => {
// physics
for (let i=0;i<N;i++){
for (let j=i+1;j<N;j++){
const a=nodes[i], b=nodes[j];
const dx=b.x-a.x, dy=b.y-a.y;
let d2=dx*dx+dy*dy; if (d2<1) d2=1;
const d=Math.sqrt(d2);
const f=1600/d2;
const fx=(dx/d)*f, fy=(dy/d)*f;
a.vx-=fx; a.vy-=fy; b.vx+=fx; b.vy+=fy;
}
}
for (const [i,j] of edges){
const a=nodes[i], b=nodes[j];
const dx=b.x-a.x, dy=b.y-a.y, d=Math.hypot(dx,dy)||1;
const f=(d-90)*0.008;
const fx=(dx/d)*f, fy=(dy/d)*f;
a.vx+=fx; a.vy+=fy; b.vx-=fx; b.vy-=fy;
}
const cx=k.w/2, cy=k.h/2;
for (const n of nodes){
n.vx += (cx-n.x)*0.002;
n.vy += (cy-n.y)*0.002;
n.vx *= 0.85; n.vy *= 0.85;
n.x += n.vx; n.y += n.vy;
}
ctx.clearRect(0,0,k.w,k.h);
ctx.strokeStyle = 'rgba(180,180,220,0.25)'; ctx.lineWidth=1;
for (const [i,j] of edges){
const a=nodes[i], b=nodes[j];
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
}
ctx.font='11px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
for (const n of nodes){
ctx.fillStyle = n.c;
ctx.beginPath(); ctx.arc(n.x,n.y,7,0,Math.PI*2); ctx.fill();
ctx.fillStyle = tx;
ctx.fillText(n.label, n.x, n.y-14);
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/confetti-cannon.js
(function(){
window.HPX = window.HPX || {};
window.HPX['confetti-cannon'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
let parts = [];
const fire = () => {
for (let side=0; side<2; side++){
const x0 = side===0 ? 20 : k.w-20;
const y0 = k.h - 20;
for (let i=0;i<40;i++){
const a = side===0 ? U.rand(-Math.PI*0.7, -Math.PI*0.4) : U.rand(-Math.PI*0.6, -Math.PI*0.3) - Math.PI/2 - Math.PI/6;
const spd = U.rand(300, 520);
parts.push({
x: x0, y: y0,
vx: Math.cos(a)*spd, vy: Math.sin(a)*spd,
w: U.rand(6,12), h: U.rand(3,7),
rot: Math.random()*Math.PI, vr: U.rand(-6,6),
c: pal[(Math.random()*pal.length)|0],
life: 1
});
}
}
};
fire();
let last = 0;
const stop = U.loop((t) => {
ctx.clearRect(0,0,k.w,k.h);
if (t - last > 3) { fire(); last = t; }
const dt = 1/60;
parts = parts.filter(p => p.life > 0 && p.y < k.h+40);
for (const p of parts){
p.vy += 520*dt;
p.x += p.vx*dt; p.y += p.vy*dt;
p.rot += p.vr*dt;
p.life -= 0.006;
ctx.save();
ctx.translate(p.x, p.y); ctx.rotate(p.rot);
ctx.globalAlpha = Math.max(0, p.life);
ctx.fillStyle = p.c;
ctx.fillRect(-p.w/2, -p.h/2, p.w, p.h);
ctx.restore();
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/letter-explode.js
(function(){
window.HPX = window.HPX || {};
window.HPX['letter-explode'] = function(el){
const U = window.HPX._u;
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
const src = el.querySelector('[data-fx-text]') || el;
const text = (el.getAttribute('data-fx-text-value') || src.textContent || 'EXPLODE').trim();
// Build a container, hide source text
const wrap = document.createElement('div');
wrap.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;';
const inner = document.createElement('div');
inner.style.cssText = 'font-size:64px;font-weight:900;letter-spacing:0.02em;color:var(--text-1,#fff);white-space:nowrap;';
wrap.appendChild(inner);
el.appendChild(wrap);
const spans = [];
for (const ch of text){
const s = document.createElement('span');
s.textContent = ch === ' ' ? '\u00A0' : ch;
s.style.display='inline-block';
s.style.transform='translate(0,0)';
s.style.transition='transform 900ms cubic-bezier(.2,.9,.3,1), opacity 900ms';
s.style.opacity='0';
inner.appendChild(s);
spans.push(s);
}
let stopped = false;
const run = () => {
if (stopped) return;
spans.forEach((s,i) => {
const dx = U.rand(-400, 400), dy = U.rand(-300, 300);
s.style.transition='none';
s.style.transform=`translate(${dx}px,${dy}px) rotate(${U.rand(-180,180)}deg)`;
s.style.opacity='0';
});
// force reflow
void inner.offsetWidth;
spans.forEach((s,i) => {
setTimeout(() => {
if (stopped) return;
s.style.transition='transform 900ms cubic-bezier(.2,.9,.3,1), opacity 900ms';
s.style.transform='translate(0,0) rotate(0deg)';
s.style.opacity='1';
}, i*35);
});
};
run();
const iv = setInterval(run, 4500);
return { stop(){ stopped=true; clearInterval(iv); if (wrap.parentNode) wrap.parentNode.removeChild(wrap); } };
};
})();
assets/animations/fx/counter-explosion.js
(function(){
window.HPX = window.HPX || {};
window.HPX['counter-explosion'] = function(el){
const U = window.HPX._u;
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
const target = parseInt(el.getAttribute('data-fx-to') || '2400', 10);
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
// number overlay
const num = document.createElement('div');
num.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:900 120px system-ui,sans-serif;color:var(--text-1,#fff);pointer-events:none;text-shadow:0 4px 40px rgba(124,92,255,0.5);';
num.textContent = '0';
el.appendChild(num);
let parts = [];
let state = 'count'; // count | burst | hold
let stateT = 0;
let value = 0;
let cycle = 0;
const burst = () => {
const cx = k.w/2, cy = k.h/2;
for (let i=0;i<120;i++){
const a = Math.random()*Math.PI*2;
const s = U.rand(120, 400);
parts.push({x:cx,y:cy,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,r:U.rand(2,5),c:pal[(Math.random()*pal.length)|0]});
}
};
const stop = U.loop(() => {
ctx.clearRect(0,0,k.w,k.h);
const dt = 1/60;
stateT += dt;
if (state === 'count'){
const dur = 2.2;
const p = Math.min(1, stateT/dur);
const eased = 1 - Math.pow(1-p,3);
value = Math.round(target*eased);
num.textContent = value.toLocaleString();
if (p >= 1){ state='burst'; stateT=0; burst(); }
} else if (state === 'burst'){
if (stateT > 0.05 && stateT < 0.3 && parts.length < 200) {}
if (stateT > 2.5){ state='hold'; stateT=0; }
} else if (state === 'hold'){
if (stateT > 1.5){
state='count'; stateT=0; value=0; num.textContent='0'; cycle++;
}
}
parts = parts.filter(p => p.life > 0);
for (const p of parts){
p.vy += 260*dt; p.vx *= 0.985; p.vy *= 0.985;
p.x += p.vx*dt; p.y += p.vy*dt; p.life -= 0.01;
ctx.globalAlpha = Math.max(0,p.life);
ctx.fillStyle = p.c;
ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); if (num.parentNode) num.parentNode.removeChild(num); } };
};
})();
assets/animations/fx/firework.js
(function(){
window.HPX = window.HPX || {};
window.HPX['firework'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
let rockets = [], sparks = [];
const launch = () => {
rockets.push({
x: U.rand(k.w*0.2, k.w*0.8), y: k.h+10,
vx: U.rand(-30,30), vy: U.rand(-520,-380),
tgtY: U.rand(k.h*0.15, k.h*0.45),
c: pal[(Math.random()*pal.length)|0]
});
};
const burst = (x, y, c) => {
const n = 70;
for (let i=0;i<n;i++){
const a = Math.random()*Math.PI*2;
const s = U.rand(60, 240);
sparks.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,c});
}
};
let last = -1;
const stop = U.loop((t) => {
ctx.fillStyle = 'rgba(0,0,0,0.18)';
ctx.fillRect(0,0,k.w,k.h);
if (t - last > 0.7) { launch(); last = t; }
const dt = 1/60;
rockets = rockets.filter(r => {
r.x += r.vx*dt; r.y += r.vy*dt; r.vy += 260*dt;
ctx.fillStyle = r.c;
ctx.beginPath(); ctx.arc(r.x, r.y, 2.5, 0, Math.PI*2); ctx.fill();
if (r.y <= r.tgtY || r.vy >= 0) { burst(r.x, r.y, r.c); return false; }
return true;
});
sparks = sparks.filter(p => p.life > 0);
for (const p of sparks){
p.vy += 90*dt;
p.vx *= 0.98; p.vy *= 0.98;
p.x += p.vx*dt; p.y += p.vy*dt;
p.life -= 0.012;
ctx.globalAlpha = Math.max(0, p.life);
ctx.fillStyle = p.c;
ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/matrix-rain.js
(function(){
window.HPX = window.HPX || {};
window.HPX['matrix-rain'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const glyphs = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ0123456789ABCDEF'.split('');
const fs = 16;
let cols = 0, drops = [];
const init = () => {
cols = Math.ceil(k.w/fs);
drops = Array.from({length:cols}, () => U.rand(-20, 0));
};
init();
let lw = k.w, lh = k.h;
const stop = U.loop(() => {
if (k.w!==lw || k.h!==lh){ init(); lw=k.w; lh=k.h; }
ctx.fillStyle = 'rgba(0,0,0,0.08)';
ctx.fillRect(0,0,k.w,k.h);
ctx.font = fs+'px monospace';
for (let i=0;i<cols;i++){
const ch = glyphs[(Math.random()*glyphs.length)|0];
const x = i*fs, y = drops[i]*fs;
ctx.fillStyle = '#9fffc9';
ctx.fillText(ch, x, y);
ctx.fillStyle = '#00ff6a';
ctx.fillText(ch, x, y - fs);
drops[i] += 1;
if (y > k.h && Math.random() > 0.975) drops[i] = 0;
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/galaxy-swirl.js
(function(){
window.HPX = window.HPX || {};
window.HPX['galaxy-swirl'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
const N = 800;
const parts = Array.from({length:N}, (_,i) => {
const arm = i%3;
const t = Math.random();
const r = t*180 + 8;
const base = (arm/3)*Math.PI*2;
return { r, a: base + Math.log(r+1)*1.6 + U.rand(-0.2,0.2),
c: pal[arm%pal.length],
s: U.rand(0.8, 2.2) };
});
const stop = U.loop((t) => {
ctx.fillStyle = 'rgba(0,0,0,0.15)';
ctx.fillRect(0,0,k.w,k.h);
const cx=k.w/2, cy=k.h/2;
for (const p of parts){
const a = p.a + t*0.15;
const x = cx + Math.cos(a)*p.r;
const y = cy + Math.sin(a)*p.r*0.7;
ctx.fillStyle = p.c;
ctx.globalAlpha = 0.7;
ctx.beginPath(); ctx.arc(x,y,p.s,0,Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/particle-burst.js
(function(){
window.HPX = window.HPX || {};
window.HPX['particle-burst'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
let parts = [];
const spawn = () => {
const cx = k.w/2, cy = k.h/2;
const n = 90;
for (let i=0;i<n;i++){
const a = Math.random()*Math.PI*2;
const s = U.rand(80, 260);
parts.push({
x: cx, y: cy,
vx: Math.cos(a)*s, vy: Math.sin(a)*s,
life: 1, r: U.rand(2,5),
c: pal[(Math.random()*pal.length)|0]
});
}
};
spawn();
let lastSpawn = 0;
const stop = U.loop((t) => {
ctx.clearRect(0,0,k.w,k.h);
if (t - lastSpawn > 2.5) { spawn(); lastSpawn = t; }
const dt = 1/60;
parts = parts.filter(p => p.life > 0);
for (const p of parts){
p.vy += 220*dt;
p.vx *= 0.985; p.vy *= 0.985;
p.x += p.vx*dt; p.y += p.vy*dt;
p.life -= 0.012;
ctx.globalAlpha = Math.max(0, p.life);
ctx.fillStyle = p.c;
ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/gradient-blob.js
(function(){
window.HPX = window.HPX || {};
window.HPX['gradient-blob'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
const blobs = Array.from({length:4}, (_,i) => ({
x: U.rand(0,1), y: U.rand(0,1),
vx: U.rand(-0.08,0.08), vy: U.rand(-0.08,0.08),
r: U.rand(180,320),
c: pal[i%pal.length]
}));
const hex2rgb = (h) => {
const m = h.replace('#','').match(/.{2}/g);
if (!m) return [124,92,255];
return m.map(x=>parseInt(x,16));
};
const stop = U.loop((t) => {
ctx.fillStyle = 'rgba(10,12,22,0.2)';
ctx.fillRect(0,0,k.w,k.h);
ctx.globalCompositeOperation = 'lighter';
for (const b of blobs){
b.x += b.vx*0.01; b.y += b.vy*0.01;
if (b.x<0||b.x>1) b.vx*=-1;
if (b.y<0||b.y>1) b.vy*=-1;
const px = b.x*k.w, py = b.y*k.h;
const r = b.r + Math.sin(t*0.8 + b.x*6)*30;
const [R,G,B] = hex2rgb(b.c);
const grad = ctx.createRadialGradient(px,py,0,px,py,r);
grad.addColorStop(0, `rgba(${R},${G},${B},0.55)`);
grad.addColorStop(1, `rgba(${R},${G},${B},0)`);
ctx.fillStyle = grad;
ctx.beginPath(); ctx.arc(px,py,r,0,Math.PI*2); ctx.fill();
}
ctx.globalCompositeOperation = 'source-over';
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/magnetic-field.js
(function(){
window.HPX = window.HPX || {};
window.HPX['magnetic-field'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
const N = 60;
const parts = Array.from({length:N}, (_,i) => ({
phase: Math.random()*Math.PI*2,
freq: U.rand(0.4, 1.2),
amp: U.rand(30, 90),
y0: U.rand(0.15, 0.85),
c: pal[i%pal.length],
trail: []
}));
const stop = U.loop((t) => {
ctx.fillStyle = 'rgba(0,0,0,0.08)';
ctx.fillRect(0,0,k.w,k.h);
for (const p of parts){
const x = ((t*80 + p.phase*50) % (k.w+100)) - 50;
const y = k.h*p.y0 + Math.sin(x*0.02 + p.phase + t*p.freq)*p.amp;
p.trail.push([x,y]);
if (p.trail.length > 18) p.trail.shift();
ctx.strokeStyle = p.c;
ctx.lineWidth = 2;
ctx.beginPath();
for (let i=0;i<p.trail.length;i++){
const [tx,ty] = p.trail[i];
if (i===0) ctx.moveTo(tx,ty); else ctx.lineTo(tx,ty);
}
ctx.globalAlpha = 0.7;
ctx.stroke();
ctx.globalAlpha = 1;
ctx.fillStyle = p.c;
ctx.beginPath(); ctx.arc(x,y,2.5,0,Math.PI*2); ctx.fill();
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/shockwave.js
(function(){
window.HPX = window.HPX || {};
window.HPX['shockwave'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
let waves = [];
let last = -1;
const stop = U.loop((t) => {
ctx.fillStyle = 'rgba(0,0,0,0.12)';
ctx.fillRect(0,0,k.w,k.h);
if (t - last > 0.6){ last = t; waves.push({t:0}); }
const cx=k.w/2, cy=k.h/2;
const max = Math.hypot(k.w,k.h)/2;
waves = waves.filter(w => w.t < 1);
for (const w of waves){
w.t += 0.012;
const r = w.t * max;
const alpha = 1 - w.t;
ctx.strokeStyle = w.t<0.5?ac2:ac;
ctx.globalAlpha = alpha;
ctx.lineWidth = 3 + (1-w.t)*3;
ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.stroke();
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.globalAlpha = alpha*0.4;
ctx.beginPath(); ctx.arc(cx,cy,r*0.92,0,Math.PI*2); ctx.stroke();
}
ctx.globalAlpha = 1;
// core
const g = ctx.createRadialGradient(cx,cy,0,cx,cy,40);
g.addColorStop(0,'rgba(255,255,255,0.9)');
g.addColorStop(1,'rgba(124,92,255,0)');
ctx.fillStyle = g;
ctx.beginPath(); ctx.arc(cx,cy,40,0,Math.PI*2); ctx.fill();
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/base.css
/* html-ppt :: base.css — reset + shared tokens + layout primitives */
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
:root {
--bg: #ffffff;
--bg-soft: #f7f7f8;
--surface: #ffffff;
--surface-2: #f2f2f4;
--border: rgba(0,0,0,.08);
--border-strong: rgba(0,0,0,.16);
--text-1: #111216;
--text-2: #55596a;
--text-3: #8a8f9e;
--accent: #3b6cff;
--accent-2: #7a5cff;
--accent-3: #ff5c8a;
--good: #1aaf6c;
--warn: #f5a524;
--bad: #e0445a;
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
--radius: 18px;
--radius-sm: 12px;
--radius-lg: 26px;
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
--font-display: var(--font-sans);
--letter-tight: -.03em;
--letter-normal: -.01em;
--ease: cubic-bezier(.4,0,.2,1);
}
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
font-family:var(--font-sans);font-weight:400;line-height:1.6;
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
letter-spacing:var(--letter-normal)}
img,svg,video{max-width:100%;display:block}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
code,kbd,pre,samp{font-family:var(--font-mono)}
/* ================= SLIDE SYSTEM ================= */
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
.slide{
position:absolute;inset:0;
display:flex;flex-direction:column;justify-content:center;
padding:72px 96px;
box-sizing:border-box;
opacity:0;pointer-events:none;
transition:opacity .5s var(--ease), transform .5s var(--ease);
transform:translateX(30px);
overflow:hidden;
}
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
.slide.is-prev{transform:translateX(-30px)}
/* single-page standalone (used when a layout file is opened directly) */
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
/* ================= TYPOGRAPHY ================= */
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
.dim{color:var(--text-2)}
.dim2{color:var(--text-3)}
.mono{font-family:var(--font-mono)}
.serif{font-family:var(--font-serif)}
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
/* ================= LAYOUT PRIMITIVES ================= */
.stack>*+*{margin-top:14px}
.row{display:flex;gap:24px;align-items:center}
.row.wrap{flex-wrap:wrap}
.grid{display:grid;gap:24px}
.g2{grid-template-columns:repeat(2,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}
.center{display:flex;align-items:center;justify-content:center;text-align:center}
.fill{flex:1}
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
/* ================= CARDS ================= */
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
/* ================= BARS / DIVIDERS ================= */
.divider{height:1px;background:var(--border);width:100%}
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
/* ================= CHROME (header/footer/progress) ================= */
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
.slide-number::before{content:attr(data-current)}
.slide-number::after{content:" / " attr(data-total)}
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
/* ================= PRESENTER / OVERVIEW ================= */
.notes{display:none!important}
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
.notes-overlay.open{transform:translateY(0)}
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
display:none;padding:40px;overflow:auto}
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
font-size:11px;transition:transform .2s var(--ease)}
.overview .thumb:hover{transform:scale(1.04)}
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
/* ================= PRESENTER VIEW ================= */
/* Presenter view opens in a separate popup window (S key).
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
* The audience window (this file) is NOT affected — it stays as normal deck view.
* Only the .notes class below is needed to hide speaker notes from audience. */
/* ================= UTILITY ================= */
.hidden{display:none!important}
.nowrap{white-space:nowrap}
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
.uppercase{text-transform:uppercase;letter-spacing:.12em}
/* ================= PRINT ================= */
@media print{
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
}
assets/animations/fx/word-cascade.js
(function(){
window.HPX = window.HPX || {};
window.HPX['word-cascade'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
const WORDS = ['AI','知识','Graph','Claude','LLM','Agent','Vector','RAG','Token','神经',
'Prompt','Chain','Skill','Code','Cloud','GPU','Flow','推理','Data','Model'];
let items = [];
let last = -1;
let piles = {}; // column -> stack height
const stop = U.loop((t) => {
ctx.clearRect(0,0,k.w,k.h);
if (t - last > 0.18){
last = t;
const w = WORDS[(Math.random()*WORDS.length)|0];
items.push({
text: w, x: U.rand(40, k.w-40), y: -20,
vy: 0, c: pal[(Math.random()*pal.length)|0],
size: U.rand(16,26), landed: false
});
}
ctx.textAlign='center'; ctx.textBaseline='middle';
for (const it of items){
if (!it.landed){
it.vy += 0.4;
it.y += it.vy;
const col = Math.round(it.x/60);
const floor = k.h - (piles[col]||0) - it.size*0.6;
if (it.y >= floor){
it.y = floor; it.landed = true;
piles[col] = (piles[col]||0) + it.size*1.1;
if ((piles[col]||0) > k.h*0.8) piles[col] = 0; // reset if too high
}
}
ctx.fillStyle = it.c;
ctx.font = `700 ${it.size}px system-ui,sans-serif`;
ctx.fillText(it.text, it.x, it.y);
}
// prune old landed
if (items.length > 120){
items = items.filter(i => !i.landed).concat(items.filter(i=>i.landed).slice(-60));
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/typewriter-multi.js
(function(){
window.HPX = window.HPX || {};
window.HPX['typewriter-multi'] = function(el){
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
const lines = [
(el.getAttribute('data-fx-line1') || '> initializing knowledge graph...'),
(el.getAttribute('data-fx-line2') || '> loading 28 concept nodes'),
(el.getAttribute('data-fx-line3') || '> agent ready. awaiting prompt_'),
];
const wrap = document.createElement('div');
wrap.style.cssText = 'position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:14px;padding:32px 48px;font:600 22px ui-monospace,Menlo,monospace;color:var(--text-1,#e7e7ef);';
el.appendChild(wrap);
const rows = lines.map((txt) => {
const row = document.createElement('div');
row.style.cssText = 'white-space:pre;display:flex;align-items:center;';
const span = document.createElement('span'); span.textContent = '';
const cur = document.createElement('span');
cur.textContent = '\u2588';
cur.style.cssText = 'display:inline-block;margin-left:2px;color:var(--accent,#22d3ee);animation:hpxBlink 1s steps(2) infinite;';
row.appendChild(span); row.appendChild(cur);
wrap.appendChild(row);
return {row, span, txt, i:0};
});
// inject blink keyframes once
if (!document.getElementById('hpx-blink-kf')){
const st = document.createElement('style');
st.id = 'hpx-blink-kf';
st.textContent = '@keyframes hpxBlink{50%{opacity:0}}';
document.head.appendChild(st);
}
let stopped = false;
const speeds = [55, 70, 45];
rows.forEach((r, idx) => {
const tick = () => {
if (stopped) return;
if (r.i < r.txt.length){
r.span.textContent += r.txt[r.i++];
setTimeout(tick, speeds[idx]);
} else {
setTimeout(() => {
if (stopped) return;
r.i = 0; r.span.textContent = '';
tick();
}, 2200);
}
};
setTimeout(tick, idx*400);
});
return { stop(){ stopped = true; if (wrap.parentNode) wrap.parentNode.removeChild(wrap); } };
};
})();
assets/fonts.css
/* html-ppt :: shared webfonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
assets/runtime.js
/* html-ppt :: runtime.js
* Keyboard-driven deck runtime. Zero dependencies.
*
* Features:
* ← → / space / PgUp PgDn / Home End navigation
* F fullscreen
* S presenter mode (opens a NEW WINDOW with current/next slide preview + notes + timer)
* The original window stays as audience view, synced via BroadcastChannel.
* Slide previews use CSS transform:scale() at design resolution for pixel-perfect layout.
* N quick notes overlay (bottom drawer)
* O slide overview grid
* T cycle themes (reads data-themes on <html> or <body>)
* A cycle demo animation on current slide
* URL hash #/N deep-link to slide N (1-based)
* Progress bar auto-managed
*/
(function () {
'use strict';
const ANIMS = ['fade-up','fade-down','fade-left','fade-right','rise-in','drop-in',
'zoom-pop','blur-in','glitch-in','typewriter','neon-glow','shimmer-sweep',
'gradient-flow','stagger-list','counter-up','path-draw','parallax-tilt',
'card-flip-3d','cube-rotate-3d','page-turn-3d','perspective-zoom',
'marquee-scroll','kenburns','confetti-burst','spotlight','morph-shape','ripple-reveal'];
function ready(fn){ if(document.readyState!='loading')fn(); else document.addEventListener('DOMContentLoaded',fn);}
/* ========== Parse URL for preview-only mode ==========
* When loaded as iframe.src = "index.html?preview=3", runtime enters a
* locked single-slide mode: only slide N is visible, no chrome, no keys,
* no hash updates. This is how the presenter window shows pixel-perfect
* previews — by loading the actual deck file in an iframe and telling it
* to display only a specific slide.
*/
function getPreviewIdx() {
const m = /[?&]preview=(\d+)/.exec(location.search || '');
return m ? parseInt(m[1], 10) - 1 : -1;
}
ready(function () {
const deck = document.querySelector('.deck');
if (!deck) return;
const slides = Array.from(deck.querySelectorAll('.slide'));
if (!slides.length) return;
const previewOnlyIdx = getPreviewIdx();
const isPreviewMode = previewOnlyIdx >= 0 && previewOnlyIdx < slides.length;
/* ===== Preview-only mode: show one slide, hide everything else ===== */
if (isPreviewMode) {
function showSlide(i) {
slides.forEach((s, j) => {
const active = (j === i);
s.classList.toggle('is-active', active);
s.style.display = active ? '' : 'none';
if (active) {
s.style.opacity = '1';
s.style.transform = 'none';
s.style.pointerEvents = 'auto';
}
});
}
showSlide(previewOnlyIdx);
/* Hide chrome that the presenter shouldn't see in preview */
const hideSel = '.progress-bar, .notes-overlay, .overview, .notes, aside.notes, .speaker-notes';
document.querySelectorAll(hideSel).forEach(el => { el.style.display = 'none'; });
document.documentElement.setAttribute('data-preview', '1');
document.body.setAttribute('data-preview', '1');
/* Auto-detect theme base path for theme switching in preview mode */
function getPreviewThemeBase() {
const base = document.documentElement.getAttribute('data-theme-base');
if (base) return base;
const tl = document.getElementById('theme-link');
if (tl) {
const raw = tl.getAttribute('href') || '';
const ls = raw.lastIndexOf('/');
if (ls >= 0) return raw.substring(0, ls + 1);
}
return 'assets/themes/';
}
const previewThemeBase = getPreviewThemeBase();
/* Listen for postMessage from parent presenter window:
* - preview-goto: switch visible slide WITHOUT reloading
* - preview-theme: switch theme CSS link to match audience window */
window.addEventListener('message', function(e) {
if (!e.data) return;
if (e.data.type === 'preview-goto') {
const n = parseInt(e.data.idx, 10);
if (n >= 0 && n < slides.length) showSlide(n);
} else if (e.data.type === 'preview-theme' && e.data.name) {
let link = document.getElementById('theme-link');
if (!link) {
link = document.createElement('link');
link.rel = 'stylesheet';
link.id = 'theme-link';
document.head.appendChild(link);
}
link.href = previewThemeBase + e.data.name + '.css';
document.documentElement.setAttribute('data-theme', e.data.name);
}
});
/* Signal to parent that preview iframe is ready */
try { window.parent && window.parent.postMessage({ type: 'preview-ready' }, '*'); } catch(e) {}
return;
}
let idx = 0;
const total = slides.length;
/* ===== BroadcastChannel for presenter sync ===== */
const CHANNEL_NAME = 'html-ppt-presenter-' + location.pathname;
let bc;
try { bc = new BroadcastChannel(CHANNEL_NAME); } catch(e) { bc = null; }
// Are we running inside the presenter popup? (legacy flag, now unused)
const isPresenterWindow = false;
/* ===== progress bar ===== */
let bar = document.querySelector('.progress-bar');
if (!bar) {
bar = document.createElement('div');
bar.className = 'progress-bar';
bar.innerHTML = '<span></span>';
document.body.appendChild(bar);
}
const barFill = bar.querySelector('span');
/* ===== notes overlay (N key) ===== */
let notes = document.querySelector('.notes-overlay');
if (!notes) {
notes = document.createElement('div');
notes.className = 'notes-overlay';
document.body.appendChild(notes);
}
/* ===== overview grid (O key) ===== */
let overview = document.querySelector('.overview');
if (!overview) {
overview = document.createElement('div');
overview.className = 'overview';
slides.forEach((s, i) => {
const t = document.createElement('div');
t.className = 'thumb';
// Force 16:9 aspect ratio robustly
t.style.padding = '0 0 56.25% 0';
t.style.height = '0';
t.style.position = 'relative';
t.style.overflow = 'hidden';
const title = s.getAttribute('data-title') ||
(s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1));
// Create a container for the mini-slide
const mini = document.createElement('div');
mini.className = 'mini-slide';
mini.style.position = 'absolute';
mini.style.top = '0';
mini.style.left = '0';
mini.style.width = '1920px';
mini.style.height = '1080px';
mini.style.transformOrigin = 'top left';
mini.style.pointerEvents = 'none';
mini.style.background = 'var(--bg)';
// Clone the slide content
const clone = s.cloneNode(true);
clone.className = 'slide is-active'; // force active styles
clone.style.position = 'absolute';
clone.style.inset = '0';
clone.style.transform = 'none';
clone.style.opacity = '1';
clone.style.padding = '72px 96px'; // ensure padding is kept
mini.appendChild(clone);
t.appendChild(mini);
// Add the number and title overlay
const overlay = document.createElement('div');
overlay.style.position = 'absolute';
overlay.style.inset = '0';
overlay.style.background = 'linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, transparent 40%, transparent 60%, rgba(0,0,0,0.8) 100%)';
overlay.style.color = '#fff';
overlay.style.zIndex = '10';
overlay.style.pointerEvents = 'none';
const n = document.createElement('div');
n.className = 'n';
n.textContent = i + 1;
n.style.position = 'absolute';
n.style.top = '12px';
n.style.left = '16px';
n.style.fontWeight = '700';
n.style.fontSize = '16px';
n.style.color = '#fff';
n.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
const text = document.createElement('div');
text.className = 't';
text.textContent = title.trim().slice(0,80);
text.style.position = 'absolute';
text.style.bottom = '12px';
text.style.left = '16px';
text.style.right = '16px';
text.style.fontWeight = '600';
text.style.fontSize = '14px';
text.style.color = '#fff';
text.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
overlay.appendChild(n);
overlay.appendChild(text);
t.appendChild(overlay);
t.addEventListener('click', () => { go(i); toggleOverview(false); });
overview.appendChild(t);
});
document.body.appendChild(overview);
}
/* ===== navigation ===== */
function go(n, fromRemote){
n = Math.max(0, Math.min(total-1, n));
slides.forEach((s,i) => {
s.classList.toggle('is-active', i===n);
s.classList.toggle('is-prev', i<n);
});
idx = n;
barFill.style.width = ((n+1)/total*100)+'%';
const numEl = document.querySelector('.slide-number');
if (numEl) { numEl.setAttribute('data-current', n+1); numEl.setAttribute('data-total', total); }
// notes (bottom overlay)
const note = slides[n].querySelector('.notes, aside.notes, .speaker-notes');
notes.innerHTML = note ? note.innerHTML : '';
// hash
const hashTarget = '#/'+(n+1);
if (location.hash !== hashTarget && !isPresenterWindow) {
history.replaceState(null,'', hashTarget);
}
// re-trigger entry animations
slides[n].querySelectorAll('[data-anim]').forEach(el => {
const a = el.getAttribute('data-anim');
el.classList.remove('anim-'+a);
void el.offsetWidth;
el.classList.add('anim-'+a);
});
// counter-up
slides[n].querySelectorAll('.counter').forEach(el => {
const target = parseFloat(el.getAttribute('data-to')||el.textContent);
const dur = parseInt(el.getAttribute('data-dur')||'1200',10);
const start = performance.now();
const from = 0;
function tick(now){
const t = Math.min(1,(now-start)/dur);
const v = from + (target-from)*(1-Math.pow(1-t,3));
el.textContent = (target % 1 === 0) ? Math.round(v) : v.toFixed(1);
if (t<1) requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
// Broadcast to other window (audience ↔ presenter)
if (!fromRemote && bc) {
bc.postMessage({ type: 'go', idx: n });
}
}
/* ===== listen for remote navigation / theme changes ===== */
if (bc) {
bc.onmessage = function(e) {
if (!e.data) return;
if (e.data.type === 'go' && typeof e.data.idx === 'number') {
go(e.data.idx, true);
} else if (e.data.type === 'theme' && e.data.name) {
/* Sync theme across windows */
const i = themes.indexOf(e.data.name);
if (i >= 0) themeIdx = i;
applyTheme(e.data.name);
}
};
}
function toggleNotes(force){ notes.classList.toggle('open', force!==undefined?force:!notes.classList.contains('open')); }
function toggleOverview(force){
const isOpen = force!==undefined ? force : !overview.classList.contains('open');
overview.classList.toggle('open', isOpen);
if (isOpen) {
requestAnimationFrame(() => {
const thumbs = overview.querySelectorAll('.thumb');
if (thumbs.length) {
const scale = thumbs[0].clientWidth / 1920;
overview.querySelectorAll('.mini-slide').forEach(m => {
m.style.transform = 'scale(' + scale + ')';
});
}
});
}
}
/* ========== PRESENTER MODE — Magnetic-card popup window ========== */
/* Opens a new window with 4 draggable, resizable cards:
* CURRENT — iframe(?preview=N) pixel-perfect preview of current slide
* NEXT — iframe(?preview=N+1) pixel-perfect preview of next slide
* SCRIPT — large speaker notes (逐字稿)
* TIMER — elapsed timer + page counter + controls
* Cards remember position/size in localStorage.
* Two windows sync via BroadcastChannel.
*/
let presenterWin = null;
function openPresenterWindow() {
if (presenterWin && !presenterWin.closed) {
presenterWin.focus();
return;
}
// Build absolute URL of THIS deck file (without hash/query)
const deckUrl = location.protocol + '//' + location.host + location.pathname;
// Collect slide titles + notes (HTML strings)
const slideMeta = slides.map((s, i) => {
const note = s.querySelector('.notes, aside.notes, .speaker-notes');
return {
title: s.getAttribute('data-title') ||
(s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1)),
notes: note ? note.innerHTML : ''
};
});
/* Capture current theme so presenter previews match the audience */
const currentTheme = root.getAttribute('data-theme') || (themes[themeIdx] || '');
const presenterHTML = buildPresenterHTML(deckUrl, slideMeta, total, idx, CHANNEL_NAME, currentTheme);
presenterWin = window.open('', 'html-ppt-presenter', 'width=1280,height=820,menubar=no,toolbar=no');
if (!presenterWin) {
alert('请允许弹出窗口以使用演讲者视图');
return;
}
presenterWin.document.open();
presenterWin.document.write(presenterHTML);
presenterWin.document.close();
}
function buildPresenterHTML(deckUrl, slideMeta, total, startIdx, channelName, currentTheme) {
const metaJSON = JSON.stringify(slideMeta);
const deckUrlJSON = JSON.stringify(deckUrl);
const channelJSON = JSON.stringify(channelName);
const themeJSON = JSON.stringify(currentTheme || '');
const storageKey = 'html-ppt-presenter:' + location.pathname;
// Build the document as a single template string for clarity
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>Presenter View</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%; overflow: hidden;
background: #1a1d24;
background-image:
radial-gradient(circle at 20% 30%, rgba(88,166,255,.04), transparent 50%),
radial-gradient(circle at 80% 70%, rgba(188,140,255,.04), transparent 50%);
color: #e6edf3;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif;
}
/* Stage: positioned area where cards live */
#stage { position: absolute; inset: 0; overflow: hidden; }
/* Magnetic card */
.pcard {
position: absolute;
background: #0d1117;
border: 1px solid rgba(255,255,255,.1);
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0,0,0,.45), 0 0 0 1px rgba(255,255,255,.02);
display: flex; flex-direction: column;
overflow: hidden;
min-width: 180px; min-height: 100px;
transition: box-shadow .2s, border-color .2s;
}
.pcard.dragging { box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 2px rgba(88,166,255,.5); border-color: #58a6ff; transition: none; z-index: 9999; }
.pcard.resizing { box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 2px rgba(63,185,80,.5); border-color: #3fb950; transition: none; z-index: 9999; }
.pcard:hover { border-color: rgba(88,166,255,.3); }
/* Card header (drag handle) */
.pcard-head {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px;
background: rgba(255,255,255,.04);
border-bottom: 1px solid rgba(255,255,255,.06);
cursor: move;
user-select: none;
flex-shrink: 0;
}
.pcard-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dot-color, #58a6ff); flex-shrink: 0; }
.pcard-title {
font-size: 11px; letter-spacing: .15em; text-transform: uppercase;
font-weight: 700; color: #8b949e; flex: 1;
}
.pcard-meta { font-size: 11px; color: #6e7681; }
/* Card body */
.pcard-body { flex: 1; position: relative; overflow: hidden; min-height: 0; }
/* Preview cards (CURRENT/NEXT) — iframe-based pixel-perfect render */
.pcard-preview .pcard-body { background: #000; }
.pcard-preview iframe {
position: absolute; top: 0; left: 0;
width: 1920px; height: 1080px;
border: none;
transform-origin: top left;
pointer-events: none;
background: transparent;
}
.pcard-preview .preview-end {
position: absolute; inset: 0;
display: flex; align-items: center; justify-content: center;
color: #484f58; font-size: 14px; letter-spacing: .12em;
}
/* Notes card */
.pcard-notes .pcard-body {
padding: 14px 18px;
overflow-y: auto;
font-size: 18px; line-height: 1.75;
color: #d0d7de;
font-family: "Noto Sans SC", -apple-system, sans-serif;
}
.pcard-notes .pcard-body p { margin: 0 0 .7em 0; }
.pcard-notes .pcard-body strong { color: #f0883e; }
.pcard-notes .pcard-body em { color: #58a6ff; font-style: normal; }
.pcard-notes .pcard-body code {
font-family: "SF Mono", monospace; font-size: .9em;
background: rgba(255,255,255,.08); padding: 1px 6px; border-radius: 4px;
}
.pcard-notes .empty { color: #484f58; font-style: italic; }
/* Timer card */
.pcard-timer .pcard-body {
display: flex; flex-direction: column; gap: 14px;
padding: 18px 20px; justify-content: center;
}
.timer-display {
font-family: "SF Mono", "JetBrains Mono", monospace;
font-size: 42px; font-weight: 700;
color: #3fb950;
letter-spacing: .04em;
line-height: 1;
}
.timer-row {
display: flex; align-items: center; gap: 12px;
font-size: 14px; color: #8b949e;
}
.timer-row .label { font-size: 10px; letter-spacing: .15em; text-transform: uppercase; color: #6e7681; }
.timer-row .val { color: #e6edf3; font-weight: 600; font-family: "SF Mono", monospace; }
.timer-controls { display: flex; gap: 8px; flex-wrap: wrap; }
.timer-btn {
background: rgba(255,255,255,.06);
border: 1px solid rgba(255,255,255,.1);
color: #e6edf3;
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
font-family: inherit;
}
.timer-btn:hover { background: rgba(88,166,255,.15); border-color: #58a6ff; }
.timer-btn:active { transform: translateY(1px); }
/* Resize handle */
.pcard-resize {
position: absolute; right: 0; bottom: 0;
width: 18px; height: 18px;
cursor: nwse-resize;
background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,.25) 50%, rgba(255,255,255,.25) 60%, transparent 60%, transparent 70%, rgba(255,255,255,.25) 70%, rgba(255,255,255,.25) 80%, transparent 80%);
z-index: 5;
}
.pcard-resize:hover { background: linear-gradient(135deg, transparent 50%, #58a6ff 50%, #58a6ff 60%, transparent 60%, transparent 70%, #58a6ff 70%, #58a6ff 80%, transparent 80%); }
/* Bottom hint bar */
.hint-bar {
position: fixed; bottom: 0; left: 0; right: 0;
background: rgba(0,0,0,.6);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(255,255,255,.08);
padding: 6px 16px;
font-size: 11px; color: #8b949e;
display: flex; gap: 18px; align-items: center;
z-index: 1000;
}
.hint-bar kbd {
background: rgba(255,255,255,.08);
padding: 1px 6px; border-radius: 3px;
font-family: "SF Mono", monospace;
font-size: 10px;
border: 1px solid rgba(255,255,255,.1);
color: #e6edf3;
}
.hint-bar .reset-layout {
margin-left: auto;
background: transparent; border: 1px solid rgba(255,255,255,.15);
color: #8b949e; padding: 3px 10px; border-radius: 4px;
font-size: 11px; cursor: pointer; font-family: inherit;
}
.hint-bar .reset-layout:hover { background: rgba(248,81,73,.15); border-color: #f85149; color: #f85149; }
body.is-dragging-card * { user-select: none !important; }
body.is-dragging-card iframe { pointer-events: none !important; }
</style>
</head>
<body>
<div id="stage">
<div class="pcard pcard-preview" id="card-cur" style="--dot-color:#58a6ff">
<div class="pcard-head" data-drag>
<span class="pcard-dot"></span>
<span class="pcard-title">CURRENT</span>
<span class="pcard-meta" id="cur-meta">—</span>
</div>
<div class="pcard-body"><iframe id="iframe-cur"></iframe></div>
<div class="pcard-resize" data-resize></div>
</div>
<div class="pcard pcard-preview" id="card-nxt" style="--dot-color:#bc8cff">
<div class="pcard-head" data-drag>
<span class="pcard-dot"></span>
<span class="pcard-title">NEXT</span>
<span class="pcard-meta" id="nxt-meta">—</span>
</div>
<div class="pcard-body"><iframe id="iframe-nxt"></iframe></div>
<div class="pcard-resize" data-resize></div>
</div>
<div class="pcard pcard-notes" id="card-notes" style="--dot-color:#f0883e">
<div class="pcard-head" data-drag>
<span class="pcard-dot"></span>
<span class="pcard-title">SPEAKER SCRIPT · 逐字稿</span>
</div>
<div class="pcard-body" id="notes-body"></div>
<div class="pcard-resize" data-resize></div>
</div>
<div class="pcard pcard-timer" id="card-timer" style="--dot-color:#3fb950">
<div class="pcard-head" data-drag>
<span class="pcard-dot"></span>
<span class="pcard-title">TIMER</span>
</div>
<div class="pcard-body">
<div class="timer-display" id="timer-display">00:00</div>
<div class="timer-row">
<span class="label">Slide</span>
<span class="val" id="timer-count">1 / ${total}</span>
</div>
<div class="timer-controls">
<button class="timer-btn" id="btn-prev">← Prev</button>
<button class="timer-btn" id="btn-next">Next →</button>
<button class="timer-btn" id="btn-reset">⏱ Reset</button>
</div>
</div>
<div class="pcard-resize" data-resize></div>
</div>
</div>
<div class="hint-bar">
<span><kbd>← →</kbd> 翻页</span>
<span><kbd>R</kbd> 重置计时</span>
<span><kbd>Esc</kbd> 关闭</span>
<span style="color:#6e7681">拖动卡片头部移动 · 拖动右下角调整大小</span>
<button class="reset-layout" id="reset-layout">重置布局</button>
</div>
<script>
(function(){
var slideMeta = ${metaJSON};
var total = ${total};
var idx = ${startIdx};
var deckUrl = ${deckUrlJSON};
var STORAGE_KEY = ${JSON.stringify(storageKey)};
var bc;
try { bc = new BroadcastChannel(${channelJSON}); } catch(e) {}
var iframeCur = document.getElementById('iframe-cur');
var iframeNxt = document.getElementById('iframe-nxt');
var notesBody = document.getElementById('notes-body');
var curMeta = document.getElementById('cur-meta');
var nxtMeta = document.getElementById('nxt-meta');
var timerDisplay = document.getElementById('timer-display');
var timerCount = document.getElementById('timer-count');
/* ===== Default card layout ===== */
function defaultLayout() {
var w = window.innerWidth;
var h = window.innerHeight - 36; /* leave room for hint bar */
return {
'card-cur': { x: 16, y: 16, w: Math.round(w*0.55) - 24, h: Math.round(h*0.62) - 16 },
'card-nxt': { x: Math.round(w*0.55) + 8, y: 16, w: w - Math.round(w*0.55) - 24, h: Math.round(h*0.42) - 16 },
'card-notes': { x: Math.round(w*0.55) + 8, y: Math.round(h*0.42) + 8, w: w - Math.round(w*0.55) - 24, h: h - Math.round(h*0.42) - 16 },
'card-timer': { x: 16, y: Math.round(h*0.62) + 8, w: Math.round(w*0.55) - 24, h: h - Math.round(h*0.62) - 16 }
};
}
/* ===== Apply / save / restore layout ===== */
function applyLayout(layout) {
Object.keys(layout).forEach(function(id){
var el = document.getElementById(id);
var l = layout[id];
if (el && l) {
el.style.left = l.x + 'px';
el.style.top = l.y + 'px';
el.style.width = l.w + 'px';
el.style.height = l.h + 'px';
}
});
rescaleAll();
}
function readLayout() {
try {
var saved = localStorage.getItem(STORAGE_KEY);
if (saved) return JSON.parse(saved);
} catch(e) {}
return defaultLayout();
}
function saveLayout() {
var layout = {};
['card-cur','card-nxt','card-notes','card-timer'].forEach(function(id){
var el = document.getElementById(id);
if (el) {
layout[id] = {
x: parseInt(el.style.left,10) || 0,
y: parseInt(el.style.top,10) || 0,
w: parseInt(el.style.width,10) || 300,
h: parseInt(el.style.height,10) || 200
};
}
});
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(layout)); } catch(e) {}
}
/* ===== iframe rescale to fit card body ===== */
function rescaleIframe(iframe) {
if (!iframe || iframe.style.display === 'none') return;
var body = iframe.parentElement;
var cw = body.clientWidth, ch = body.clientHeight;
if (!cw || !ch) return;
var s = Math.min(cw / 1920, ch / 1080);
iframe.style.transform = 'scale(' + s + ')';
/* Center the scaled iframe in the body */
var sw = 1920 * s, sh = 1080 * s;
iframe.style.left = Math.max(0, (cw - sw) / 2) + 'px';
iframe.style.top = Math.max(0, (ch - sh) / 2) + 'px';
}
function rescaleAll() {
rescaleIframe(iframeCur);
rescaleIframe(iframeNxt);
}
window.addEventListener('resize', rescaleAll);
/* ===== Drag (move card by header) ===== */
document.querySelectorAll('[data-drag]').forEach(function(handle){
handle.addEventListener('mousedown', function(e){
if (e.button !== 0) return;
var card = handle.closest('.pcard');
if (!card) return;
e.preventDefault();
card.classList.add('dragging');
document.body.classList.add('is-dragging-card');
var startX = e.clientX, startY = e.clientY;
var startL = parseInt(card.style.left,10) || 0;
var startT = parseInt(card.style.top,10) || 0;
function onMove(ev){
var nx = Math.max(0, Math.min(window.innerWidth - 100, startL + ev.clientX - startX));
var ny = Math.max(0, Math.min(window.innerHeight - 50, startT + ev.clientY - startY));
card.style.left = nx + 'px';
card.style.top = ny + 'px';
}
function onUp(){
card.classList.remove('dragging');
document.body.classList.remove('is-dragging-card');
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
saveLayout();
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
});
/* ===== Resize (drag bottom-right corner) ===== */
document.querySelectorAll('[data-resize]').forEach(function(handle){
handle.addEventListener('mousedown', function(e){
if (e.button !== 0) return;
var card = handle.closest('.pcard');
if (!card) return;
e.preventDefault(); e.stopPropagation();
card.classList.add('resizing');
document.body.classList.add('is-dragging-card');
var startX = e.clientX, startY = e.clientY;
var startW = parseInt(card.style.width,10) || card.offsetWidth;
var startH = parseInt(card.style.height,10) || card.offsetHeight;
function onMove(ev){
var nw = Math.max(180, startW + ev.clientX - startX);
var nh = Math.max(100, startH + ev.clientY - startY);
card.style.width = nw + 'px';
card.style.height = nh + 'px';
if (card.querySelector('iframe')) rescaleIframe(card.querySelector('iframe'));
}
function onUp(){
card.classList.remove('resizing');
document.body.classList.remove('is-dragging-card');
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
rescaleAll();
saveLayout();
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
});
/* ===== Preview iframe ready tracking =====
* Each iframe loads the deck ONCE with ?preview=1 on init. Subsequent
* slide changes are sent via postMessage('preview-goto') so the iframe
* just toggles visibility of a different .slide — no reload, no flicker.
*/
var iframeReady = { cur: false, nxt: false };
var currentTheme = ${themeJSON};
window.addEventListener('message', function(e) {
if (!e.data || e.data.type !== 'preview-ready') return;
var iframe = null;
if (e.source === iframeCur.contentWindow) {
iframeReady.cur = true;
iframe = iframeCur;
postPreviewGoto(iframeCur, idx);
} else if (e.source === iframeNxt.contentWindow) {
iframeReady.nxt = true;
iframe = iframeNxt;
postPreviewGoto(iframeNxt, idx + 1 < total ? idx + 1 : idx);
}
/* Sync current theme to the iframe */
if (iframe && currentTheme) {
try { iframe.contentWindow.postMessage({ type: 'preview-theme', name: currentTheme }, '*'); } catch(err) {}
}
if (iframe) rescaleIframe(iframe);
});
function postPreviewGoto(iframe, n) {
try {
iframe.contentWindow.postMessage({ type: 'preview-goto', idx: n }, '*');
} catch(e) {}
}
/* ===== Update content =====
* Smooth (no-reload) navigation: send postMessage to iframes instead of
* resetting src. Iframes stay loaded, just switch visible .slide.
*/
function update(n) {
n = Math.max(0, Math.min(total - 1, n));
idx = n;
/* Current preview — postMessage (smooth) */
if (iframeReady.cur) postPreviewGoto(iframeCur, n);
curMeta.textContent = (n + 1) + '/' + total;
/* Next preview */
if (n + 1 < total) {
iframeNxt.style.display = '';
var endEl = document.querySelector('#card-nxt .preview-end');
if (endEl) endEl.remove();
if (iframeReady.nxt) postPreviewGoto(iframeNxt, n + 1);
nxtMeta.textContent = (n + 2) + '/' + total;
} else {
iframeNxt.style.display = 'none';
var body = document.querySelector('#card-nxt .pcard-body');
if (body && !body.querySelector('.preview-end')) {
var end = document.createElement('div');
end.className = 'preview-end';
end.textContent = '— END OF DECK —';
body.appendChild(end);
}
nxtMeta.textContent = 'END';
}
/* Notes */
var note = slideMeta[n].notes;
notesBody.innerHTML = note || '<span class="empty">(这一页还没有逐字稿)</span>';
/* Timer count */
timerCount.textContent = (n + 1) + ' / ' + total;
}
/* ===== Timer ===== */
var tStart = Date.now();
setInterval(function(){
var s = Math.floor((Date.now() - tStart) / 1000);
var mm = String(Math.floor(s/60)).padStart(2,'0');
var ss = String(s%60).padStart(2,'0');
timerDisplay.textContent = mm + ':' + ss;
}, 1000);
function resetTimer(){ tStart = Date.now(); timerDisplay.textContent = '00:00'; }
/* ===== BroadcastChannel sync ===== */
if (bc) {
bc.onmessage = function(e){
if (!e.data) return;
if (e.data.type === 'go') update(e.data.idx);
else if (e.data.type === 'theme' && e.data.name) {
currentTheme = e.data.name;
/* Forward theme change to preview iframes */
[iframeCur, iframeNxt].forEach(function(iframe){
try {
iframe.contentWindow.postMessage({ type: 'preview-theme', name: e.data.name }, '*');
} catch(err) {}
});
}
};
}
function go(n) {
update(n);
if (bc) bc.postMessage({ type: 'go', idx: idx });
}
/* ===== Buttons ===== */
document.getElementById('btn-prev').addEventListener('click', function(){ go(idx - 1); });
document.getElementById('btn-next').addEventListener('click', function(){ go(idx + 1); });
document.getElementById('btn-reset').addEventListener('click', resetTimer);
document.getElementById('reset-layout').addEventListener('click', function(){
if (confirm('恢复默认卡片布局?')) {
try { localStorage.removeItem(STORAGE_KEY); } catch(e){}
applyLayout(defaultLayout());
}
});
/* ===== Keyboard ===== */
document.addEventListener('keydown', function(e){
if (e.metaKey || e.ctrlKey || e.altKey) return;
switch(e.key) {
case 'ArrowRight': case ' ': case 'PageDown': go(idx + 1); e.preventDefault(); break;
case 'ArrowLeft': case 'PageUp': go(idx - 1); e.preventDefault(); break;
case 'Home': go(0); break;
case 'End': go(total - 1); break;
case 'r': case 'R': resetTimer(); break;
case 'Escape': window.close(); break;
}
});
/* ===== Iframe load → rescale (catches initial size) ===== */
iframeCur.addEventListener('load', function(){ rescaleIframe(iframeCur); });
iframeNxt.addEventListener('load', function(){ rescaleIframe(iframeNxt); });
/* ===== Init =====
* Load each iframe ONCE with the deck file. After they post
* 'preview-ready', all subsequent navigation is via postMessage
* (smooth, no reload, no flicker).
*/
applyLayout(readLayout());
iframeCur.src = deckUrl + '?preview=' + (idx + 1);
if (idx + 1 < total) iframeNxt.src = deckUrl + '?preview=' + (idx + 2);
/* Initialize notes/timer/count without touching iframes */
notesBody.innerHTML = slideMeta[idx].notes || '<span class="empty">(这一页还没有逐字稿)</span>';
curMeta.textContent = (idx + 1) + '/' + total;
nxtMeta.textContent = (idx + 2) + '/' + total;
timerCount.textContent = (idx + 1) + ' / ' + total;
})();
</` + `script>
</body></html>`;
}
function fullscreen(){ const el=document.documentElement;
if (!document.fullscreenElement) el.requestFullscreen&&el.requestFullscreen();
else document.exitFullscreen&&document.exitFullscreen();
}
// theme cycling
const root = document.documentElement;
const themesAttr = root.getAttribute('data-themes') || document.body.getAttribute('data-themes');
const themes = themesAttr ? themesAttr.split(',').map(s=>s.trim()).filter(Boolean) : [];
let themeIdx = 0;
// Auto-detect theme base path from existing <link id="theme-link">
let themeBase = root.getAttribute('data-theme-base');
if (!themeBase) {
const existingLink = document.getElementById('theme-link');
if (existingLink) {
// el.getAttribute('href') gives the raw relative path written in HTML
const rawHref = existingLink.getAttribute('href') || '';
const lastSlash = rawHref.lastIndexOf('/');
themeBase = lastSlash >= 0 ? rawHref.substring(0, lastSlash + 1) : 'assets/themes/';
} else {
themeBase = 'assets/themes/';
}
}
function applyTheme(name) {
let link = document.getElementById('theme-link');
if (!link) {
link = document.createElement('link');
link.rel = 'stylesheet';
link.id = 'theme-link';
document.head.appendChild(link);
}
link.href = themeBase + name + '.css';
root.setAttribute('data-theme', name);
const ind = document.querySelector('.theme-indicator');
if (ind) ind.textContent = name;
}
function cycleTheme(fromRemote){
if (!themes.length) return;
themeIdx = (themeIdx+1) % themes.length;
const name = themes[themeIdx];
applyTheme(name);
/* Broadcast to other window (audience ↔ presenter) */
if (!fromRemote && bc) bc.postMessage({ type: 'theme', name: name });
}
// animation cycling on current slide
let animIdx = 0;
function cycleAnim(){
animIdx = (animIdx+1) % ANIMS.length;
const a = ANIMS[animIdx];
const target = slides[idx].querySelector('[data-anim-target]') || slides[idx];
ANIMS.forEach(x => target.classList.remove('anim-'+x));
void target.offsetWidth;
target.classList.add('anim-'+a);
target.setAttribute('data-anim', a);
const ind = document.querySelector('.anim-indicator');
if (ind) ind.textContent = a;
}
document.addEventListener('keydown', function (e) {
if (e.metaKey||e.ctrlKey||e.altKey) return;
switch (e.key) {
case 'ArrowRight': case ' ': case 'PageDown': case 'Enter': go(idx+1); e.preventDefault(); break;
case 'ArrowLeft': case 'PageUp': case 'Backspace': go(idx-1); e.preventDefault(); break;
case 'Home': go(0); break;
case 'End': go(total-1); break;
case 'f': case 'F': fullscreen(); break;
case 's': case 'S': openPresenterWindow(); break;
case 'n': case 'N': toggleNotes(); break;
case 'o': case 'O': toggleOverview(); break;
case 't': case 'T': cycleTheme(); break;
case 'a': case 'A': cycleAnim(); break;
case 'Escape': toggleOverview(false); toggleNotes(false); break;
}
});
// hash deep-link
function fromHash(){
const m = /^#\/(\d+)/.exec(location.hash||'');
if (m) go(Math.max(0, parseInt(m[1],10)-1));
}
window.addEventListener('hashchange', fromHash);
fromHash();
go(idx);
});
})();
assets/themes/academic-paper.css
/* theme: academic-paper — 学术论文 */
:root{
--bg:#fdfcf8;--bg-soft:#f7f5ed;--surface:#ffffff;--surface-2:#f5f3ea;
--border:rgba(20,20,20,.14);--border-strong:rgba(20,20,20,.35);
--text-1:#0a0a0a;--text-2:#333333;--text-3:#707070;
--accent:#1a3a7a;--accent-2:#0a0a0a;--accent-3:#8a1a1a;
--good:#1a5a2a;--warn:#8a6a1a;--bad:#8a1a1a;
--grad:linear-gradient(135deg,#1a3a7a,#0a0a0a);
--grad-soft:linear-gradient(135deg,#e8edf8,#f5f3ea);
--radius:0px;--radius-sm:0px;--radius-lg:0px;
--shadow:none;
--shadow-lg:0 1px 2px rgba(0,0,0,.1);
--font-sans:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
--font-serif:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
--font-display:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
}
body{font-family:var(--font-serif)}
h1.title,h2.title,.h1,.h2{font-weight:700;font-family:var(--font-serif)}
.card{border:1px solid var(--border);box-shadow:none}
.divider{background:var(--text-1);height:1px}
.divider-accent{background:var(--text-1);height:2px;width:100%}
a{color:var(--accent);text-decoration:underline}
.kicker{color:var(--accent);font-style:italic;text-transform:none;letter-spacing:0;font-weight:400}
assets/animations/fx/neural-net.js
(function(){
window.HPX = window.HPX || {};
window.HPX['neural-net'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
const layers = [4,6,6,3];
let nodes = [], edges = [], pulses = [];
const layout = () => {
nodes = [];
const pad = 40;
const cw = k.w - pad*2, ch = k.h - pad*2;
for (let L=0; L<layers.length; L++){
const x = pad + (cw * L / (layers.length-1));
const n = layers[L];
for (let i=0;i<n;i++){
const y = pad + (ch * (i+0.5) / n);
nodes.push({x,y,L,i});
}
}
edges = [];
for (let L=0; L<layers.length-1; L++){
const a = nodes.filter(n=>n.L===L), b = nodes.filter(n=>n.L===L+1);
for (const x of a) for (const y of b) edges.push([nodes.indexOf(x),nodes.indexOf(y)]);
}
};
layout();
let lw=k.w, lh=k.h, last=0;
const stop = U.loop((t) => {
if (k.w!==lw||k.h!==lh){ layout(); lw=k.w; lh=k.h; }
ctx.clearRect(0,0,k.w,k.h);
ctx.strokeStyle = 'rgba(160,160,200,0.22)'; ctx.lineWidth=1;
for (const [i,j] of edges){
const a=nodes[i], b=nodes[j];
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
}
if (t - last > 0.25){
last = t;
const starts = nodes.filter(n=>n.L===0);
const s = starts[(Math.random()*starts.length)|0];
pulses.push({node:s, L:0, t:0});
}
pulses = pulses.filter(p => p.L < layers.length-1);
for (const p of pulses){
p.t += 0.03;
if (p.t >= 1){
const next = nodes.filter(n=>n.L===p.L+1);
p.node2 = next[(Math.random()*next.length)|0];
if (!p._started){ p._started = true; }
}
}
// animate progression
for (const p of pulses){
if (!p.target){
const next = nodes.filter(n=>n.L===p.L+1);
p.target = next[(Math.random()*next.length)|0];
}
p.t += 0.04;
const a = p.node, b = p.target;
const x = a.x + (b.x-a.x)*Math.min(1,p.t);
const y = a.y + (b.y-a.y)*Math.min(1,p.t);
ctx.fillStyle = ac2;
ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill();
if (p.t >= 1){ p.node = b; p.target=null; p.L++; p.t=0; }
}
for (const n of nodes){
ctx.fillStyle = ac;
ctx.beginPath(); ctx.arc(n.x,n.y,6,0,Math.PI*2); ctx.fill();
ctx.strokeStyle = ac2; ctx.lineWidth=1.5;
ctx.beginPath(); ctx.arc(n.x,n.y,8,0,Math.PI*2); ctx.stroke();
}
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/animations/fx/orbit-ring.js
(function(){
window.HPX = window.HPX || {};
window.HPX['orbit-ring'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const pal = U.palette(el);
const rings = [
{r:40, n:3, sp:1.2, c:pal[0]},
{r:75, n:5, sp:0.8, c:pal[1]},
{r:110, n:8, sp:-0.6, c:pal[2]},
{r:145, n:12, sp:0.4, c:pal[3]},
{r:180, n:16, sp:-0.3, c:pal[4]}
];
const stop = U.loop((t) => {
ctx.clearRect(0,0,k.w,k.h);
const cx=k.w/2, cy=k.h/2;
// radial glow
const g = ctx.createRadialGradient(cx,cy,0,cx,cy,210);
g.addColorStop(0,'rgba(124,92,255,0.25)');
g.addColorStop(1,'rgba(0,0,0,0)');
ctx.fillStyle = g; ctx.fillRect(0,0,k.w,k.h);
for (const R of rings){
ctx.strokeStyle = 'rgba(200,200,230,0.2)'; ctx.lineWidth=1;
ctx.beginPath(); ctx.arc(cx,cy,R.r,0,Math.PI*2); ctx.stroke();
for (let i=0;i<R.n;i++){
const a = (i/R.n)*Math.PI*2 + t*R.sp;
const x = cx + Math.cos(a)*R.r;
const y = cy + Math.sin(a)*R.r;
ctx.fillStyle = R.c;
ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill();
}
}
ctx.fillStyle = '#fff';
ctx.beginPath(); ctx.arc(cx,cy,5,0,Math.PI*2); ctx.fill();
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/themes/aurora.css
/* theme: aurora — 极光渐变 */
:root{
--bg:#06091c;--bg-soft:#0a1130;--surface:rgba(255,255,255,.05);--surface-2:rgba(255,255,255,.08);
--border:rgba(180,220,255,.14);--border-strong:rgba(180,220,255,.28);
--text-1:#e8f0ff;--text-2:#b4c4e4;--text-3:#6a7a9e;
--accent:#5ef2c6;--accent-2:#7aa2ff;--accent-3:#c984ff;
--good:#5ef2c6;--warn:#ffd27a;--bad:#ff8ab0;
--grad:linear-gradient(135deg,#5ef2c6,#7aa2ff 50%,#c984ff);
--grad-soft:linear-gradient(135deg,rgba(94,242,198,.2),rgba(201,132,255,.2));
--radius:20px;--radius-sm:14px;--radius-lg:28px;
--shadow:0 20px 60px rgba(0,0,0,.4),inset 0 1px 0 rgba(255,255,255,.08);
--shadow-lg:0 30px 80px rgba(0,0,0,.55);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
body{background:
radial-gradient(60% 50% at 20% 10%,rgba(94,242,198,.35),transparent 70%),
radial-gradient(55% 50% at 80% 20%,rgba(122,162,255,.32),transparent 70%),
radial-gradient(70% 60% at 50% 100%,rgba(201,132,255,.3),transparent 70%),
#06091c}
.card{backdrop-filter:blur(24px) saturate(160%);-webkit-backdrop-filter:blur(24px) saturate(160%)}
assets/themes/arctic-cool.css
/* theme: arctic-cool — 冷色调 蓝/青/石板灰 */
:root{
--bg:#f2f6fb;--bg-soft:#e7eef7;--surface:#ffffff;--surface-2:#edf3fa;
--border:rgba(40,70,110,.12);--border-strong:rgba(40,70,110,.24);
--text-1:#0e1f33;--text-2:#3a5778;--text-3:#6b819b;
--accent:#1e6fb0;--accent-2:#17b1b1;--accent-3:#6f8aa6;
--good:#1aaf84;--warn:#d19030;--bad:#c5485a;
--grad:linear-gradient(135deg,#1e6fb0,#17b1b1 60%,#5fb9d6);
--grad-soft:linear-gradient(135deg,#e7eef7,#dff3f3);
--radius:14px;--radius-sm:10px;--radius-lg:22px;
--shadow:0 10px 28px rgba(40,70,110,.12);
--shadow-lg:0 24px 60px rgba(40,70,110,.18);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/animations/fx/sparkle-trail.js
(function(){
window.HPX = window.HPX || {};
window.HPX['sparkle-trail'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
k.c.style.pointerEvents = 'none';
el.style.cursor = 'crosshair';
const pal = U.palette(el);
let sparks = [];
const onMove = (e) => {
const r = el.getBoundingClientRect();
const x = e.clientX - r.left, y = e.clientY - r.top;
for (let i=0;i<3;i++){
sparks.push({
x, y,
vx: U.rand(-60,60), vy: U.rand(-80,20),
life: 1, c: pal[(Math.random()*pal.length)|0],
r: U.rand(1.5,3.5)
});
}
};
// auto-wiggle if no mouse moves
let auto = true, autoT = 0;
const onAny = () => { auto = false; };
el.addEventListener('pointermove', onMove);
el.addEventListener('pointerenter', onAny);
const stop = U.loop(() => {
ctx.fillStyle = 'rgba(0,0,0,0.15)';
ctx.fillRect(0,0,k.w,k.h);
if (auto){
autoT += 0.04;
const x = k.w/2 + Math.cos(autoT)*k.w*0.3;
const y = k.h/2 + Math.sin(autoT*1.3)*k.h*0.3;
for (let i=0;i<3;i++){
sparks.push({
x, y,
vx: U.rand(-60,60), vy: U.rand(-80,20),
life: 1, c: pal[(Math.random()*pal.length)|0],
r: U.rand(1.5,3.5)
});
}
}
const dt = 1/60;
sparks = sparks.filter(s => s.life > 0);
for (const s of sparks){
s.vy += 160*dt;
s.x += s.vx*dt; s.y += s.vy*dt;
s.life -= 0.018;
ctx.globalAlpha = Math.max(0, s.life);
ctx.fillStyle = s.c;
ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
});
return { stop(){
el.removeEventListener('pointermove', onMove);
el.removeEventListener('pointerenter', onAny);
el.style.cursor = '';
stop(); k.destroy();
}};
};
})();
assets/themes/corporate-clean.css
/* theme: corporate-clean — 企业商务 */
:root{
--bg:#ffffff;--bg-soft:#f5f7fa;--surface:#ffffff;--surface-2:#f0f3f7;
--border:rgba(10,37,64,.12);--border-strong:rgba(10,37,64,.28);
--text-1:#0a2540;--text-2:#425466;--text-3:#8898aa;
--accent:#0a2540;--accent-2:#1d4ed8;--accent-3:#64748b;
--good:#0e9f6e;--warn:#d97706;--bad:#dc2626;
--grad:linear-gradient(135deg,#0a2540,#1d4ed8);
--grad-soft:linear-gradient(135deg,#f0f4fb,#e4ecf7);
--radius:6px;--radius-sm:4px;--radius-lg:10px;
--shadow:0 1px 3px rgba(10,37,64,.08),0 4px 12px rgba(10,37,64,.05);
--shadow-lg:0 4px 12px rgba(10,37,64,.1),0 16px 40px rgba(10,37,64,.08);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-display:'Inter','Noto Sans SC',sans-serif;
}
.card{border:1px solid var(--border)}
.divider-accent{background:var(--accent);height:3px;width:56px}
.kicker{color:var(--accent-2)}
h1.title,h2.title,.h1,.h2{font-weight:700;color:var(--accent)}
assets/themes/catppuccin-mocha.css
/* theme: catppuccin-mocha — catppuccin 深 */
:root{
--bg:#1e1e2e;--bg-soft:#181825;--surface:#313244;--surface-2:#45475a;
--border:rgba(205,214,244,.12);--border-strong:rgba(205,214,244,.24);
--text-1:#cdd6f4;--text-2:#a6adc8;--text-3:#7f849c;
--accent:#cba6f7;--accent-2:#89b4fa;--accent-3:#f5c2e7;
--good:#a6e3a1;--warn:#f9e2af;--bad:#f38ba8;
--grad:linear-gradient(135deg,#cba6f7,#89b4fa 50%,#94e2d5);
--grad-soft:linear-gradient(135deg,#313244,#45475a);
--radius:14px;--radius-sm:10px;--radius-lg:22px;
--shadow:0 10px 30px rgba(0,0,0,.35);
--shadow-lg:0 24px 60px rgba(0,0,0,.5);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/animations/fx/starfield.js
(function(){
window.HPX = window.HPX || {};
window.HPX['starfield'] = function(el){
const U = window.HPX._u;
const k = U.canvas(el), ctx = k.ctx;
const tx = U.text(el, '#ffffff');
const N = 260;
const stars = Array.from({length:N}, () => ({
x: U.rand(-1,1), y: U.rand(-1,1), z: Math.random()
}));
const stop = U.loop(() => {
ctx.fillStyle = 'rgba(0,0,0,0.25)';
ctx.fillRect(0,0,k.w,k.h);
const cx = k.w/2, cy = k.h/2;
for (const s of stars){
s.z -= 0.006;
if (s.z <= 0.02) { s.x = U.rand(-1,1); s.y = U.rand(-1,1); s.z = 1; }
const px = cx + (s.x/s.z)*cx;
const py = cy + (s.y/s.z)*cy;
if (px<0||py<0||px>k.w||py>k.h) continue;
const r = (1-s.z)*2.4;
ctx.globalAlpha = 1-s.z;
ctx.fillStyle = tx;
ctx.beginPath(); ctx.arc(px,py,r,0,Math.PI*2); ctx.fill();
}
ctx.globalAlpha = 1;
});
return { stop(){ stop(); k.destroy(); } };
};
})();
assets/themes/cyberpunk-neon.css
/* theme: cyberpunk-neon — 赛博朋克霓虹 */
:root{
--bg:#000000;--bg-soft:#0a0a12;--surface:#0f0f1a;--surface-2:#14141f;
--border:rgba(255,0,170,.25);--border-strong:rgba(0,240,255,.55);
--text-1:#f5f7ff;--text-2:#b4b8d4;--text-3:#6b6e8a;
--accent:#ff2bd6;--accent-2:#00f0ff;--accent-3:#f9f871;
--good:#39ff14;--warn:#f9f871;--bad:#ff2bd6;
--grad:linear-gradient(135deg,#ff2bd6,#7a00ff 50%,#00f0ff);
--grad-soft:linear-gradient(135deg,rgba(255,43,214,.18),rgba(0,240,255,.18));
--radius:6px;--radius-sm:3px;--radius-lg:10px;
--shadow:0 0 0 1px rgba(255,43,214,.35),0 0 24px rgba(255,43,214,.35),0 0 48px rgba(0,240,255,.18);
--shadow-lg:0 0 0 1px rgba(0,240,255,.5),0 0 40px rgba(0,240,255,.45),0 0 80px rgba(255,43,214,.3);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-display:'JetBrains Mono','IBM Plex Mono',monospace;
}
body{background:
radial-gradient(ellipse at 15% 0%,rgba(255,43,214,.22),transparent 60%),
radial-gradient(ellipse at 85% 100%,rgba(0,240,255,.2),transparent 60%),
#000}
h1.title,h2.title,.h1,.h2{text-shadow:0 0 12px rgba(255,43,214,.6),0 0 30px rgba(0,240,255,.35)}
.kicker{color:var(--accent-2);text-shadow:0 0 8px rgba(0,240,255,.6)}
.card{background:rgba(15,15,26,.72);backdrop-filter:blur(8px)}
.divider-accent{background:var(--grad);box-shadow:0 0 12px var(--accent)}
assets/themes/dracula.css
/* theme: dracula — dracula 深色 */
:root{
--bg:#282a36;--bg-soft:#21222c;--surface:#343746;--surface-2:#44475a;
--border:rgba(248,248,242,.12);--border-strong:rgba(248,248,242,.24);
--text-1:#f8f8f2;--text-2:#bdbde0;--text-3:#6272a4;
--accent:#bd93f9;--accent-2:#ff79c6;--accent-3:#8be9fd;
--good:#50fa7b;--warn:#f1fa8c;--bad:#ff5555;
--grad:linear-gradient(135deg,#bd93f9,#ff79c6 55%,#8be9fd);
--grad-soft:linear-gradient(135deg,#343746,#44475a);
--radius:12px;--radius-sm:8px;--radius-lg:18px;
--shadow:0 10px 30px rgba(0,0,0,.4);
--shadow-lg:0 22px 60px rgba(0,0,0,.55);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/editorial-serif.css
/* theme: editorial-serif — 杂志风衬线,高级 */
:root{
--bg:#faf7f2;--bg-soft:#f3efe6;--surface:#ffffff;--surface-2:#f7f2e8;
--border:rgba(40,28,18,.12);--border-strong:rgba(40,28,18,.24);
--text-1:#1b1410;--text-2:#5c4a3e;--text-3:#8a7868;
--accent:#8a2a1c;--accent-2:#c97a4a;--accent-3:#1b1410;
--good:#3f7d4f;--warn:#b07a1f;--bad:#8a2a1c;
--grad:linear-gradient(135deg,#8a2a1c,#c97a4a);
--grad-soft:linear-gradient(135deg,#faf7f2,#f3efe6);
--radius:4px;--radius-sm:2px;--radius-lg:8px;
--shadow:0 2px 12px rgba(40,28,18,.06);
--shadow-lg:0 20px 50px rgba(40,28,18,.14);
--font-sans:'Playfair Display','Noto Serif SC',serif;
--font-display:'Playfair Display','Noto Serif SC',serif;
--font-serif:'Playfair Display','Noto Serif SC',serif;
--letter-tight:-.02em;
}
.h1,.h2,h1.title,h2.title{font-style:italic;font-weight:600}
assets/themes/glassmorphism.css
/* theme: glassmorphism — 毛玻璃 */
:root{
--bg:#0b1024;--bg-soft:#0e1530;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
--border:rgba(255,255,255,.14);--border-strong:rgba(255,255,255,.28);
--text-1:#f2f4ff;--text-2:#c3c8e6;--text-3:#8287a8;
--accent:#7dd3fc;--accent-2:#c084fc;--accent-3:#f0abfc;
--good:#86efac;--warn:#fde68a;--bad:#fca5a5;
--grad:linear-gradient(135deg,#7dd3fc,#c084fc 55%,#f0abfc);
--grad-soft:linear-gradient(135deg,rgba(125,211,252,.18),rgba(192,132,252,.18));
--radius:22px;--radius-sm:14px;--radius-lg:30px;
--shadow:0 20px 60px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.12);
--shadow-lg:0 30px 80px rgba(0,0,0,.5);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
body{background:
radial-gradient(60% 60% at 20% 20%,rgba(125,211,252,.3),transparent 60%),
radial-gradient(50% 50% at 80% 30%,rgba(192,132,252,.28),transparent 60%),
radial-gradient(60% 60% at 60% 90%,rgba(240,171,252,.25),transparent 60%),
#0b1024}
.card{backdrop-filter:blur(28px) saturate(180%);-webkit-backdrop-filter:blur(28px) saturate(180%);
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.18)}
assets/themes/gruvbox-dark.css
/* theme: gruvbox-dark */
:root{
--bg:#282828;--bg-soft:#1d2021;--surface:#3c3836;--surface-2:#504945;
--border:rgba(235,219,178,.14);--border-strong:rgba(235,219,178,.28);
--text-1:#ebdbb2;--text-2:#d5c4a1;--text-3:#928374;
--accent:#fabd2f;--accent-2:#fe8019;--accent-3:#b8bb26;
--good:#b8bb26;--warn:#fabd2f;--bad:#fb4934;
--grad:linear-gradient(135deg,#fe8019,#fabd2f 55%,#b8bb26);
--grad-soft:linear-gradient(135deg,#3c3836,#504945);
--radius:6px;--radius-sm:4px;--radius-lg:12px;
--shadow:0 10px 30px rgba(0,0,0,.5);
--shadow-lg:0 24px 60px rgba(0,0,0,.65);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/japanese-minimal.css
/* theme: japanese-minimal — 和风极简 */
:root{
--bg:#fafaf5;--bg-soft:#f2f0e6;--surface:#ffffff;--surface-2:#f5f3ea;
--border:rgba(40,30,20,.1);--border-strong:rgba(40,30,20,.3);
--text-1:#1a1a18;--text-2:#5c564c;--text-3:#9c958a;
--accent:#d93a2a;--accent-2:#1a1a18;--accent-3:#c9a961;
--good:#4a6b3e;--warn:#c9a961;--bad:#d93a2a;
--grad:linear-gradient(135deg,#d93a2a,#1a1a18);
--grad-soft:linear-gradient(135deg,#faeae6,#f5f3ea);
--radius:0px;--radius-sm:0px;--radius-lg:2px;
--shadow:none;
--shadow-lg:0 1px 0 rgba(40,30,20,.12);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-serif:'Noto Serif SC','Playfair Display',serif;
--font-display:'Noto Serif SC','Playfair Display',serif;
}
h1.title,h2.title,.h1,.h2{font-weight:500;letter-spacing:.04em}
.card{border:1px solid var(--border);box-shadow:none;padding:36px 40px}
.divider-accent{background:var(--accent);height:2px;width:48px}
.kicker{color:var(--accent);letter-spacing:.2em}
.slide{padding:96px 128px}
assets/themes/bauhaus.css
/* theme: bauhaus — 几何+原色 */
:root{
--bg:#f4efe3;--bg-soft:#e8e2d1;--surface:#ffffff;--surface-2:#f4efe3;
--border:#111111;--border-strong:#111111;
--text-1:#111111;--text-2:#333333;--text-3:#666666;
--accent:#e03c27;--accent-2:#f4c430;--accent-3:#1d4eaf;
--good:#1b8c3c;--warn:#f4c430;--bad:#e03c27;
--grad:linear-gradient(135deg,#e03c27 0 33%,#f4c430 33% 66%,#1d4eaf 66% 100%);
--grad-soft:linear-gradient(135deg,#f4efe3,#e8e2d1);
--radius:0;--radius-sm:0;--radius-lg:0;
--shadow:4px 4px 0 #111;--shadow-lg:8px 8px 0 #111;
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
--font-display:'Archivo Black',sans-serif;
--letter-tight:-.03em;
}
.card{border:2px solid #111}
assets/themes/blueprint.css
/* theme: blueprint — 蓝图工程 */
:root{
--bg:#0b3a6f;--bg-soft:#0a3260;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
--border:rgba(190,220,255,.3);--border-strong:rgba(190,220,255,.55);
--text-1:#e8f3ff;--text-2:#b8d4f0;--text-3:#7da8cf;
--accent:#ffffff;--accent-2:#aee1ff;--accent-3:#ffd27a;
--good:#8ef0a6;--warn:#ffd27a;--bad:#ff8a96;
--grad:linear-gradient(135deg,#ffffff,#aee1ff);
--grad-soft:linear-gradient(135deg,#0a3260,#0b3a6f);
--radius:2px;--radius-sm:2px;--radius-lg:4px;
--shadow:none;--shadow-lg:0 16px 40px rgba(0,0,0,.3);
--font-sans:'JetBrains Mono','IBM Plex Mono',monospace;
--font-display:'JetBrains Mono',monospace;
}
body{background:
linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px) 0 0/40px 40px,
linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px) 0 0/40px 40px,
#0b3a6f}
.card{border:1px dashed rgba(190,220,255,.45);background:rgba(255,255,255,.04)}
assets/themes/memphis-pop.css
/* theme: memphis-pop — 孟菲斯波普 */
:root{
--bg:#fef6e8;--bg-soft:#fdebc7;--surface:#ffffff;--surface-2:#fff1d1;
--border:#111111;--border-strong:#111111;
--text-1:#111111;--text-2:#333333;--text-3:#666666;
--accent:#ff3d8b;--accent-2:#37c2d7;--accent-3:#ffcc00;
--good:#6ac04c;--warn:#ffcc00;--bad:#ff3d8b;
--grad:linear-gradient(135deg,#ff3d8b,#ffcc00 50%,#37c2d7);
--grad-soft:linear-gradient(135deg,#fdebc7,#fff1d1);
--radius:10px;--radius-sm:6px;--radius-lg:18px;
--shadow:5px 5px 0 #111;--shadow-lg:9px 9px 0 #111;
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
--font-display:'Archivo Black',sans-serif;
}
.card{border:2.5px solid #111}
body{background-image:
radial-gradient(circle at 10% 20%,#ff3d8b 3px,transparent 4px),
radial-gradient(circle at 80% 40%,#37c2d7 3px,transparent 4px),
radial-gradient(circle at 30% 80%,#ffcc00 3px,transparent 4px);
background-size:200px 200px,220px 220px,260px 260px}
assets/themes/midcentury.css
/* theme: midcentury — 世纪中期现代 */
:root{
--bg:#f3ead8;--bg-soft:#ebdfc4;--surface:#f9f2e0;--surface-2:#e8dcbe;
--border:rgba(60,40,20,.18);--border-strong:rgba(60,40,20,.4);
--text-1:#201810;--text-2:#5a4830;--text-3:#9a8868;
--accent:#d4902a;--accent-2:#2a7a7f;--accent-3:#c7502a;
--good:#5a7a3a;--warn:#d4902a;--bad:#c7502a;
--grad:linear-gradient(135deg,#d4902a,#c7502a 55%,#2a7a7f);
--grad-soft:linear-gradient(135deg,#f4e0b6,#eac7a8);
--radius:2px;--radius-sm:0px;--radius-lg:4px;
--shadow:4px 4px 0 rgba(40,25,10,.12);
--shadow-lg:6px 6px 0 rgba(40,25,10,.2),0 10px 24px rgba(40,25,10,.14);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-display:'Playfair Display','Noto Serif SC',serif;
}
.card{border:1.5px solid var(--border-strong)}
.divider-accent{background:var(--accent-3);height:4px;width:80px}
.kicker{color:var(--accent-2)}
h1.title,.h1{color:var(--accent-3)}
assets/themes/catppuccin-latte.css
/* theme: catppuccin-latte — catppuccin 浅 */
:root{
--bg:#eff1f5;--bg-soft:#e6e9ef;--surface:#ffffff;--surface-2:#eef0f4;
--border:rgba(76,79,105,.14);--border-strong:rgba(76,79,105,.28);
--text-1:#4c4f69;--text-2:#6c6f85;--text-3:#9ca0b0;
--accent:#8839ef;--accent-2:#1e66f5;--accent-3:#ea76cb;
--good:#40a02b;--warn:#df8e1d;--bad:#d20f39;
--grad:linear-gradient(135deg,#8839ef,#1e66f5 50%,#04a5e5);
--grad-soft:linear-gradient(135deg,#eff1f5,#e6e9ef);
--radius:14px;--radius-sm:10px;--radius-lg:22px;
--shadow:0 8px 24px rgba(76,79,105,.1);
--shadow-lg:0 20px 56px rgba(76,79,105,.16);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/minimal-white.css
/* theme: minimal-white — 极简白,克制高级 */
:root{
--bg:#ffffff;--bg-soft:#fafafa;--surface:#ffffff;--surface-2:#f5f5f6;
--border:rgba(17,18,22,.08);--border-strong:rgba(17,18,22,.16);
--text-1:#0c0d10;--text-2:#55596a;--text-3:#9ca1b0;
--accent:#111216;--accent-2:#3b3f4a;--accent-3:#6b6f7a;
--good:#1aaf6c;--warn:#c98500;--bad:#c13a3a;
--grad:linear-gradient(135deg,#111216,#3b3f4a);
--grad-soft:linear-gradient(135deg,#f5f5f6,#ffffff);
--radius:14px;--radius-sm:8px;--radius-lg:22px;
--shadow:0 1px 2px rgba(17,18,22,.04),0 8px 24px rgba(17,18,22,.06);
--shadow-lg:0 20px 60px rgba(17,18,22,.1);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-display:'Inter','Noto Sans SC',sans-serif;
--letter-tight:-.035em;
}
assets/themes/nord.css
/* theme: nord */
:root{
--bg:#2e3440;--bg-soft:#272b35;--surface:#3b4252;--surface-2:#434c5e;
--border:rgba(236,239,244,.12);--border-strong:rgba(236,239,244,.24);
--text-1:#eceff4;--text-2:#d8dee9;--text-3:#7b8394;
--accent:#88c0d0;--accent-2:#81a1c1;--accent-3:#b48ead;
--good:#a3be8c;--warn:#ebcb8b;--bad:#bf616a;
--grad:linear-gradient(135deg,#88c0d0,#81a1c1 50%,#b48ead);
--grad-soft:linear-gradient(135deg,#3b4252,#434c5e);
--radius:12px;--radius-sm:8px;--radius-lg:20px;
--shadow:0 10px 30px rgba(0,0,0,.35);
--shadow-lg:0 22px 60px rgba(0,0,0,.5);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/pitch-deck-vc.css
/* theme: pitch-deck-vc — YC 风融资 pitch */
:root{
--bg:#ffffff;--bg-soft:#fafbfc;--surface:#ffffff;--surface-2:#f5f7fa;
--border:rgba(20,30,50,.1);--border-strong:rgba(20,30,50,.22);
--text-1:#0b0d12;--text-2:#4a5270;--text-3:#8b93a8;
--accent:#0070f3;--accent-2:#7928ca;--accent-3:#ff4ecb;
--good:#0cce6b;--warn:#f5a524;--bad:#ee0000;
--grad:linear-gradient(135deg,#0070f3,#7928ca);
--grad-soft:linear-gradient(135deg,#e8f0ff,#f3e8ff);
--radius:14px;--radius-sm:8px;--radius-lg:22px;
--shadow:0 2px 8px rgba(20,30,50,.06),0 12px 32px rgba(20,30,50,.06);
--shadow-lg:0 8px 24px rgba(20,30,50,.1),0 30px 80px rgba(20,30,50,.1);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-display:'Inter','Noto Sans SC',sans-serif;
}
.slide{padding:88px 120px}
h1.title,.h1{font-weight:800;letter-spacing:-.035em}
h1.title .gradient-text,.h1 .gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.card{border:1px solid var(--border)}
.divider-accent{background:var(--grad);height:4px;width:64px;border-radius:2px}
.kicker{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
assets/themes/rainbow-gradient.css
/* theme: rainbow-gradient — 彩虹渐变点缀(白底) */
:root{
--bg:#ffffff;--bg-soft:#f8f8fb;--surface:#ffffff;--surface-2:#f4f4f8;
--border:rgba(20,20,40,.08);--border-strong:rgba(20,20,40,.2);
--text-1:#0c0d10;--text-2:#4d5162;--text-3:#9096a8;
--accent:#ff4d8b;--accent-2:#7a5cff;--accent-3:#36b6ff;
--good:#1aaf6c;--warn:#f5a524;--bad:#e0445a;
--grad:linear-gradient(90deg,#ff0080,#ff4d00,#ff9900,#ffe600,#00c853,#0091ea,#6200ea,#ff0080);
--grad-soft:linear-gradient(135deg,#fff,#f8f8fb);
--radius:16px;--radius-sm:10px;--radius-lg:24px;
--shadow:0 12px 32px rgba(124,92,255,.1);
--shadow-lg:0 24px 60px rgba(124,92,255,.18);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
.gradient-text{background-size:200% auto;animation:rbflow 6s linear infinite}
@keyframes rbflow{to{background-position:200% 0}}
assets/themes/engineering-whiteprint.css
/* theme: engineering-whiteprint — 工程白图 */
:root{
--bg:#ffffff;--bg-soft:#f8fafc;--surface:#ffffff;--surface-2:#f4f7fb;
--border:rgba(10,30,70,.22);--border-strong:#0a1e46;
--text-1:#0a1e46;--text-2:#3a4a6a;--text-3:#8090a8;
--accent:#0a1e46;--accent-2:#1e5ac4;--accent-3:#c42a10;
--good:#1a6a3a;--warn:#c47a10;--bad:#c42a10;
--grad:linear-gradient(135deg,#0a1e46,#1e5ac4);
--grad-soft:linear-gradient(135deg,#eaf0fb,#f4f7fb);
--radius:0px;--radius-sm:0px;--radius-lg:0px;
--shadow:none;
--shadow-lg:0 0 0 1px var(--border-strong);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-mono:'JetBrains Mono','IBM Plex Mono',monospace;
--font-display:'JetBrains Mono','Inter',monospace;
}
body{background:
repeating-linear-gradient(0deg,rgba(10,30,70,.07) 0 1px,transparent 1px 40px),
repeating-linear-gradient(90deg,rgba(10,30,70,.07) 0 1px,transparent 1px 40px),
#ffffff}
.card{border:1px solid var(--border-strong);box-shadow:none;background:rgba(255,255,255,.85)}
.divider{background:var(--border-strong);height:1px}
.divider-accent{background:var(--border-strong);height:1px;width:100%}
.kicker{font-family:var(--font-mono);color:var(--accent-2);letter-spacing:.18em}
h1.title,h2.title,.h1,.h2{font-weight:600}
.pill{font-family:var(--font-mono);border:1px solid var(--border-strong);border-radius:0}
assets/themes/magazine-bold.css
/* theme: magazine-bold — 杂志大标题 */
:root{
--bg:#f5efe2;--bg-soft:#ebe4d2;--surface:#fbf6e8;--surface-2:#ede5d0;
--border:rgba(10,10,10,.16);--border-strong:#0a0a0a;
--text-1:#0a0a0a;--text-2:#2a2a2a;--text-3:#6a6458;
--accent:#ea5a1a;--accent-2:#0a0a0a;--accent-3:#c42a10;
--good:#2a6a2a;--warn:#ea5a1a;--bad:#c42a10;
--grad:linear-gradient(135deg,#ea5a1a,#c42a10);
--grad-soft:linear-gradient(135deg,#fbe4d0,#f5d6c0);
--radius:0px;--radius-sm:0px;--radius-lg:2px;
--shadow:none;
--shadow-lg:6px 6px 0 var(--accent);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-serif:'Playfair Display','Noto Serif SC',Georgia,serif;
--font-display:'Playfair Display','Noto Serif SC',Georgia,serif;
}
h1.title,.h1{font-size:120px;line-height:.92;font-weight:900;letter-spacing:-.04em;font-family:var(--font-serif)}
h2.title,.h2{font-size:72px;font-weight:800;font-family:var(--font-serif)}
.card{border:1.5px solid var(--text-1)}
.divider-accent{background:var(--accent);height:6px;width:90px}
.kicker{color:var(--accent);text-transform:uppercase;font-weight:700;letter-spacing:.25em}
assets/themes/sharp-mono.css
/* theme: sharp-mono — 锐利黑白高对比 */
:root{
--bg:#ffffff;--bg-soft:#ffffff;--surface:#ffffff;--surface-2:#000000;
--border:#000000;--border-strong:#000000;
--text-1:#000000;--text-2:#1a1a1a;--text-3:#4a4a4a;
--accent:#000000;--accent-2:#000000;--accent-3:#ff2200;
--good:#008800;--warn:#ff9900;--bad:#ff0000;
--grad:linear-gradient(135deg,#000,#222);
--grad-soft:linear-gradient(135deg,#fff,#eee);
--radius:0;--radius-sm:0;--radius-lg:0;
--shadow:4px 4px 0 #000;--shadow-lg:8px 8px 0 #000;
--font-sans:'Archivo Black','Inter','Noto Sans SC',sans-serif;
--font-display:'Archivo Black',sans-serif;
--letter-tight:-.04em;
}
.h1,.h2,h1.title,h2.title{text-transform:uppercase}
.card{border:2px solid #000}
assets/themes/rose-pine.css
/* theme: rose-pine */
:root{
--bg:#191724;--bg-soft:#1f1d2e;--surface:#26233a;--surface-2:#2a2740;
--border:rgba(224,222,244,.12);--border-strong:rgba(224,222,244,.24);
--text-1:#e0def4;--text-2:#c4b8d8;--text-3:#6e6a86;
--accent:#ebbcba;--accent-2:#c4a7e7;--accent-3:#9ccfd8;
--good:#31748f;--warn:#f6c177;--bad:#eb6f92;
--grad:linear-gradient(135deg,#ebbcba,#c4a7e7 55%,#9ccfd8);
--grad-soft:linear-gradient(135deg,#26233a,#2a2740);
--radius:14px;--radius-sm:10px;--radius-lg:22px;
--shadow:0 10px 30px rgba(0,0,0,.4);
--shadow-lg:0 22px 58px rgba(0,0,0,.55);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/sunset-warm.css
/* theme: sunset-warm — 暖色调 橘/珊瑚/琥珀 */
:root{
--bg:#fff7ef;--bg-soft:#ffeedc;--surface:#ffffff;--surface-2:#fff2e0;
--border:rgba(120,60,20,.12);--border-strong:rgba(120,60,20,.22);
--text-1:#2a160a;--text-2:#6b4630;--text-3:#a28572;
--accent:#e36a2d;--accent-2:#f2a341;--accent-3:#d94860;
--good:#5ea35a;--warn:#f2a341;--bad:#d94860;
--grad:linear-gradient(135deg,#d94860,#e36a2d 50%,#f2a341);
--grad-soft:linear-gradient(135deg,#ffeedc,#ffe0d0);
--radius:18px;--radius-sm:12px;--radius-lg:28px;
--shadow:0 12px 32px rgba(227,106,45,.16);
--shadow-lg:0 24px 64px rgba(227,106,45,.22);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/terminal-green.css
/* theme: terminal-green — 绿屏终端 */
:root{
--bg:#030a04;--bg-soft:#041308;--surface:#0a1b10;--surface-2:#0d2614;
--border:rgba(0,255,120,.22);--border-strong:rgba(0,255,120,.42);
--text-1:#8cff9a;--text-2:#4bd17a;--text-3:#2f8a4d;
--accent:#00ff88;--accent-2:#67ffd0;--accent-3:#b6ff6b;
--good:#00ff88;--warn:#ffe066;--bad:#ff6464;
--grad:linear-gradient(135deg,#00ff88,#67ffd0);
--grad-soft:linear-gradient(135deg,#0a1b10,#0d2614);
--radius:4px;--radius-sm:2px;--radius-lg:8px;
--shadow:0 0 30px rgba(0,255,136,.15);
--shadow-lg:0 0 60px rgba(0,255,136,.28);
--font-sans:'JetBrains Mono','IBM Plex Mono',monospace;
--font-display:'JetBrains Mono',monospace;
--letter-tight:-.01em;
}
body{text-shadow:0 0 2px rgba(0,255,136,.5)}
.card{border:1px solid rgba(0,255,120,.3);background:rgba(10,27,16,.6)}
assets/themes/neo-brutalism.css
/* theme: neo-brutalism — 厚描边、硬阴影、明黄 */
:root{
--bg:#fffef0;--bg-soft:#fffbd0;--surface:#ffffff;--surface-2:#fff38a;
--border:#000000;--border-strong:#000000;
--text-1:#000000;--text-2:#222222;--text-3:#555555;
--accent:#ffd400;--accent-2:#ff5ca8;--accent-3:#3a7cff;
--good:#00b36b;--warn:#ff9900;--bad:#ff3a30;
--grad:linear-gradient(135deg,#ffd400,#ff5ca8);
--grad-soft:linear-gradient(135deg,#fffbd0,#fff);
--radius:6px;--radius-sm:4px;--radius-lg:10px;
--shadow:6px 6px 0 #000;--shadow-lg:10px 10px 0 #000;
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
--font-display:'Archivo Black','Space Grotesk',sans-serif;
--letter-tight:-.03em;
}
.card{border:3px solid #000}
.pill{border:2px solid #000;background:#ffd400;color:#000}
assets/themes/tokyo-night.css
/* theme: tokyo-night */
:root{
--bg:#1a1b26;--bg-soft:#16161e;--surface:#24283b;--surface-2:#2f334d;
--border:rgba(192,202,245,.12);--border-strong:rgba(192,202,245,.24);
--text-1:#c0caf5;--text-2:#a9b1d6;--text-3:#565f89;
--accent:#7aa2f7;--accent-2:#bb9af7;--accent-3:#7dcfff;
--good:#9ece6a;--warn:#e0af68;--bad:#f7768e;
--grad:linear-gradient(135deg,#7aa2f7,#bb9af7 55%,#f7768e);
--grad-soft:linear-gradient(135deg,#24283b,#2f334d);
--radius:12px;--radius-sm:8px;--radius-lg:20px;
--shadow:0 10px 30px rgba(0,0,0,.45);
--shadow-lg:0 24px 62px rgba(0,0,0,.6);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/vaporwave.css
/* theme: vaporwave — 蒸汽波 */
:root{
--bg:#1a0938;--bg-soft:#261050;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
--border:rgba(255,110,199,.28);--border-strong:rgba(0,245,255,.5);
--text-1:#fdf0ff;--text-2:#d4a8e8;--text-3:#8a6ba8;
--accent:#ff6ec7;--accent-2:#00f5ff;--accent-3:#ffd166;
--grad:linear-gradient(135deg,#ff6ec7 0%,#c94fff 35%,#00f5ff 100%);
--grad-soft:linear-gradient(135deg,rgba(255,110,199,.25),rgba(0,245,255,.25));
--radius:18px;--radius-sm:10px;--radius-lg:28px;
--shadow:0 20px 60px rgba(255,110,199,.2),0 0 1px rgba(0,245,255,.6);
--shadow-lg:0 30px 80px rgba(255,110,199,.3),0 0 2px rgba(0,245,255,.8);
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
--font-display:'Space Grotesk','Inter',sans-serif;
}
body{background:
linear-gradient(180deg,#1a0938 0%,#3a0f5c 45%,#7a1f6b 85%,#e85d9c 100%),
radial-gradient(ellipse at 50% 80%,rgba(0,245,255,.3),transparent 60%)}
h1.title,.h1{background:var(--grad);-webkit-background-clip:text;background-clip:text;
-webkit-text-fill-color:transparent;color:transparent}
.card{backdrop-filter:blur(18px)}
.divider-accent{background:var(--grad);height:4px;width:120px;box-shadow:0 0 20px var(--accent)}
assets/themes/swiss-grid.css
/* theme: swiss-grid — 瑞士网格,Helvetica 感 */
:root{
--bg:#ffffff;--bg-soft:#f4f4f4;--surface:#ffffff;--surface-2:#f4f4f4;
--border:#111111;--border-strong:#111111;
--text-1:#111111;--text-2:#444444;--text-3:#888888;
--accent:#d6001c;--accent-2:#111111;--accent-3:#888888;
--good:#0f8a2f;--warn:#d38a00;--bad:#d6001c;
--grad:linear-gradient(135deg,#d6001c,#111);
--grad-soft:linear-gradient(135deg,#f4f4f4,#fff);
--radius:0;--radius-sm:0;--radius-lg:0;
--shadow:none;--shadow-lg:none;
--font-sans:'Inter','Helvetica Neue',Helvetica,'Noto Sans SC',sans-serif;
--font-display:'Inter','Helvetica Neue',Helvetica,sans-serif;
--letter-tight:-.04em;
}
.card{border-top:2px solid #111;border-bottom:1px solid #111;border-left:none;border-right:none;box-shadow:none;background:#fff}
.slide{background-image:linear-gradient(90deg,rgba(0,0,0,.04) 1px,transparent 1px);background-size:calc(100%/12) 100%}
assets/themes/news-broadcast.css
/* theme: news-broadcast — 新闻播报 */
:root{
--bg:#ffffff;--bg-soft:#f4f4f4;--surface:#ffffff;--surface-2:#ececec;
--border:rgba(0,0,0,.14);--border-strong:#0a0a0a;
--text-1:#0a0a0a;--text-2:#3a3a3a;--text-3:#7a7a7a;
--accent:#e11d2d;--accent-2:#0a0a0a;--accent-3:#ffd100;
--good:#0e7c3a;--warn:#ffd100;--bad:#e11d2d;
--grad:linear-gradient(90deg,#e11d2d 0%,#e11d2d 100%);
--grad-soft:linear-gradient(135deg,#fde5e7,#f4f4f4);
--radius:0px;--radius-sm:0px;--radius-lg:2px;
--shadow:none;
--shadow-lg:0 4px 0 var(--accent);
--font-sans:'Oswald','Inter','Noto Sans SC',sans-serif;
--font-display:'Oswald','Inter','Noto Sans SC',sans-serif;
}
h1.title,h2.title,.h1,.h2{font-weight:700;text-transform:uppercase;letter-spacing:-.01em}
.card{border:2px solid var(--text-1);box-shadow:6px 6px 0 var(--accent)}
.divider-accent{background:var(--accent);height:6px;width:100%}
.kicker{background:var(--accent);color:#fff;padding:4px 12px;display:inline-block;letter-spacing:.15em}
.slide::before{content:"";position:absolute;left:0;top:0;bottom:0;width:8px;background:var(--accent);z-index:3}
assets/themes/retro-tv.css
/* theme: retro-tv — 复古显像管 */
:root{
--bg:#f5ecd7;--bg-soft:#efe4c6;--surface:#fbf5e2;--surface-2:#efe3c2;
--border:rgba(120,70,20,.22);--border-strong:rgba(120,70,20,.45);
--text-1:#2a1a08;--text-2:#6b4a22;--text-3:#a68656;
--accent:#e67e14;--accent-2:#c73a1f;--accent-3:#f2b544;
--good:#3e8940;--warn:#e67e14;--bad:#c73a1f;
--grad:linear-gradient(135deg,#c73a1f,#e67e14 55%,#f2b544);
--grad-soft:linear-gradient(135deg,#fde6c4,#fbd9a0);
--radius:10px;--radius-sm:6px;--radius-lg:16px;
--shadow:0 6px 0 rgba(80,40,0,.12),0 12px 28px rgba(80,40,0,.15);
--shadow-lg:0 10px 0 rgba(80,40,0,.15),0 24px 50px rgba(80,40,0,.2);
--font-sans:'Inter','Noto Sans SC',sans-serif;
--font-display:'Playfair Display','Noto Serif SC',serif;
}
body{background:
repeating-linear-gradient(0deg,rgba(80,40,0,.06) 0 2px,transparent 2px 4px),
radial-gradient(ellipse at center,#f7ecd0 0%,#e8d9b0 85%,#c9b888 100%)}
.slide::before{content:"";position:absolute;inset:0;pointer-events:none;
background:repeating-linear-gradient(0deg,rgba(0,0,0,.035) 0 2px,transparent 2px 4px);z-index:1}
.slide > *{position:relative;z-index:2}
h1.title,.h1{color:var(--accent-2)}
docs/readme/montage-animations.html
<!doctype html>
<html lang="en" data-theme="aurora">
<head>
<meta charset="utf-8">
<title>47 animations showcase</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" href="../../assets/themes/aurora.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{width:1920px;height:1080px;overflow:hidden}
body{background:#0a0a14;font-family:"PingFang SC","Noto Sans SC","Inter",-apple-system,sans-serif;color:#fff;padding:48px 56px 44px;display:flex;flex-direction:column;gap:28px}
.hdr{display:flex;align-items:flex-end;justify-content:space-between}
.hdr h2{font:900 48px/1 "Inter",sans-serif;letter-spacing:-.02em;color:#fff}
.hdr h2 b{display:inline-block;font-size:58px;padding-right:14px;background:linear-gradient(90deg,#ff4d8d,#f59e0b,#22d3ee);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.hdr .sub{font:600 16px/1 "JetBrains Mono","SF Mono",monospace;color:#aab0c0;letter-spacing:.1em;text-transform:uppercase}
.grid{flex:1;display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);gap:18px;min-height:0}
.cell{position:relative;border-radius:18px;overflow:hidden;border:1px solid rgba(255,255,255,.08);box-shadow:0 20px 56px rgba(0,0,0,.45);background:#050510;min-height:0}
.cell .fx-host{position:absolute;inset:0}
.cell .label{position:absolute;left:14px;bottom:12px;z-index:5;font:700 11px/1 "JetBrains Mono","SF Mono",monospace;letter-spacing:.1em;padding:6px 12px;border-radius:999px;background:rgba(10,10,20,.72);color:#fff;text-transform:uppercase;border:1px solid rgba(255,255,255,.14);backdrop-filter:blur(6px)}
.cell .kind{position:absolute;left:14px;top:12px;z-index:5;font:700 10px/1 "JetBrains Mono","SF Mono",monospace;letter-spacing:.14em;padding:5px 10px;border-radius:999px;background:rgba(255,255,255,.1);color:#fff;text-transform:uppercase;border:1px solid rgba(255,255,255,.16)}
</style>
</head>
<body>
<div class="hdr">
<h2><b>47</b>Animations — 27 CSS · 20 Canvas FX</h2>
<div class="sub">html-ppt · data-anim="…" / data-fx="…" · pick 8 canvas FX</div>
</div>
<div class="grid">
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="knowledge-graph" style="--accent:#7c5cff;--accent-2:#22d3ee;--text-1:#fff"></div><span class="label">knowledge-graph</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="neural-net" style="--accent:#22d3ee;--accent-2:#ff4d8d"></div><span class="label">neural-net</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="galaxy-swirl" style="--accent:#7c5cff;--accent-2:#ff4d8d"></div><span class="label">galaxy-swirl</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="constellation" style="--accent:#9fb4ff"></div><span class="label">constellation</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="matrix-rain"></div><span class="label">matrix-rain</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="starfield" style="--accent:#fff;--accent-2:#9fb4ff"></div><span class="label">starfield</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="firework" style="--accent:#ff4d8d;--accent-2:#22d3ee"></div><span class="label">firework</span></div>
<div class="cell"><span class="kind">data-fx</span><div class="fx-host" data-fx="particle-burst" style="--accent:#ff4d8d;--accent-2:#7c5cff"></div><span class="label">particle-burst</span></div>
</div>
<script src="../../assets/animations/fx/_util.js"></script>
<script src="../../assets/animations/fx/knowledge-graph.js"></script>
<script src="../../assets/animations/fx/neural-net.js"></script>
<script src="../../assets/animations/fx/galaxy-swirl.js"></script>
<script src="../../assets/animations/fx/constellation.js"></script>
<script src="../../assets/animations/fx/matrix-rain.js"></script>
<script src="../../assets/animations/fx/starfield.js"></script>
<script src="../../assets/animations/fx/firework.js"></script>
<script src="../../assets/animations/fx/particle-burst.js"></script>
<script>
(function(){
document.querySelectorAll('[data-fx]').forEach(el => {
const name = el.getAttribute('data-fx');
const fn = window.HPX && window.HPX[name];
if (typeof fn === 'function') {
try { fn(el, {}); } catch(e) { console.warn('[fx]', name, e); }
}
});
})();
</script>
</body>
</html>
docs/readme/montage-templates.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>14 full-deck templates showcase</title>
<style>
:root{--ink:#0b0b10;--muted:#6b6b78;--line:#e7e7ef}
*{box-sizing:border-box;margin:0;padding:0}
html,body{width:1920px;height:1080px;overflow:hidden}
body{background:#f6f7fa;font-family:"PingFang SC","Noto Sans SC","Inter",-apple-system,sans-serif;color:var(--ink);padding:48px 56px 44px;display:flex;flex-direction:column;gap:28px}
.hdr{display:flex;align-items:flex-end;justify-content:space-between}
.hdr h2{font:900 48px/1 "Inter",sans-serif;letter-spacing:-.02em}
.hdr h2 b{display:inline-block;font-size:58px;padding-right:14px;background:linear-gradient(90deg,#f59e0b,#ff4d8d);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.hdr .sub{font:600 16px/1 "JetBrains Mono","SF Mono",monospace;color:var(--muted);letter-spacing:.1em;text-transform:uppercase}
.grid{flex:1;display:grid;grid-template-columns:repeat(3,1fr);grid-template-rows:repeat(2,1fr);gap:20px;min-height:0}
.cell{position:relative;border-radius:20px;overflow:hidden;border:1px solid var(--line);box-shadow:0 20px 56px rgba(10,10,30,.12);background:#fff;min-height:0}
.cell.dark{background:#0a0a14;border-color:rgba(255,255,255,.08)}
.cell iframe{position:absolute;inset:0;width:1920px;height:1080px;border:0;pointer-events:none;transform-origin:top left}
.cell .label{position:absolute;left:16px;bottom:14px;z-index:5;font:700 12px/1 "JetBrains Mono","SF Mono",monospace;letter-spacing:.1em;padding:7px 14px;border-radius:999px;background:rgba(255,255,255,.94);color:#1a1a22;text-transform:uppercase;border:1px solid rgba(0,0,0,.06)}
.cell.dark .label{background:rgba(10,10,20,.78);color:#fff;border-color:rgba(255,255,255,.14)}
</style>
</head>
<body>
<div class="hdr">
<h2><b>14</b>Full-Deck Templates — complete world-views</h2>
<div class="sub">html-ppt · templates/full-decks/* · pick 6 of 14</div>
</div>
<div class="grid" id="grid"></div>
<script>
const DECKS = [
['graphify-dark-graph',true],
['xhs-post',false],
['hermes-cyber-terminal',true],
['knowledge-arch-blueprint',false],
['pitch-deck',false],
['xhs-white-editorial',false]
];
const grid = document.getElementById('grid');
DECKS.forEach(([name, dark]) => {
const cell = document.createElement('div');
cell.className = 'cell' + (dark ? ' dark' : '');
const ifr = document.createElement('iframe');
ifr.src = '../../templates/full-decks/' + name + '/index.html';
ifr.loading = 'eager';
cell.appendChild(ifr);
const lab = document.createElement('span');
lab.className = 'label';
lab.textContent = name;
cell.appendChild(lab);
grid.appendChild(cell);
});
function fit(){
document.querySelectorAll('.cell iframe').forEach(ifr => {
const c = ifr.parentElement;
const w = c.clientWidth, h = c.clientHeight;
const s = Math.min(w / 1920, h / 1080);
ifr.style.transform = 'scale('+s+')';
const sw = 1920*s, sh = 1080*s;
ifr.style.left = ((w - sw)/2) + 'px';
ifr.style.top = ((h - sh)/2) + 'px';
ifr.style.position = 'absolute';
});
}
window.addEventListener('resize', fit);
setTimeout(fit, 100);
setTimeout(fit, 500);
setTimeout(fit, 1500);
</script>
</body>
</html>
docs/readme/montage-layouts.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>31 layouts showcase</title>
<style>
:root{--ink:#0b0b10;--muted:#6b6b78;--line:#e7e7ef}
*{box-sizing:border-box;margin:0;padding:0}
html,body{width:1920px;height:1080px;overflow:hidden}
body{background:#f6f7fa;font-family:"PingFang SC","Noto Sans SC","Inter",-apple-system,sans-serif;color:var(--ink);padding:48px 56px 44px;display:flex;flex-direction:column;gap:28px}
.hdr{display:flex;align-items:flex-end;justify-content:space-between}
.hdr h2{font:900 48px/1 "Inter",sans-serif;letter-spacing:-.02em}
.hdr h2 b{display:inline-block;font-size:58px;padding-right:14px;background:linear-gradient(90deg,#2563eb,#22d3ee);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.hdr .sub{font:600 16px/1 "JetBrains Mono","SF Mono",monospace;color:var(--muted);letter-spacing:.1em;text-transform:uppercase}
.grid{flex:1;display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);gap:18px;min-height:0}
.cell{position:relative;border-radius:18px;overflow:hidden;border:1px solid var(--line);box-shadow:0 16px 48px rgba(10,10,30,.1);background:#fff;min-height:0}
.cell iframe{position:absolute;inset:0;width:1920px;height:1080px;border:0;pointer-events:none;transform-origin:top left}
.cell .label{position:absolute;left:14px;bottom:12px;z-index:5;font:700 11px/1 "JetBrains Mono","SF Mono",monospace;letter-spacing:.1em;padding:6px 12px;border-radius:999px;background:rgba(255,255,255,.94);color:#1a1a22;text-transform:uppercase;border:1px solid rgba(0,0,0,.06)}
</style>
</head>
<body>
<div class="hdr">
<h2><b>31</b>Layouts — batteries included, demo data bundled</h2>
<div class="sub">html-ppt · templates/single-page/*.html · pick 8 of 31</div>
</div>
<div class="grid" id="grid"></div>
<script>
const LAYOUTS = [
['kpi-grid','KPI Grid'],
['chart-bar','Chart · Bar'],
['timeline','Timeline'],
['mindmap','Mindmap'],
['flow-diagram','Flow Diagram'],
['roadmap','Roadmap'],
['pros-cons','Pros / Cons'],
['code','Code']
];
const grid = document.getElementById('grid');
LAYOUTS.forEach(([name,label]) => {
const cell = document.createElement('div');
cell.className = 'cell';
const ifr = document.createElement('iframe');
ifr.src = '../../templates/single-page/' + name + '.html';
ifr.loading = 'eager';
cell.appendChild(ifr);
const lab = document.createElement('span');
lab.className = 'label';
lab.textContent = label + ' · ' + name;
cell.appendChild(lab);
grid.appendChild(cell);
});
function fit(){
document.querySelectorAll('.cell iframe').forEach(ifr => {
const c = ifr.parentElement;
const w = c.clientWidth, h = c.clientHeight;
const s = Math.min(w / 1920, h / 1080);
ifr.style.transform = 'scale('+s+')';
const sw = 1920*s, sh = 1080*s;
ifr.style.left = ((w - sw)/2) + 'px';
ifr.style.top = ((h - sh)/2) + 'px';
ifr.style.position = 'absolute';
});
}
window.addEventListener('resize', fit);
setTimeout(fit, 80);
setTimeout(fit, 400);
setTimeout(fit, 1200);
</script>
</body>
</html>
assets/themes/soft-pastel.css
/* theme: soft-pastel — 柔和马卡龙 */
:root{
--bg:#fdf7fb;--bg-soft:#fbeef3;--surface:#ffffff;--surface-2:#fdf0f5;
--border:rgba(120,70,110,.12);--border-strong:rgba(120,70,110,.22);
--text-1:#3a1f33;--text-2:#6b4d62;--text-3:#a28a99;
--accent:#f49bb8;--accent-2:#b5d5f0;--accent-3:#f7d08a;
--good:#9dd9a3;--warn:#f7d08a;--bad:#ef9a9a;
--grad:linear-gradient(135deg,#f49bb8,#b5d5f0 55%,#c4a0e8);
--grad-soft:linear-gradient(135deg,#fbeef3,#eaf4fc);
--radius:24px;--radius-sm:16px;--radius-lg:32px;
--shadow:0 8px 28px rgba(244,155,184,.18);
--shadow-lg:0 24px 70px rgba(181,213,240,.3);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
assets/themes/solarized-light.css
/* theme: solarized-light */
:root{
--bg:#fdf6e3;--bg-soft:#eee8d5;--surface:#ffffff;--surface-2:#f5efd7;
--border:rgba(88,110,117,.2);--border-strong:rgba(88,110,117,.4);
--text-1:#073642;--text-2:#586e75;--text-3:#93a1a1;
--accent:#268bd2;--accent-2:#2aa198;--accent-3:#d33682;
--good:#859900;--warn:#b58900;--bad:#dc322f;
--grad:linear-gradient(135deg,#268bd2,#2aa198 50%,#859900);
--grad-soft:linear-gradient(135deg,#fdf6e3,#eee8d5);
--radius:10px;--radius-sm:6px;--radius-lg:16px;
--shadow:0 6px 20px rgba(88,110,117,.14);
--shadow-lg:0 18px 50px rgba(88,110,117,.24);
--font-sans:'Inter','Noto Sans SC',sans-serif;
}
examples/demo-deck/index.html
<!DOCTYPE html>
<html lang="zh-CN" data-theme="aurora">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>html-ppt · Demo Deck</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/aurora.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head>
<body data-themes="aurora,minimal-white,editorial-serif,tokyo-night,catppuccin-mocha,xiaohongshu-white,neo-brutalism,sunset-warm" data-theme-base="../../assets/themes/">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="deck-header"><span class="eyebrow">Tech sharing · 2026-04-15</span><span class="eyebrow">html-ppt</span></div>
<p class="kicker">Keynote · Demo</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in">做一份<span class="gradient-text">像杂志</span>一样的<br>技术分享稿</h1>
<p class="lede">24 主题 · 30 版式 · 25 动效 · 零构建</p>
<div class="row wrap mt-l" style="gap:8px">
<span class="pill pill-accent">tokens</span>
<span class="pill">keyboard first</span>
<span class="pill">PNG export</span>
<span class="pill">CN + EN</span>
</div>
<div class="deck-footer"><span class="dim2">lewis · sudolewis@gmail.com</span><span class="slide-number" data-current="1" data-total="8"></span></div>
<div class="notes">Hi,今天我给大家演示一下 html-ppt 这套演讲系统是怎么工作的。这份 demo 本身就是用它做出来的——每一张幻灯片都只是几行 HTML + 一个 class。</div>
</section>
<!-- 2. TOC -->
<section class="slide" data-title="Agenda">
<p class="kicker">Agenda</p>
<h2 class="h2">今天 10 分钟,讲三件事</h2>
<div class="grid g3 mt-l anim-stagger-list" data-anim-target>
<div class="card card-accent"><h4>① 为什么</h4><p class="dim">每次做 PPT 都在重复劳动,而这件事 99% 可以模板化。</p></div>
<div class="card card-accent"><h4>② 怎么做</h4><p class="dim">tokens + layouts + animations,三层分离。</p></div>
<div class="card card-accent"><h4>③ 效果</h4><p class="dim">同一份 deck,一键切 24 种主题。</p></div>
</div>
<div class="deck-footer"><span class="dim2">agenda</span><span class="slide-number" data-current="2" data-total="8"></span></div>
<div class="notes">三段式结构——Why / How / Result。这是最稳的讲法。</div>
</section>
<!-- 3. Big quote -->
<section class="slide center tc" data-title="Quote">
<div style="max-width:1000px">
<div class="serif" style="font-size:120px;line-height:.9;color:var(--accent);opacity:.7">"</div>
<blockquote class="serif anim-fade-up" data-anim="fade-up" style="font-size:52px;line-height:1.3;margin:-30px 0 18px;font-weight:600;font-style:italic">
好的演讲稿是写出来的,<br>不是「做」出来的。
</blockquote>
<p class="dim" style="font-size:18px;letter-spacing:.1em">— 每一个被 PPT 折磨过的人</p>
</div>
<div class="deck-footer"><span class="dim2">quote</span><span class="slide-number" data-current="3" data-total="8"></span></div>
<div class="notes">这里停一秒。让这句话自己说话。</div>
</section>
<!-- 4. Stat -->
<section class="slide center tc" data-title="Stat">
<div>
<p class="kicker">The result</p>
<div style="font-size:240px;line-height:1;font-weight:900">
<span class="counter gradient-text" data-to="92">0</span><span class="gradient-text">%</span>
</div>
<h3 class="mt-s">你花在 PPT 上的时间可以被省下</h3>
<p class="lede" style="margin:14px auto 0">10 份真实 deck 的平均测试数据。</p>
</div>
<div class="deck-footer"><span class="dim2">proof</span><span class="slide-number" data-current="4" data-total="8"></span></div>
<div class="notes">强调:数据来源——自己真实的 10 个 deck。</div>
</section>
<!-- 5. Two column -->
<section class="slide" data-title="Tokens">
<p class="kicker">How · 核心思路</p>
<h2 class="h2">把「看起来像什么」收进 <code>:root</code></h2>
<div class="grid g2 mt-l" style="align-items:start">
<div class="card anim-fade-left" data-anim="fade-left">
<h3>概念</h3>
<p class="dim">每一种视觉属性——颜色、字体、圆角、阴影——都变成语义变量。</p>
<ul class="mt-m">
<li><code>--text-1</code> / <code>--text-2</code> / <code>--text-3</code></li>
<li><code>--surface</code> / <code>--surface-2</code></li>
<li><code>--accent</code> / <code>--accent-2</code> / <code>--accent-3</code></li>
<li><code>--radius</code> / <code>--shadow</code> / <code>--grad</code></li>
</ul>
</div>
<div class="card anim-fade-right" data-anim="fade-right">
<h3>示例</h3>
<pre class="mono" style="font-size:13px;background:var(--surface-2);padding:16px;border-radius:var(--radius-sm);overflow:auto;margin:0">
/* assets/themes/aurora.css */
:root {
--bg: #06091c;
--text-1: #e8f0ff;
--accent: #5ef2c6;
--accent-2: #7aa2ff;
--accent-3: #c984ff;
--radius: 20px;
}</pre>
<p class="dim mt-m" style="font-size:13px">——整个 aurora 主题就这么大。</p>
</div>
</div>
<div class="deck-footer"><span class="dim2">how</span><span class="slide-number" data-current="5" data-total="8"></span></div>
<div class="notes">关键是:base.css 只认变量名,不认具体色值。换主题 = 换一份变量。</div>
</section>
<!-- 6. Chart -->
<section class="slide" data-title="Chart">
<p class="kicker">Numbers · 实际效果</p>
<h2 class="h2">做 deck 的时间分布,使用前/使用后</h2>
<div class="card mt-l" style="height:440px;padding:24px"><canvas id="chart"></canvas></div>
<div class="deck-footer"><span class="dim2">data</span><span class="slide-number" data-current="6" data-total="8"></span></div>
<div class="notes">使用后,「写内容」时间占比大幅上升,其他一切下降——这正是我们想要的。</div>
</section>
<!-- 7. CTA -->
<section class="slide center tc" data-title="CTA">
<div style="max-width:920px">
<p class="kicker">Your turn</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in" style="font-size:96px"><span class="gradient-text">开始</span>做你的第一份</h1>
<p class="lede" style="margin:14px auto 30px">复制一份 deck,换你的内容,按 <b>T</b> 选一个最对味的主题,讲完还能一键导 PNG。</p>
<div class="row" style="justify-content:center;gap:14px">
<div class="card" style="padding:18px 26px"><code>./scripts/new-deck.sh my-talk</code></div>
</div>
<p class="dim2 mt-l" style="font-size:14px">←/→ 翻页 · T 主题 · A 动效 · F 全屏 · O 概览 · S 备注</p>
</div>
<div class="deck-footer"><span class="dim2">cta</span><span class="slide-number" data-current="7" data-total="8"></span></div>
<div class="notes">最后给一个具体的行动:一条命令。别停留在「我回去试试」。</div>
</section>
<!-- 8. Thanks -->
<section class="slide center tc" data-title="Thanks">
<div>
<div class="anim-confetti-burst" style="display:inline-block;padding:20px"></div>
<h1 class="h1" style="font-size:180px;line-height:1"><span class="gradient-text">Thanks</span></h1>
<p class="lede">lewis · sudolewis@gmail.com · MIT 2026</p>
</div>
<div class="deck-footer"><span class="dim2">end</span><span class="slide-number" data-current="8" data-total="8"></span></div>
<div class="notes">谢谢大家。Q&A 时间。</div>
</section>
</div>
<script src="../../assets/runtime.js"></script>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const a1=css.getPropertyValue('--accent').trim();
const a2=css.getPropertyValue('--accent-2').trim();
const text2=css.getPropertyValue('--text-2').trim();
const border=css.getPropertyValue('--border').trim();
new Chart(document.getElementById('chart'),{type:'bar',
data:{labels:['写内容','挑版式','调样式','出图','动效'],
datasets:[
{label:'使用前 (分钟)',data:[92,48,36,22,14],backgroundColor:a2,borderRadius:6},
{label:'使用后 (分钟)',data:[18,3,2,1,1],backgroundColor:a1,borderRadius:6}]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{x:{ticks:{color:text2},grid:{color:border}},
y:{ticks:{color:text2},grid:{color:border}}}}});
});
</script>
</body></html>
references/animations.md
# Animations catalog
All animations live in `assets/animations/animations.css`. Apply them by
adding `class="anim-<name>"` OR `data-anim="<name>"` to any element
(`runtime.js` re-triggers `data-anim` elements whenever a slide becomes
active, so you get the entry effect every time you navigate onto the slide).
Open `templates/animation-showcase.html` to browse all of them — one slide
per animation, auto-playing on slide enter. Press **A** on any slide to cycle
a random animation on the current page.
## Directional fades
| name | effect | use for |
|---|---|---|
| `fade-up` | Translate from +32 px, fade. | Default for paragraph + card entry. |
| `fade-down` | Translate from -32 px, fade. | Headers / banners / callouts. |
| `fade-left` | Translate from -40 px. | Left column in a two-column layout. |
| `fade-right` | Translate from +40 px. | Right column in a two-column layout. |
## Dramatic entries
| name | effect | use for |
|---|---|---|
| `rise-in` | +60 px rise + blur-off. | Slide titles, hero headlines. |
| `drop-in` | -60 px drop + slight scale. | Banners, alert bars. |
| `zoom-pop` | Scale 0.6 → 1.04 → 1. | Buttons, stat numbers, CTAs. |
| `blur-in` | 18 px blur clears. | Cover page reveal. |
| `glitch-in` | Clip-path steps + jitter. | Tech / cyber / error states. |
## Text effects
| name | effect | use for |
|---|---|---|
| `typewriter` | Monospace-like type reveal. | One-liners, slogans. |
| `neon-glow` | Cyclic text-shadow pulse. | Terminal-green / dracula themes. |
| `shimmer-sweep` | White sheen passes across. | Metallic buttons, premium cards. |
| `gradient-flow` | Infinite horizontal gradient slide. | Brand wordmarks. |
## Lists & numbers
| name | effect | use for |
|---|---|---|
| `stagger-list` | Children rise-in one-by-one. | Any `<ul>` or `.grid`. |
| `counter-up` | Number ticks 0 → target. | KPI, stat-highlight pages. |
Counter markup:
```html
<span class="counter" data-to="1248">0</span>
```
## SVG / geometry
| name | effect | use for |
|---|---|---|
| `path-draw` | Strokes draw themselves. | Lines, arrows, diagrams. |
| `morph-shape` | Path `d` morph. | Background shapes. |
Put `class="anim-path-draw"` on `<svg>`; every path/line/circle inside gets drawn.
## 3D & perspective
| name | effect | use for |
|---|---|---|
| `parallax-tilt` | Hover → 3D tilt. | Hero cards, product shots. |
| `card-flip-3d` | Y-axis 90° flip. | Before/after reveal. |
| `cube-rotate-3d` | Rotate in from a cube side. | Section dividers. |
| `page-turn-3d` | Left-hinge page turn. | Editorial / story flows. |
| `perspective-zoom` | Pull from -400 Z. | Cover openings. |
## Ambient / continuous
| name | effect | use for |
|---|---|---|
| `marquee-scroll` | Infinite horizontal loop. | Client logo strips. |
| `kenburns` | 14 s slow zoom on images. | Hero backgrounds. |
| `confetti-burst` | Pseudo-element sparkle burst. | Thanks / win pages. |
| `spotlight` | Circular clip-path reveal. | Big reveal moments. |
| `ripple-reveal` | Corner-origin ripple reveal. | Section transitions. |
## Respecting motion preferences
All animations are disabled automatically when
`prefers-reduced-motion: reduce` is set. Do not override this.
## Tips
- Prefer `data-anim="..."` over `class="anim-..."` so that the runtime
re-triggers the animation whenever the slide becomes active.
- Use at most 1-2 distinct animation types on a single slide. Mixing 5 looks
messy.
- Stagger lists + a single hero entry = clean rhythm.
- For counter-up, pair with `stat-highlight.html` or `kpi-grid.html`.
## FX (canvas)
CSS animations are fire-and-forget entry effects. **FX** are live, continuously
running canvas/DOM effects that start when their slide becomes active and stop
when it leaves. They are loaded by `assets/animations/fx-runtime.js`, which
dynamically pulls every module under `assets/animations/fx/*.js` and watches
`.slide.is-active` to run lifecycle.
Add to any page:
```html
<script src="../assets/animations/fx-runtime.js"></script>
```
Then drop one of these into any slide:
```html
<div data-fx="particle-burst" style="width:100%;height:360px;"></div>
```
The container just needs a size — the FX auto-sizes a canvas to fit with
`ResizeObserver` + DPR correction. Colors read your theme (`--accent`,
`--accent-2`, `--ok`, `--warn`, `--danger`).
| name | effect | use case | trigger |
|---|---|---|---|
| `particle-burst` | Particles explode from center, gravity + fade, re-bursts every 2.5s. | Reveal moments, stat pages. | `<div data-fx="particle-burst">` |
| `confetti-cannon` | Colored rotating rects arcing from both bottom corners. | Thank you / success pages. | `<div data-fx="confetti-cannon">` |
| `firework` | Rockets from bottom explode into colored sparks, continuous. | Celebration, launch slides. | `<div data-fx="firework">` |
| `starfield` | 3D perspective starfield flying outward. | Sci-fi / deep space backgrounds. | `<div data-fx="starfield">` |
| `matrix-rain` | Falling green katakana + hex columns. | Cyber / security / data theme. | `<div data-fx="matrix-rain">` |
| `knowledge-graph` | Force-directed graph, 28 labeled nodes, ~50 edges, live physics. | Knowledge / RAG / graph slides. | `<div data-fx="knowledge-graph">` |
| `neural-net` | 4-6-6-3 feedforward net with pulses traveling along edges. | ML / model architecture slides. | `<div data-fx="neural-net">` |
| `constellation` | Drifting points, linked when within 150 px, opacity by distance. | Ambient hero backgrounds. | `<div data-fx="constellation">` |
| `orbit-ring` | 5 concentric rings with dots at different speeds, radial glow. | System / planet / layered concepts. | `<div data-fx="orbit-ring">` |
| `galaxy-swirl` | Logarithmic spiral of ~800 particles, slow rotation. | Cover pages, intros. | `<div data-fx="galaxy-swirl">` |
| `word-cascade` | Words fall from top, pile up at bottom. | Vocabulary / concept cloud slides. | `<div data-fx="word-cascade">` |
| `letter-explode` | Heading letters fly in from random directions, loops every ~4.5s. | Big titles, hero text. | `<div data-fx="letter-explode" data-fx-text-value="EXPLODE">` |
| `chain-react` | 8 circles with a domino pulse wave traveling across. | Pipeline / sequential flow. | `<div data-fx="chain-react">` |
| `magnetic-field` | Particles travel bezier/sin curves leaving trails. | Energy / flow / abstract. | `<div data-fx="magnetic-field">` |
| `data-stream` | Rows of scrolling hex/binary text, cyberpunk. | Data, API, security. | `<div data-fx="data-stream">` |
| `gradient-blob` | 4 drifting blurred radial gradients (additive). | Soft hero backgrounds. | `<div data-fx="gradient-blob">` |
| `sparkle-trail` | Pointer-driven sparkle emitter (auto-wiggles if idle). | Interactive reveal, hover canvases. | `<div data-fx="sparkle-trail">` |
| `shockwave` | Expanding rings from center on loop. | Impact, launch, alert. | `<div data-fx="shockwave">` |
| `typewriter-multi` | 3 lines typing concurrently with blinking block cursors (DOM). | Terminal, agent boot log. | `<div data-fx="typewriter-multi" data-fx-line1="> boot...">` |
| `counter-explosion` | Number counts 0 → target, bursts particles, resets after 4s. | KPI reveal, record highs. | `<div data-fx="counter-explosion" data-fx-to="2400">` |
FX tips:
- One FX per slide is almost always enough. Mix with regular CSS `data-anim`
effects for layered polish.
- The container needs an explicit size (height) — the canvas fills 100%.
- Every module respects theme custom properties. Set `--accent` / `--accent-2`
on the slide or element to recolor on the fly.
- Lifecycle is automatic: entering a slide starts the FX, leaving stops it and
frees the canvas. You can also call `window.__hpxReinit(el)` manually.
references/authoring-guide.md
# Authoring guide
How to turn a user request ("make me a deck about X") into a finished
html-ppt deck. Follow these steps in order.
## 1. Understand the deck
Before touching files, clarify:
1. **Audience** — engineers? designers? executives? consumers?
2. **Length** — 5 min lightning? 20 min share? 45 min talk?
3. **Language** — Chinese, English, bilingual? (Noto Sans SC is preloaded.)
4. **Format** — on-screen live, PDF export, 小红书图文?
5. **Tone** — clinical / playful / editorial / cyber?
The audience + tone map to a theme; the length maps to slide count; the
format maps to runtime features (live → notes + T-cycle; PDF → page-break
CSS, already handled in `base.css`).
## 2. Pick a theme
Use `references/themes.md`. When in doubt:
- **Engineers** → `catppuccin-mocha` / `tokyo-night` / `dracula`.
- **Designers / product** → `editorial-serif` / `aurora` / `soft-pastel`.
- **Execs** → `minimal-white` / `arctic-cool` / `swiss-grid`.
- **Consumers** → `xiaohongshu-white` / `sunset-warm` / `soft-pastel`.
- **Cyber / CLI / infra** → `terminal-green` / `blueprint` / `gruvbox-dark`.
- **Pitch / bold** → `neo-brutalism` / `sharp-mono` / `bauhaus`.
- **Launch / product reveal** → `glassmorphism` / `aurora`.
Wire the theme as `<link id="theme-link" href="../assets/themes/NAME.css">`
and list 3-5 alternatives in `data-themes` so the user can press T to audition.
## 3. Outline the deck
A solid 20-minute deck is usually:
```
cover → toc → section-divider #1 → [2-4 body pages] →
section-divider #2 → [2-4 body pages] → section-divider #3 →
[2-4 body pages] → cta → thanks
```
Pick 1 layout per page from `references/layouts.md`. Don't repeat the same
layout twice in a row.
## 4. Scaffold the deck
```bash
./scripts/new-deck.sh my-talk
```
This copies `templates/deck.html` into `examples/my-talk/index.html` with
paths rewritten. Add/remove `<section class="slide">` blocks to match your
outline.
## 5. Author each slide
For each outline item:
1. Open the matching single-page layout, e.g. `templates/single-page/kpi-grid.html`.
2. Copy the `<section class="slide">…</section>` block.
3. Paste into your deck.
4. Replace demo data with real data. Keep the class structure intact.
5. Set `data-title="..."` (used by the Overview grid).
6. Add `<div class="notes">…</div>` with speaker notes.
## 6. Add animations sparingly
Rules of thumb:
- Cover/title: `rise-in` or `blur-in`.
- Body content: `fade-up` for the hero element, `stagger-list` for grids/lists.
- Stat pages: `counter-up`.
- Section dividers: `perspective-zoom` or `cube-rotate-3d`.
- Closer: `confetti-burst` on the "Thanks" text.
Pick **one** accent animation per slide. Everything else should be calm.
## 7. Chinese + English decks
- Fonts are already imported in `fonts.css` (Noto Sans SC + Noto Serif SC).
- Use `lang="zh-CN"` on `<html>`.
- For bilingual titles, stack lines: `<h1 class="h1">主标题<br><span class="dim">English subtitle</span></h1>`.
- Keep English subtitles in a lighter weight (300) and dim color to avoid
visual competition.
## 8. Review in-browser
```bash
open examples/my-talk/index.html
```
Walk through every slide with ← →. Press:
- **O** — overview grid; catch any layout clipping.
- **T** — cycle themes; make sure nothing looks broken in any theme.
- **S** — open speaker notes; verify every slide has notes.
## 9. Export to PNG
```bash
# single slide
./scripts/render.sh examples/my-talk/index.html
# all slides (autodetect count by looking for .slide sections)
./scripts/render.sh examples/my-talk/index.html all
# explicit slide count + output dir
./scripts/render.sh examples/my-talk/index.html 12 out/my-talk-png
```
Output is 1920×1080 by default. Change in `render.sh` if the user wants 3:4
for 小红书图文 (1242×1660).
## 10. What to NOT do
- Don't hand-author from a blank file.
- Don't use raw hex colors in slide markup. Use tokens.
- Don't load heavy animation frameworks. Everything should stay within the
CSS/JS that already ships.
- Don't add more than one new template file unless a genuinely new layout
type is needed. Prefer composition.
- Don't delete slides from the showcase decks.
- **Don't put presenter-only text on the slide.** Any descriptive text,
narration cues, or explanations meant for the speaker (e.g. "这一页的重点是…",
"Note: mention X here", small grey captions explaining the slide's purpose)
MUST go inside `<div class="notes">`, not as visible elements. The `.notes`
div is hidden (`display:none`) and only shown via the S overlay. Slides
should contain ONLY audience-facing content.
## Troubleshooting
- **Theme doesn't switch with T**: check `data-themes` on `<body>` and
`data-theme-base` pointing to the themes directory relative to the HTML
file.
- **Fonts fall back**: make sure `fonts.css` is linked before the theme.
- **Chart.js colors wrong**: charts read CSS vars in JS; make sure they run
after the DOM is ready (`addEventListener('DOMContentLoaded', …)`).
- **PNG too small**: bump `--window-size` in `scripts/render.sh`.
assets/themes/xiaohongshu-white.css
/* theme: xiaohongshu-white — 小红书白底高级感 */
:root{
--bg:#fffdfb;--bg-soft:#fff6f1;--surface:#ffffff;--surface-2:#fff1ea;
--border:rgba(60,30,20,.1);--border-strong:rgba(60,30,20,.22);
--text-1:#1a1210;--text-2:#4f3a32;--text-3:#a08d85;
--accent:#ff2742;--accent-2:#ff7a90;--accent-3:#ffb38a;
--good:#3ba55c;--warn:#f5a524;--bad:#ff2742;
--grad:linear-gradient(135deg,#ff2742,#ff7a90 55%,#ffb38a);
--grad-soft:linear-gradient(135deg,#fff6f1,#ffeae0);
--radius:20px;--radius-sm:14px;--radius-lg:28px;
--shadow:0 12px 30px rgba(255,39,66,.08);
--shadow-lg:0 24px 60px rgba(255,39,66,.14);
--font-sans:'Noto Sans SC','Inter',sans-serif;
--font-display:'Noto Serif SC','Playfair Display',serif;
--letter-tight:-.02em;
}
assets/themes/y2k-chrome.css
/* theme: y2k-chrome — 千禧银色铬金属 */
:root{
--bg:#dfe4ec;--bg-soft:#eef1f6;--surface:rgba(255,255,255,.72);--surface-2:rgba(255,255,255,.5);
--border:rgba(120,135,170,.32);--border-strong:rgba(80,100,140,.55);
--text-1:#1a1f2e;--text-2:#4a536a;--text-3:#8590a6;
--accent:#8a5cff;--accent-2:#3ccfd8;--accent-3:#ff84c4;
--grad:linear-gradient(135deg,#b8c4d8 0%,#f5f7fb 30%,#8a9ab8 55%,#e8ecf4 80%,#6b7a95 100%);
--grad-soft:linear-gradient(135deg,#c9e4ff,#f5d6ff 50%,#d6fffa);
--radius:26px;--radius-sm:16px;--radius-lg:36px;
--shadow:0 12px 30px rgba(70,90,130,.22),inset 0 1px 0 rgba(255,255,255,.9),inset 0 -1px 0 rgba(80,100,140,.2);
--shadow-lg:0 24px 60px rgba(70,90,130,.35),inset 0 2px 0 rgba(255,255,255,.95);
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
--font-display:'Space Grotesk','Inter',sans-serif;
}
body{background:
linear-gradient(135deg,#c4cfe0 0%,#f0f3f8 25%,#aab8d0 50%,#f5f7fb 75%,#b8c4d8 100%)}
h1.title,.h1{background:linear-gradient(180deg,#f8faff 0%,#9aa8c4 50%,#4a5670 100%);
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
.card{backdrop-filter:blur(16px) saturate(140%);-webkit-backdrop-filter:blur(16px) saturate(140%)}
.pill{background:linear-gradient(180deg,#fff,#d4dcec);border-color:rgba(120,135,170,.4)}
scripts/new-deck.sh
#!/usr/bin/env bash
# html-ppt :: new-deck.sh — scaffold a new deck from templates/deck.html
#
# Usage:
# new-deck.sh <name> [output-parent-dir]
#
# Creates <parent>/<name>/index.html with paths rewritten to point at the
# skill's shared assets/themes/animations. Defaults to ./examples/.
set -euo pipefail
NAME="${1:-}"
if [[ -z "$NAME" ]]; then
echo "usage: new-deck.sh <name> [parent-dir]" >&2
exit 1
fi
PARENT="${2:-examples}"
HERE="$(cd "$(dirname "$0")/.." && pwd)"
TEMPLATE="$HERE/templates/deck.html"
if [[ ! -f "$TEMPLATE" ]]; then
echo "error: template not found at $TEMPLATE" >&2
exit 1
fi
OUT_DIR="$HERE/$PARENT/$NAME"
if [[ -e "$OUT_DIR" ]]; then
echo "error: $OUT_DIR already exists" >&2
exit 1
fi
mkdir -p "$OUT_DIR"
# templates/deck.html references ../assets/...; for examples/<name>/index.html
# that same relative path (../../assets/...) needs one more ../.
sed 's|href="../assets/|href="../../assets/|g; s|src="../assets/|src="../../assets/|g; s|data-theme-base="../assets/|data-theme-base="../../assets/|g' \
"$TEMPLATE" > "$OUT_DIR/index.html"
echo "✔ created $OUT_DIR/index.html"
echo ""
echo "next steps:"
echo " open $OUT_DIR/index.html"
echo " # press T to cycle themes, ← → to navigate, O for overview"
echo ""
echo " # render to PNG:"
echo " $HERE/scripts/render.sh $OUT_DIR/index.html all"
scripts/render.sh
#!/usr/bin/env bash
# html-ppt :: render.sh — headless Chrome screenshot(s)
#
# Usage:
# render.sh <html-file> # one PNG, slide 1
# render.sh <html-file> <N> # N PNGs, slides 1..N, via #/k
# render.sh <html-file> all # autodetect .slide count
# render.sh <html-file> <N> <out-dir> # custom output dir
#
# Requires: Google Chrome at /Applications/Google Chrome.app (macOS).
set -euo pipefail
CHROME="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
if [[ ! -x "$CHROME" ]]; then
echo "error: Chrome not found at $CHROME" >&2
exit 1
fi
FILE="${1:-}"
if [[ -z "$FILE" ]]; then
echo "usage: render.sh <html> [N|all] [out-dir]" >&2
exit 1
fi
if [[ ! -f "$FILE" ]]; then
echo "error: $FILE not found" >&2
exit 1
fi
COUNT="${2:-1}"
OUT="${3:-}"
ABS="$(cd "$(dirname "$FILE")" && pwd)/$(basename "$FILE")"
STEM="$(basename "${FILE%.*}")"
if [[ "$COUNT" == "all" ]]; then
COUNT="$(grep -c 'class="slide"' "$FILE" || true)"
[[ -z "$COUNT" || "$COUNT" -lt 1 ]] && COUNT=1
fi
if [[ -z "$OUT" ]]; then
if [[ "$COUNT" -gt 1 ]]; then
OUT="$(dirname "$FILE")/${STEM}-png"
mkdir -p "$OUT"
fi
fi
render_one() {
local url="$1" target="$2"
"$CHROME" \
--headless=new \
--disable-gpu \
--hide-scrollbars \
--no-sandbox \
--virtual-time-budget=4000 \
--window-size=1920,1080 \
--screenshot="$target" \
"$url" >/dev/null 2>&1
echo " ✔ $target"
}
if [[ "$COUNT" == "1" ]]; then
OUT_FILE="${OUT:-$(dirname "$FILE")/${STEM}.png}"
render_one "file://$ABS" "$OUT_FILE"
else
for i in $(seq 1 "$COUNT"); do
render_one "file://$ABS#/$i" "$OUT/${STEM}_$(printf '%02d' "$i").png"
done
fi
echo "done: rendered $COUNT slide(s) from $FILE"
docs/readme/_theme-cell.html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<title>theme cell</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link id="theme-link" rel="stylesheet" href="../../assets/themes/minimal-white.css">
<style>
html,body{height:100%;margin:0;overflow:hidden}
body{
background:var(--bg,#fff);color:var(--text-1,#111);
font-family:var(--font-sans);box-sizing:border-box;
padding:5cqw 6cqw;container-type:size;
}
.k{font:700 2.6cqw/1 var(--font-mono,monospace);color:var(--text-3,#888);letter-spacing:.14em;text-transform:uppercase;margin-bottom:2.5cqh}
h1{font:900 11cqw/.95 var(--font-display,var(--font-sans));letter-spacing:-.025em;margin:0 0 3cqh;color:var(--text-1)}
.lede{font:500 3.2cqw/1.4 var(--font-sans);color:var(--text-2,#555);margin:0 0 3.5cqh;max-width:85cqw}
.row{display:flex;gap:1.4cqw;flex-wrap:wrap}
.pill{padding:1.2cqh 2.4cqw;border-radius:999px;background:var(--surface-2,#f4f4f8);color:var(--text-1);font:600 2.3cqw/1 var(--font-sans);border:1px solid var(--border,#e5e5ea)}
.pill.accent{background:var(--accent,#7c5cff);color:#fff;border-color:transparent}
.kpi{margin-top:4cqh;display:flex;gap:5cqw}
.kpi div{font:900 10cqw/1 var(--font-display,var(--font-sans));letter-spacing:-.03em;color:var(--accent,#7c5cff)}
.kpi div span{display:block;font:500 1.8cqw/1 var(--font-sans);color:var(--text-3,#888);margin-top:1cqh;letter-spacing:.08em;text-transform:uppercase}
.gradient-hero{background:linear-gradient(90deg,var(--accent,#7c5cff),var(--accent-2,#22d3ee),var(--accent-3,#ff4d8d));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
</style>
</head>
<body>
<div class="k" id="kname">theme · minimal-white</div>
<h1 id="h1">2026<br>年度回顾</h1>
<p class="lede">同一份 outline,换一行 theme,排版、字体、色系、装饰全部重写。</p>
<div class="row">
<span class="pill accent">12 里程碑</span>
<span class="pill">团队 +40%</span>
<span class="pill">SAT 98%</span>
</div>
<div class="kpi">
<div>98%<span>Sat</span></div>
<div>12<span>MS</span></div>
<div>7×<span>Faster</span></div>
</div>
<script>
(function(){
const m = /[?&]theme=([\w-]+)/.exec(location.search||'');
if (m){
const name = m[1];
document.getElementById('theme-link').href = '../../assets/themes/'+name+'.css';
document.getElementById('kname').textContent = 'theme · ' + name;
if (/rainbow|aurora|cyberpunk|vaporwave|y2k/.test(name)){
document.getElementById('h1').classList.add('gradient-hero');
}
}
})();
</script>
</body>
</html>
references/themes.md
# Themes catalog
Every theme is a short CSS file in `assets/themes/` that overrides tokens
defined in `assets/base.css`. Switch themes by changing the `href` of
`<link id="theme-link">` or by pressing **T** if the deck has a
`data-themes="a,b,c"` attribute on `<body>` or `<html>`.
All themes define the same variables: `--bg`, `--bg-soft`, `--surface`,
`--surface-2`, `--border`, `--text-1/2/3`, `--accent`, `--accent-2/3`,
`--good`, `--warn`, `--bad`, `--grad`, `--grad-soft`, `--radius*`, `--shadow*`,
`--font-sans`, `--font-display`.
## Light & calm
| name | description | when to use |
|---|---|---|
| `minimal-white` | 极简白,克制高级。Inter,强文字层级,极低阴影。 | 内部汇报、一对一技术评审、不抢内容的严肃话题 |
| `editorial-serif` | 杂志风 Playfair 衬线 + 奶油底。 | 品牌故事、文字密度大的长文演讲 |
| `soft-pastel` | 柔和马卡龙三色渐变。 | 产品发布、面向消费者、轻松话题 |
| `xiaohongshu-white` | 小红书白底 + 暖红 accent + 衬线标题。 | 小红书图文、生活/美学类内容 |
| `solarized-light` | 经典低眩光配色。 | 长时间观看的工作坊、教学 |
| `catppuccin-latte` | catppuccin 浅色。 | 开发者、极客友好的技术分享 |
## Bold & statement
| name | description | when to use |
|---|---|---|
| `sharp-mono` | 纯黑白 + Archivo Black + 硬阴影。 | 宣言类、极具冲击力的视觉 |
| `neo-brutalism` | 厚描边、硬阴影、明黄 accent。 | 创业路演、敢说敢做的调性 |
| `bauhaus` | 几何 + 红黄蓝原色。 | 设计 talk、艺术史/产品美学主题 |
| `swiss-grid` | 瑞士网格 + Helvetica 感 + 12 栏底纹。 | 严肃排版、设计行业 |
| `memphis-pop` | 孟菲斯波普背景点 + 大字标题。 | 年轻、潮流、品牌合作 |
## Cool & dark
| name | description | when to use |
|---|---|---|
| `catppuccin-mocha` | catppuccin 深。 | 开发者内部分享、长时间观看 |
| `dracula` | 经典 Dracula 紫红主色。 | 代码密集的技术分享 |
| `tokyo-night` | Tokyo Night 蓝夜。 | 偏冷技术分享、基础设施 |
| `nord` | 北欧清冷蓝白。 | 基础设施、云产品 |
| `gruvbox-dark` | 温暖复古深色。 | Terminal / vim / *nix 社群 |
| `rose-pine` | 玫瑰松,柔和暗色。 | 设计+开发交界、审美向技术 |
| `arctic-cool` | 蓝/青/石板灰 浅色版。 | 商业分析、金融、冷静理性 |
## Warm & vibrant
| name | description | when to use |
|---|---|---|
| `sunset-warm` | 橘 / 珊瑚 / 琥珀三色渐变。 | 生活方式、奖项颁发、情绪正向 |
## Effect-heavy
| name | description | when to use |
|---|---|---|
| `glassmorphism` | 毛玻璃 + 多色光斑背景。 | Apple 式发布会、产品特性展示 |
| `aurora` | 极光渐变 + blur + saturate。 | 封面 / CTA / 结语页 |
| `rainbow-gradient` | 白底 + 彩虹流动渐变 accent。 | 欢乐向、节日、庆祝页 |
| `blueprint` | 蓝图工程 + 网格底纹 + 蒙太奇字体。 | 系统架构、工程蓝图 |
| `terminal-green` | 绿屏终端 + 等宽 + 发光文字。 | CLI/black-hat/复古朋克 |
## v2 additions
### Light & professional
| name | description | when to use |
|---|---|---|
| `corporate-clean` | 纯白 + 海军蓝 accent + Inter + 保守边框。 | 董事会汇报、B2B 销售、金融保险 |
| `pitch-deck-vc` | YC 风白底 + 蓝紫渐变 accent + 大留白。 | 融资路演、种子轮、VC meeting |
| `academic-paper` | 论文白 + 衬线正文 + 黑墨 + 蓝链接。 | 学术报告、研究分享、会议论文 |
| `japanese-minimal` | 象牙白 + 朱红 accent + 极大留白 + Noto Serif。 | 品牌升级、匠人故事、禅意叙事 |
| `engineering-whiteprint` | 白底 + 坐标纸网格 + 海军墨线 + 等宽字。 | 系统设计、API 文档、架构白皮书 |
### Bold & editorial
| name | description | when to use |
|---|---|---|
| `magazine-bold` | 奶油底 + 超大 Playfair 衬线 + 橙色 spot。 | 专栏文章、封面故事、品牌月刊 |
| `news-broadcast` | 白底 + 红色竖条 + Oswald 大写 + 硬阴影。 | 突发新闻、发布通稿、数据播报 |
| `midcentury` | 奶油底 + 芥末/青/焦橙 + 锐利几何。 | 设计史、家居美学、复古品牌 |
| `retro-tv` | 暖奶油 + CRT 扫描线 + 琥珀橙 accent。 | 怀旧叙事、八零九零年代主题 |
### Effect-heavy / dramatic
| name | description | when to use |
|---|---|---|
| `cyberpunk-neon` | 纯黑 + 霓虹粉青黄 + 发光 + JetBrains Mono。 | 黑客、地下文化、赛博 talk |
| `vaporwave` | 深紫 + 粉红青蓝渐变 + 晕染光斑。 | 音乐、潮流艺术、A E S T H E T I C |
| `y2k-chrome` | 银铬渐变 + 彩虹 accent + 大圆角 + Space Grotesk。 | 千禧怀旧、时尚品牌、Gen-Z |
## How to apply
```html
<link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css">
```
Or enable `T`-cycling by listing themes on the body:
```html
<body data-themes="minimal-white,aurora,catppuccin-mocha" data-theme-base="../assets/themes/">
```
## How to extend
Copy an existing theme, rename it, and override only the variables you want to
change. Keep each theme under ~200 lines. Prefer adjusting tokens to adding
new selectors.
docs/readme/montage-themes.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>36 themes showcase</title>
<style>
:root{--ink:#0b0b10;--muted:#6b6b78;--line:#e7e7ef}
*{box-sizing:border-box;margin:0;padding:0}
html,body{width:1920px;height:1080px;overflow:hidden}
body{background:#f6f7fa;font-family:"PingFang SC","Noto Sans SC","Inter","SF Pro Display",-apple-system,sans-serif;color:var(--ink);padding:48px 56px 44px;display:flex;flex-direction:column;gap:28px}
.hdr{display:flex;align-items:flex-end;justify-content:space-between}
.hdr h2{font:900 48px/1 "Inter",sans-serif;letter-spacing:-.02em}
.hdr h2 b{display:inline-block;font-size:58px;padding-right:14px;background:linear-gradient(90deg,#7c5cff,#22d3ee);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.hdr .sub{font:600 16px/1 "JetBrains Mono","SF Mono",monospace;color:var(--muted);letter-spacing:.1em;text-transform:uppercase}
.grid{flex:1;display:grid;grid-template-columns:repeat(4,1fr);grid-template-rows:repeat(2,1fr);gap:18px;min-height:0}
.cell{position:relative;border-radius:18px;overflow:hidden;border:1px solid var(--line);box-shadow:0 16px 48px rgba(10,10,30,.1);background:#fff;min-height:0}
.cell iframe{position:absolute;inset:0;width:100%;height:100%;border:0;pointer-events:none}
.cell .label{position:absolute;left:14px;bottom:12px;z-index:5;font:700 11px/1 "JetBrains Mono","SF Mono",monospace;letter-spacing:.1em;padding:6px 12px;border-radius:999px;background:rgba(255,255,255,.94);color:#1a1a22;text-transform:uppercase;border:1px solid rgba(0,0,0,.06)}
.cell.dark .label{background:rgba(10,10,20,.78);color:#fff;border-color:rgba(255,255,255,.14)}
</style>
</head>
<body>
<div class="hdr">
<h2><b>36</b>Themes — one keyword, new identity</h2>
<div class="sub">html-ppt · assets/themes/*.css · pick 8 of 36</div>
</div>
<div class="grid">
<div class="cell"><iframe src="_theme-cell.html?theme=minimal-white"></iframe><span class="label">minimal-white</span></div>
<div class="cell dark"><iframe src="_theme-cell.html?theme=tokyo-night"></iframe><span class="label">tokyo-night</span></div>
<div class="cell dark"><iframe src="_theme-cell.html?theme=aurora"></iframe><span class="label">aurora</span></div>
<div class="cell"><iframe src="_theme-cell.html?theme=xiaohongshu-white"></iframe><span class="label">xiaohongshu-white</span></div>
<div class="cell dark"><iframe src="_theme-cell.html?theme=cyberpunk-neon"></iframe><span class="label">cyberpunk-neon</span></div>
<div class="cell dark"><iframe src="_theme-cell.html?theme=dracula"></iframe><span class="label">dracula</span></div>
<div class="cell"><iframe src="_theme-cell.html?theme=soft-pastel"></iframe><span class="label">soft-pastel</span></div>
<div class="cell"><iframe src="_theme-cell.html?theme=magazine-bold"></iframe><span class="label">magazine-bold</span></div>
</div>
</body>
</html>
templates/full-decks/course-module/README.md
# course-module · 教学模块
7-slide teaching module: cover (title + meta), objectives, core concept, worked example, exercise, check-your-understanding (MCQ), summary.
Academic but friendly look: warm off-white paper, Playfair Display display type, a green/terracotta accent pair. A persistent **left sidebar** on content slides lists the module's learning objectives and checks them off as you progress — students always know where they are.
**Use when:** online course modules, lecture handouts, onboarding curricula, workshop units.
**Feel:** a good textbook opened to a chapter — structured, quiet, encouraging.
templates/deck.html
<!DOCTYPE html>
<html lang="zh-CN" data-theme="minimal-white">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>html-ppt · Deck</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
</head>
<body data-themes="minimal-white,editorial-serif,soft-pastel,arctic-cool,sunset-warm,catppuccin-mocha,tokyo-night,aurora,xiaohongshu-white,neo-brutalism" data-theme-base="../assets/themes/">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<p class="kicker">html-ppt · 2026</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">用模板,<span class="gradient-text">换主题</span><br>讲任何事情</h1>
<p class="lede">24 themes · 30 layouts · 25 animations · zero build</p>
<div class="deck-footer"><span class="dim2">lewis</span><span class="slide-number" data-current="1" data-total="6"></span></div>
<div class="notes">这是一个最小可用的 deck。你可以复制这个文件作为新 deck 的起点。</div>
</section>
<!-- 2. TOC -->
<section class="slide" data-title="目录">
<p class="kicker">Agenda</p>
<h2 class="h2">我们会讲三件事</h2>
<div class="grid g3 mt-l anim-stagger-list" data-anim-target>
<div class="card"><h4>01 · Tokens</h4><p class="dim">把颜色/字体/圆角收进 CSS 变量。</p></div>
<div class="card"><h4>02 · Layouts</h4><p class="dim">30 种可复用单页。</p></div>
<div class="card"><h4>03 · Runtime</h4><p class="dim">键盘驱动、按 T 换主题。</p></div>
</div>
</section>
<!-- 3. Stat -->
<section class="slide center tc" data-title="Stat">
<div>
<p class="kicker">Result</p>
<div style="font-size:220px;font-weight:900;line-height:1"><span class="counter gradient-text" data-to="92">0</span><span class="gradient-text">%</span></div>
<h3>的准备时间被省下</h3>
</div>
</section>
<!-- 4. Two column -->
<section class="slide" data-title="Tokens">
<p class="kicker">Under the hood</p>
<h2 class="h2">换主题 = 换一组变量</h2>
<div class="grid g2 mt-l">
<div class="card"><h4>语义变量</h4><p class="dim">写 <code>var(--surface)</code>,不写具体色值。</p></div>
<div class="card"><h4>一键切换</h4><p class="dim">按 T 循环所有主题——所有 slide 同步更新。</p></div>
</div>
</section>
<!-- 5. CTA -->
<section class="slide center tc" data-title="CTA">
<div>
<p class="kicker">Your turn</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in">开始做你的 deck</h1>
<p class="lede" style="margin:16px auto">按 ← → 翻页 · T 切主题 · A 切动效 · F 全屏 · O 概览 · S 备注</p>
</div>
</section>
<!-- 6. Thanks -->
<section class="slide center tc" data-title="Thanks">
<h1 class="h1" style="font-size:160px;line-height:1"><span class="gradient-text">Thanks</span></h1>
<p class="lede">lewis · sudolewis@gmail.com</p>
</section>
</div>
<script src="../assets/runtime.js"></script>
</body></html>
templates/animation-showcase.html
<!DOCTYPE html>
<html lang="en" data-theme="aurora">
<head>
<meta charset="utf-8"><title>Animation + FX Showcase — html-ppt</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
<style>
.fx-stage{
position:relative;
margin:20px auto 0;
width:min(900px, 92%);
height:380px;
border-radius:var(--radius-lg, 16px);
background:rgba(10,12,22,0.55);
border:1px solid var(--border,#2a2a3a);
box-shadow:var(--shadow-lg, 0 20px 60px rgba(0,0,0,.4));
overflow:hidden;
}
.fx-label{
position:absolute;top:14px;left:16px;z-index:5;
font-family:var(--font-mono, ui-monospace,Menlo,monospace);
font-size:11px;letter-spacing:.14em;color:var(--text-3,#a0a0b8);
text-transform:uppercase;
background:rgba(0,0,0,0.35);padding:4px 10px;border-radius:999px;
border:1px solid rgba(255,255,255,0.08);
}
.fx-replay{
position:absolute;top:12px;right:14px;z-index:5;
appearance:none;cursor:pointer;
padding:7px 14px;border-radius:999px;
background:linear-gradient(135deg, var(--accent,#7c5cff), var(--accent-2,#22d3ee));
color:#fff;font:600 12px system-ui,sans-serif;
border:0;letter-spacing:.05em;
box-shadow:0 4px 16px rgba(124,92,255,0.35);
}
.fx-replay:hover{ filter:brightness(1.1); transform:translateY(-1px); }
.fx-indicator{position:absolute;top:24px;right:40px;font-family:var(--font-mono);font-size:11px;color:var(--text-3);letter-spacing:.1em}
.anim-box{margin:24px auto 0;width:640px;height:240px;border-radius:var(--radius-lg);background:var(--grad-soft);display:flex;align-items:center;justify-content:center;font-size:44px;font-weight:800;color:var(--text-1);box-shadow:var(--shadow-lg);border:1px solid var(--border)}
</style>
</head>
<body>
<div class="deck"></div>
<script>
const FX = [
['particle-burst', 'Particles explode from center, gravity + fade, re-bursts every ~2.5s.'],
['confetti-cannon', 'Colored rotating rects arcing from both bottom corners.'],
['firework', 'Rockets launch from the bottom and burst into colored sparks.'],
['starfield', '3D perspective starfield — infinite flythrough.'],
['matrix-rain', 'Classic green katakana + hex columns raining down.'],
['knowledge-graph', 'Force-directed graph, 28 labeled nodes, live physics + springs.'],
['neural-net', '4-6-6-3 feedforward net with pulses traveling along edges.'],
['constellation', 'Drifting points connect when close — ambient background.'],
['orbit-ring', '5 concentric rings with dots rotating at different speeds.'],
['galaxy-swirl', 'Logarithmic spiral with ~800 particles.'],
['word-cascade', 'Words fall from top and pile up at the bottom.'],
['letter-explode', 'Letters fly in from random directions, loops.'],
['chain-react', 'Row of 8 circles — a wave pulse dominoes across them.'],
['magnetic-field', 'Particles follow sine curves leaving gradient trails.'],
['data-stream', 'Rows of scrolling hex/binary text, cyberpunk feel.'],
['gradient-blob', '4 drifting blurred radial gradients (additive blending).'],
['sparkle-trail', 'Sparkles emit at your cursor (auto-wiggles if idle).'],
['shockwave', 'Expanding rings emanating from center, looping.'],
['typewriter-multi', 'Three lines typing concurrently with blinking block cursors.'],
['counter-explosion', 'Number counts 0 → 2400, bursts particles, resets.']
];
const CSS_ANIMS = [
['fade-up','Translate from +32 px, fade.'],
['fade-down','Translate from -32 px, fade.'],
['fade-left','From left.'],
['fade-right','From right.'],
['rise-in','Rise + blur-off.'],
['drop-in','Drop from above.'],
['zoom-pop','Elastic scale pop.'],
['blur-in','Blur clears.'],
['glitch-in','Glitch jitter.'],
['typewriter','Typewriter reveal.'],
['neon-glow','Neon pulse.'],
['shimmer-sweep','Sheen sweep.'],
['gradient-flow','Gradient flow.'],
['stagger-list','Staggered children.'],
['counter-up','Number tick.'],
['path-draw','SVG strokes draw.'],
['parallax-tilt','3D hover tilt.'],
['card-flip-3d','Y-axis flip.'],
['cube-rotate-3d','Cube rotate.'],
['page-turn-3d','Page turn.'],
['perspective-zoom','Pull from -400 Z.'],
['marquee-scroll','Infinite marquee.'],
['kenburns','Ken Burns zoom.'],
['confetti-burst','Pseudo confetti.'],
['spotlight','Circular clip reveal.'],
['morph-shape','SVG d morph.'],
['ripple-reveal','Corner ripple.']
];
const deck = document.querySelector('.deck');
const total = FX.length + CSS_ANIMS.length;
// Build FX slides (1..20)
FX.forEach((f, i) => {
const idx = i + 1;
const [name, desc] = f;
const sec = document.createElement('section');
sec.className = 'slide';
sec.setAttribute('data-title', 'fx / ' + name);
const extraAttrs = name === 'letter-explode'
? 'data-fx-text-value="' + name.toUpperCase() + '"'
: name === 'counter-explosion'
? 'data-fx-to="2400"'
: name === 'typewriter-multi'
? 'data-fx-line1="> initializing knowledge graph..." data-fx-line2="> loading 28 concept nodes" data-fx-line3="> agent ready. awaiting prompt_"'
: '';
sec.innerHTML = `
<span class="fx-indicator">${idx}/${total}</span>
<p class="kicker">FX · canvas · ${String(idx).padStart(2,'0')}</p>
<h1 class="h1"><span class="gradient-text">${name}</span></h1>
<p class="lede">${desc}</p>
<div class="fx-stage">
<span class="fx-label">data-fx="${name}"</span>
<button class="fx-replay" type="button">Replay</button>
<div class="fx-host" style="position:absolute;inset:0;" data-fx="${name}" ${extraAttrs}></div>
</div>
<div class="deck-footer"><span class="dim2">Press → for next slide</span><span class="slide-number" data-current="${idx}" data-total="${total}"></span></div>
`;
deck.appendChild(sec);
// Wire Replay button
sec.querySelector('.fx-replay').addEventListener('click', () => {
const host = sec.querySelector('.fx-host');
const name2 = host.getAttribute('data-fx');
const attrs = {};
for (const a of host.attributes) attrs[a.name] = a.value;
const parent = host.parentNode;
// stop existing
if (window.__hpxActive && window.__hpxActive.has(host)){
try{ window.__hpxActive.get(host).stop(); }catch(e){}
window.__hpxActive.delete(host);
}
const fresh = document.createElement('div');
for (const k in attrs) fresh.setAttribute(k, attrs[k]);
fresh.style.cssText = host.style.cssText;
parent.replaceChild(fresh, host);
if (window.__hpxReinit) window.__hpxReinit(sec);
});
});
// Build CSS animation slides (legacy, kept for completeness)
CSS_ANIMS.forEach((a, i) => {
const idx = FX.length + i + 1;
const sec = document.createElement('section');
sec.className = 'slide';
sec.setAttribute('data-title', a[0]);
sec.innerHTML = `
<span class="fx-indicator">${idx}/${total}</span>
<p class="kicker">CSS anim · ${String(idx).padStart(2,'0')}</p>
<h1 class="h1"><span class="gradient-text">${a[0]}</span></h1>
<p class="lede">${a[1]}</p>
<div class="anim-box anim-${a[0]}" data-anim="${a[0]}" data-anim-target>${a[0]}</div>
<div class="deck-footer"><span class="dim2">Press A to cycle</span><span class="slide-number" data-current="${idx}" data-total="${total}"></span></div>
`;
deck.appendChild(sec);
});
</script>
<script src="../assets/runtime.js"></script>
<script src="../assets/animations/fx-runtime.js"></script>
</body></html>
templates/full-decks/course-module/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Module 04 · Recursion · CS101</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-course-module">
<div class="deck">
<!-- 1. Cover -->
<section class="slide full" data-title="Cover">
<p class="kicker">CS 101 · MODULE 04</p>
<h1 class="h1 mt-s">Recursion: solving<br>problems by <em>calling yourself</em>.</h1>
<p class="lede mt-l" style="max-width:62ch">In this module you'll learn why a function that calls itself is not a trick, but the most natural way to describe problems that contain smaller copies of themselves.</p>
<div class="row mt-l" style="gap:16px">
<span class="pill-academic">~ 45 min read</span>
<span class="pill-academic">prereq · functions, if/else</span>
<span class="pill-academic">lang · Python</span>
</div>
<div class="deck-footer"><span>Dr. A. Rivera · Spring 2026</span><span class="slide-number" data-current="1" data-total="7"></span></div>
</section>
<!-- 2. Objectives -->
<section class="slide" data-title="Objectives">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="current">Define recursion</li>
<li>Identify a base case</li>
<li>Trace a recursive call</li>
<li>Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Module progress</h5>
<p class="dim" style="font-size:13px">Page 2 of 7 · ~5 min in</p>
</aside>
<div class="main">
<p class="kicker">OBJECTIVES</p>
<h2 class="h2 mt-s">By the end, you will be able to…</h2>
<div class="stack mt-l">
<div class="concept-box"><h4>① Explain recursion in one sentence.</h4><p class="dim">"A function that solves a problem by calling itself on a smaller version of that problem."</p></div>
<div class="concept-box"><h4>② Write a base case that always terminates.</h4><p class="dim">Every recursive function must have an exit door, or it runs forever.</p></div>
<div class="concept-box"><h4>③ Trace a call stack on paper.</h4><p class="dim">Given <code>fact(4)</code>, draw the stack frames top-to-bottom.</p></div>
<div class="concept-box"><h4>④ Convert a while-loop to a recursive equivalent.</h4><p class="dim">And explain when one is clearer than the other.</p></div>
</div>
</div>
</section>
<!-- 3. Concept -->
<section class="slide" data-title="Concept">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="current">Identify a base case</li>
<li>Trace a recursive call</li>
<li>Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Key terms</h5>
<p class="dim" style="font-size:13px">base case · recursive case · call stack · tail call</p>
</aside>
<div class="main">
<p class="kicker">CORE CONCEPT</p>
<h2 class="h2 mt-s">Two parts, always.</h2>
<p class="lede mt-m">A recursive function has exactly two things inside it: a <b>base case</b> (when to stop) and a <b>recursive case</b> (how to shrink the problem before calling yourself).</p>
<div class="callout">
<b>Rule of thumb.</b> If you can't name the base case out loud, don't write the recursion yet. Draw it on paper first.
</div>
<div class="grid g2 mt-l">
<div class="concept-box"><h4>Base case</h4><p class="dim">The smallest possible input — one the function answers directly, without calling itself.</p><p class="pill-academic">e.g. <b>n == 0</b></p></div>
<div class="concept-box"><h4>Recursive case</h4><p class="dim">Every other input — delegate to a smaller version of the same problem.</p><p class="pill-academic">e.g. <b>n × fact(n-1)</b></p></div>
</div>
</div>
</section>
<!-- 4. Example -->
<section class="slide" data-title="Example">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="done">Identify a base case</li>
<li class="current">Trace a recursive call</li>
<li>Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Try it yourself</h5>
<p class="dim" style="font-size:13px">Open repl.it and run the code on the right. Then try <code>fact(10)</code>.</p>
</aside>
<div class="main">
<p class="kicker">WORKED EXAMPLE</p>
<h2 class="h2 mt-s">Factorial, 7 lines.</h2>
<div class="code mt-m"><pre style="margin:0"><span class="cmt"># fact(n) = n × (n-1) × … × 1, and fact(0) = 1</span>
<span class="kw">def</span> fact(n):
<span class="kw">if</span> n == <span class="str">0</span>: <span class="cmt"># base case</span>
<span class="kw">return</span> <span class="str">1</span>
<span class="kw">return</span> n * fact(n - <span class="str">1</span>) <span class="cmt"># recursive case</span>
<span class="kw">print</span>(fact(<span class="str">4</span>)) <span class="cmt"># → 24</span></pre></div>
<div class="callout">
<b>Trace fact(4).</b> 4 × fact(3) → 4 × (3 × fact(2)) → 4 × 3 × (2 × fact(1)) → 4 × 3 × 2 × 1 × fact(0) → 4 × 3 × 2 × 1 × 1 = <b>24</b>.
</div>
</div>
</section>
<!-- 5. Exercise -->
<section class="slide" data-title="Exercise">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="done">Identify a base case</li>
<li class="done">Trace a recursive call</li>
<li class="current">Convert loop ↔ recursion</li>
<li>Recognize when recursion helps</li>
</ul>
<h5>Time</h5>
<p class="dim" style="font-size:13px">~10 minutes · solo</p>
</aside>
<div class="main">
<p class="kicker">EXERCISE 4.1</p>
<h2 class="h2 mt-s">Write <em>sum_to(n)</em>.</h2>
<p class="lede mt-m">Return <code>1 + 2 + … + n</code> using recursion — no loops allowed.</p>
<div class="exercise mt-l">
<p style="margin:0;font-size:18px;color:var(--text-1)"><b>Your task</b></p>
<ol style="color:var(--text-2);line-height:1.8;margin:10px 0 0">
<li>Write the base case. What does <code>sum_to(0)</code> return?</li>
<li>Write the recursive case in terms of <code>sum_to(n - 1)</code>.</li>
<li>Test it: <code>sum_to(5) == 15</code>, <code>sum_to(10) == 55</code>.</li>
<li>Bonus: what happens if you call <code>sum_to(-3)</code>? Fix it.</li>
</ol>
</div>
<p class="dim mt-m" style="font-size:14px">Stuck? Remember: a base case is the smallest input you already know the answer to.</p>
</div>
</section>
<!-- 6. Check understanding -->
<section class="slide" data-title="Check">
<aside class="sidebar">
<div class="brand">CS 101 · M04</div>
<h5>Learning objectives</h5>
<ul class="obj-list">
<li class="done">Define recursion</li>
<li class="done">Identify a base case</li>
<li class="done">Trace a recursive call</li>
<li class="done">Convert loop ↔ recursion</li>
<li class="current">Recognize when recursion helps</li>
</ul>
<h5>Self-assess</h5>
<p class="dim" style="font-size:13px">You should get 3/3.</p>
</aside>
<div class="main">
<p class="kicker">CHECK YOUR UNDERSTANDING</p>
<h2 class="h2 mt-s">Which function will recurse forever?</h2>
<div class="mt-l">
<div class="mcq"><div class="letter">A</div><div><b>def f(n): return 1 if n == 0 else n * f(n - 1)</b><p class="dim" style="font-size:13px;margin:4px 0 0">Base case <code>n == 0</code>, shrinks toward it. Terminates.</p></div></div>
<div class="mcq correct"><div class="letter">B</div><div><b>def f(n): return n + f(n + 1)</b><p class="dim" style="font-size:13px;margin:4px 0 0"><b style="color:var(--accent)">✓ Correct.</b> No base case, and <code>n</code> grows — infinite recursion.</p></div></div>
<div class="mcq"><div class="letter">C</div><div><b>def f(n): return n if n < 2 else f(n - 1) + f(n - 2)</b><p class="dim" style="font-size:13px;margin:4px 0 0">Classic Fibonacci. Base case on <code>n < 2</code>. Terminates.</p></div></div>
</div>
</div>
</section>
<!-- 7. Summary -->
<section class="slide full" data-title="Summary">
<p class="kicker">SUMMARY · MODULE 04</p>
<h1 class="h1 mt-s">You can now…</h1>
<div class="grid g2 mt-l">
<div class="concept-box"><h4>✓ Define recursion</h4><p class="dim">A function that calls itself on a smaller input.</p></div>
<div class="concept-box"><h4>✓ Write a safe base case</h4><p class="dim">Every recursion needs an exit door.</p></div>
<div class="concept-box"><h4>✓ Trace a call stack</h4><p class="dim">You can unwind <code>fact(4)</code> by hand.</p></div>
<div class="concept-box"><h4>✓ Judge when to use it</h4><p class="dim">Trees and self-similar problems → recursion. Flat iteration → loop.</p></div>
</div>
<div class="callout mt-l">
<b>Up next · Module 05.</b> Divide & conquer: merge sort. We'll use everything you just learned — but on lists, not numbers.
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>
templates/full-decks-index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><title>Full-Deck Gallery — html-ppt v2</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<style>
html,body{background:#0b0c10;color:#e8ebf4;font-family:var(--font-sans)}
.deck{background:#0b0c10}
.slide{padding:60px 80px;color:#e8ebf4;background:transparent;display:flex;flex-direction:column}
.slide h1{color:#fff;font-size:48px;margin:0 0 6px;letter-spacing:-.02em}
.slide .sub{color:#aab0c0;font-size:18px;margin:0 0 22px}
.frame-wrap{flex:1;border-radius:14px;overflow:hidden;border:1px solid rgba(255,255,255,.12);
box-shadow:0 30px 80px rgba(0,0,0,.5);position:relative;background:#fff}
iframe.tpl{position:absolute;inset:0;width:200%;height:200%;border:0;
transform:scale(.5);transform-origin:top left}
.meta{position:absolute;top:24px;right:40px;font-family:'JetBrains Mono',monospace;
font-size:12px;color:#6a7086;letter-spacing:.14em;text-transform:uppercase;z-index:30}
.tag{display:inline-block;padding:4px 10px;border-radius:999px;background:rgba(255,255,255,.08);
color:#cfd3dc;font-size:11px;margin-right:6px}
.cover{align-items:center;justify-content:center;text-align:center}
.cover h1{font-size:84px;background:linear-gradient(135deg,#60a5fa,#a78bfa,#f472b6);
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.cover p{color:#aab0c0;max-width:60ch;font-size:20px}
.legend{display:flex;gap:10px;flex-wrap:wrap;margin-top:18px}
</style>
</head>
<body>
<div class="deck">
<section class="slide cover">
<p class="kicker" style="color:#a78bfa">HTML-PPT v2 · Full-Deck Gallery</p>
<h1>14 full-deck templates</h1>
<p>Press → to browse. Each slide is a live iframe preview of a complete, multi-slide deck template. Open any <code>templates/full-decks/<name>/index.html</code> to see the full deck, or copy the folder to scaffold your own.</p>
<div class="legend">
<span class="tag">8 extracted from real decks</span>
<span class="tag">6 scenario scaffolds</span>
<span class="tag">scoped .tpl-<name> CSS</span>
<span class="tag">36 themes compatible</span>
</div>
</section>
<!-- Template preview slides generated via JS below -->
</div>
<script>
const TPLS = [
['xhs-white-editorial', '白底杂志风', 'extracted', 'xhs posts, editorial lifestyle'],
['graphify-dark-graph', '暗底知识图谱', 'extracted', 'AI/graph/data products'],
['knowledge-arch-blueprint', '奶油蓝图架构', 'extracted', 'architecture, systems thinking'],
['hermes-cyber-terminal', '暗终端 cyber', 'extracted', 'devtool, honest-review, agent demos'],
['obsidian-claude-gradient', 'GitHub 暗紫渐变', 'extracted', 'tool walkthroughs, LLM product'],
['testing-safety-alert', '红琥珀警示', 'extracted', 'security, incident review, AI safety'],
['xhs-pastel-card', '柔和马卡龙', 'extracted', 'lifestyle, soft emotional'],
['dir-key-nav-minimal', '方向键 8 色极简', 'extracted', 'keynote, one-idea-per-slide'],
['pitch-deck', 'Pitch Deck YC 风', 'scenario', 'fundraising, startup pitch'],
['product-launch', 'Product Launch', 'scenario', 'product announcement, launch keynote'],
['tech-sharing', 'Tech Sharing 技术分享','scenario','internal tech talk, conference talk'],
['weekly-report', 'Weekly Report 周报','scenario', 'status update, business review'],
['xhs-post', '小红书 图文 9 屏 3:4','scenario', 'xiaohongshu / ig carousel'],
['course-module', 'Course Module 教学模块','scenario','online course, workshop module'],
['presenter-mode-reveal', '🎤 Presenter Mode 演讲者模式','scenario','tech sharing, talk with 逐字稿, speaker view']
];
const deck = document.querySelector('.deck');
TPLS.forEach((t,i)=>{
const s = document.createElement('section');
s.className = 'slide';
s.setAttribute('data-title',t[0]);
s.innerHTML = `
<span class="meta">${i+1}/${TPLS.length+1}</span>
<h1>${t[0]}</h1>
<p class="sub">${t[1]} · <span class="tag">${t[2]}</span> ${t[3]}</p>
<div class="frame-wrap">
<iframe class="tpl" src="full-decks/${t[0]}/index.html" loading="eager" title="${t[0]}"></iframe>
</div>`;
deck.appendChild(s);
});
</script>
<script src="../assets/runtime.js"></script>
</body></html>
references/full-decks.md
# Full-Deck Templates
Self-contained multi-slide HTML decks under `templates/full-decks/<name>/`. Each folder contains:
- `index.html` — complete multi-slide deck (cover / section / content / code / chart or diagram / CTA / thanks, 7+ slides)
- `style.css` — scoped with `.tpl-<name>` class prefix so multiple templates can coexist
- `README.md` — short rationale, inspiration, and use guidance
All templates pull the shared `assets/fonts.css`, `assets/base.css`, and `assets/runtime.js` from the skill root. Navigate with `← →` / `space`, use `F` for fullscreen, `O` for overview.
Use these when you want a coherent, opinionated look for an entire deck — not a mix-and-match of layouts. Each template is visually distinctive enough to be identified at a glance.
---
## 1. xhs-white-editorial — 白底杂志风
- **Source inspiration:** `20260409 升级版知识库/小红书图文/v2-白底版/slide_01_cover.html` + `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
- **Key visual traits:** pure-white background, top 10-color rainbow bar, 80-110px display headlines, purple→blue→green→orange→pink gradient text, macaron soft-card set (soft-purple/pink/blue/green/orange), black-on-white `.focus` pills, hero quote box.
- **When to use:** dual-purpose XHS image + horizontal deck; dense text with strong emphasis; Chinese-first audience.
- **Path:** `templates/full-decks/xhs-white-editorial/index.html`
## 2. graphify-dark-graph — 暗底知识图谱
- **Source inspiration:** `20260413-graphify/ppt/graphify.html`
- **Key visual traits:** `#06060c→#0e1020` deep-night gradient, drifting blur orbs, SVG force-directed graph overlay on cover, rainbow-shift gradient headlines, JetBrains Mono command-line glow, glass-morphism cards (warm/blue/green/purple/danger). Accent palette: amber `#e8a87c`, mint `#7ed3a4`, mist-blue `#7eb8da`, lilac `#b8a4d6`.
- **When to use:** dev-tool / CLI / knowledge-graph / data-viz launches; live-demo decks that want an "AI-native + sci-fi + warm" vibe.
- **Path:** `templates/full-decks/graphify-dark-graph/index.html`
## 3. knowledge-arch-blueprint — 奶油蓝图架构
- **Source inspiration:** `20260405-Karpathy-知识库/20260405 架构图v2.html`
- **Key visual traits:** cream paper `#F0EAE0` base, single rust accent `#B5392A`, 48px blueprint grid mask, hard 2px black border cards, pipeline step-boxes with one hero raised, right-side rust insight callout, Playfair serif big numbers, SVG dashed feedback-loop arrows. Zero gradients, zero soft shadows.
- **When to use:** system architecture diagrams, data-flow maps, engineering white-papers; you want a serious, printable, README-friendly feel.
- **Path:** `templates/full-decks/knowledge-arch-blueprint/index.html`
## 4. hermes-cyber-terminal — 暗终端 honest-review
- **Source inspiration:** `20260414-hermes-agent/ppt/hermes-record.html` + `hermes-vs-openclaw.html`
- **Key visual traits:** `#0a0c10` black, 56px cyber grid + CRT vignette + scanlines, window traffic-light chrome, `$ prompt` command-line headlines, mint-green `#7ed3a4` glow big text, JetBrains Mono throughout, stroke-only bar charts, blinking cursor, amber/green/red tag hierarchy, dark code box.
- **When to use:** reviews of CLI / agent / dev tools with trace, diff, and benchmarks; when you want the "honest technical reviewer" voice.
- **Path:** `templates/full-decks/hermes-cyber-terminal/index.html`
## 5. obsidian-claude-gradient — GitHub 暗紫渐变
- **Source inspiration:** `20260406-obsidian-claude/slides.html`
- **Key visual traits:** GitHub-dark `#0d1117`, purple+blue radial ambient plus 60px masked grid, center-aligned layout, purple pill tags, three-stop gradient text `#a855f7→#60a5fa→#34d399`, GitHub-ish code palette (`#010409` bg + purple/blue/orange/green tokens), purple-left-border highlight block.
- **When to use:** developer workflow / MCP / Agent / dev-tool tutorials; feels like GitHub Blog / Linear Changelog; config + steps heavy content.
- **Path:** `templates/full-decks/obsidian-claude-gradient/index.html`
## 6. testing-safety-alert — 红琥珀警示
- **Source inspiration:** `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
- **Key visual traits:** top and bottom 45° red-black hazard stripes, red strike-through negation headlines, L1/L2/L3 green/amber/red tier cards, alert-box with circular status dot, policy-yaml code block with red left border and `bad` keyword highlighting, red/green checklist, Q1 incident stacked bar chart.
- **When to use:** safety / risk / incident post-mortem / red-team / pre-launch AI review / policy-as-code; when the audience needs to feel "this is serious, don't skim".
- **Path:** `templates/full-decks/testing-safety-alert/index.html`
## 7. xhs-pastel-card — 柔和马卡龙慢生活
- **Source inspiration:** `20260412-obsidian-skills/html/xhs-obsidian-skills.html` + pastel patterns shared with `20260409` v2-白底版
- **Key visual traits:** cream `#fef8f1` base, three soft blurred blobs, Playfair italic serif display headlines mixed with sans body, full-color 28px rounded macaron cards (peach / mint / sky / lilac / lemon / rose), italic Playfair `01-04` numerals, SVG donut chart, chip+page topbar.
- **When to use:** lifestyle / personal-growth / slow-living / emotional content; when you want a "magazine, handmade, not-so-techy" feel; themes like rest, pause, softness.
- **Path:** `templates/full-decks/xhs-pastel-card/index.html`
## 8. dir-key-nav-minimal — 方向键 8 色极简
- **Source inspiration:** `20260405-Karpathy-知识库/20260405 演示幻灯片【方向键版】.html`
- **Key visual traits:** 8 slides each on its own mono background (indigo / cream / crimson / emerald / slate / violet / white / charcoal), each with its own accent color, 160px display headline + 4px stubby accent line divider, arrow `→` prefixed Mono list, bottom-left `← →` kbd hint plus bottom-right page label, huge breathing negative space.
- **When to use:** keynote-style minimalist talk where you have something to say and not much to show; one idea per slide; talks / launches / public presentations.
- **Path:** `templates/full-decks/dir-key-nav-minimal/index.html`
---
## Scenario decks (generic, reusable)
These are not extracted from a single source — they are generic scaffolds for the most common presentation jobs. Each is visually distinctive and content-rich out of the box.
| # | Name | Slides | Feel | When to use |
|---|---|---|---|---|
| 9 | `pitch-deck` | 10 | White + blue→purple gradient, YC/VC vibe, big numbers, traction chart | Fundraising, startup pitch, investor meeting |
| 10 | `product-launch` | 8 | Dark hero + light content, warm orange→peach, feature cards, pricing tiers, CTA | Announcing a product, launch keynote |
| 11 | `tech-sharing` | 8 | GitHub-dark, JetBrains Mono, terminal code blocks, agenda + Q&A | 技术分享, internal tech talk, conference talk |
| 12 | `weekly-report` | 7 | Corporate clarity, 8-cell KPI grid, shipped list, 8-week bar chart, next-week table | 周报, team status update, business review |
| 13 | `xhs-post` | 9 | **3:4 @ 810×1080**, warm pastel, dashed sticker cards, page dots | 小红书 图文 post, Instagram carousel |
| 14 | `course-module` | 7 | Warm paper + Playfair serif, persistent left sidebar of learning objectives, MCQ self-check | 教学模块, online course, workshop module |
| 15 | `presenter-mode-reveal` 🎤 | 6 | **演讲者模式专用** · tokyo-night 默认 · 5 主题 T 键切换 · 每页带 150–300 字逐字稿示例 | **技术分享/演讲/课程**—需要按 S 键看逐字稿的场景 ✨ |
Each folder: `index.html`, scoped `style.css` (prefixed `.tpl-<name>`), `README.md`. The `xhs-post` template overrides the default `.slide` box to fixed `810×1080` for 3:4 portrait.
> 🎤 **任何演讲场景(技术分享 / 课程 / 路演)都推荐用 `presenter-mode-reveal`**,或者参考 [presenter-mode.md](./presenter-mode.md) 指南给其他模板加 `<aside class="notes">` 逐字稿。
---
## Authoring notes
- Every template scopes its CSS under `.tpl-<name>` so two or more templates can load on the same page without collisions.
- Swap demo content, but keep the structural classes — they are what gives each template its identity.
- The shared runtime (`assets/runtime.js`) provides keyboard nav, fullscreen, overview grid, theme cycling — you don't need to add any JS.
- Charts are hand-rolled SVG (no CDN dependency). Feel free to replace with chart.js / echarts if you need interactive data.
references/layouts.md
# Layouts catalog
Every layout lives in `templates/single-page/<name>.html` as a fully
functional standalone page with realistic demo data. Open any file directly
in Chrome to see it working.
To compose a new deck: open the file, copy the `<section class="slide">…</section>`
block (or multiple blocks) into your deck HTML, and replace the demo data.
Shared CSS (base, theme, animations) is already wired by `deck.html`.
## Openers & transitions
| file | purpose |
|---|---|
| `cover.html` | Deck cover. Kicker + huge title + lede + pill row. |
| `toc.html` | Table of contents. 2×3 grid of numbered cards. |
| `section-divider.html` | Big numbered section break (02 · Theme). |
## Text-centric
| file | purpose |
|---|---|
| `bullets.html` | Classic bullet list with card-wrapped items. |
| `two-column.html` | Concept + example side by side. |
| `three-column.html` | Three equal pillars with icons. |
| `big-quote.html` | Full-bleed pull quote in editorial-serif style. |
## Numbers & data
| file | purpose |
|---|---|
| `stat-highlight.html` | One giant number + subtitle (uses `.counter` animation). |
| `kpi-grid.html` | 4 KPIs in a row with up/down deltas. |
| `table.html` | Data table with hover rows, right-aligned numerics. |
| `chart-bar.html` | Chart.js bar chart, theme-aware colors. |
| `chart-line.html` | Chart.js dual-line chart with filled area. |
| `chart-pie.html` | Chart.js doughnut + takeaways card. |
| `chart-radar.html` | Chart.js radar comparing 2 products on 6 axes. |
## Code & terminal
| file | purpose |
|---|---|
| `code.html` | Syntax-highlighted code via highlight.js (JS example). |
| `diff.html` | Hand-rolled +/- diff view. |
| `terminal.html` | Terminal window mock with traffic-light header. |
## Diagrams & flows
| file | purpose |
|---|---|
| `flow-diagram.html` | 5-node pipeline with arrows and one highlighted node. |
| `arch-diagram.html` | 3-tier architecture grid. |
| `process-steps.html` | 4 numbered steps in cards. |
| `mindmap.html` | Radial mindmap with SVG path-draw animation. |
## Plans & comparisons
| file | purpose |
|---|---|
| `timeline.html` | 5-point horizontal timeline with dots. |
| `roadmap.html` | 4-column NOW / NEXT / LATER / VISION. |
| `gantt.html` | 12-week gantt chart with 5 parallel tracks. |
| `comparison.html` | Before vs After two-panel card. |
| `pros-cons.html` | Pros and cons two-card layout. |
| `todo-checklist.html` | Checklist with checked/unchecked states. |
## Visuals
| file | purpose |
|---|---|
| `image-hero.html` | Full-bleed hero with Ken Burns gradient background. |
| `image-grid.html` | 7-cell bento grid with gradient placeholders. |
## Closers
| file | purpose |
|---|---|
| `cta.html` | Call-to-action with big gradient headline + buttons. |
| `thanks.html` | Final "Thanks" page with confetti burst. |
## Picking a layout
- **Opener**: `cover.html`, often followed by `toc.html`.
- **Section break**: `section-divider.html` before every major section.
- **Core content**: `bullets.html`, `two-column.html`, `three-column.html`.
- **Show numbers**: `stat-highlight.html` (single) or `kpi-grid.html` (4-up).
- **Show plot**: `chart-bar.html` / `chart-line.html` / `chart-pie.html` / `chart-radar.html`.
- **Show a diff or change**: `comparison.html`, `diff.html`, `pros-cons.html`.
- **Show a plan**: `timeline.html`, `roadmap.html`, `gantt.html`, `process-steps.html`.
- **Show architecture**: `arch-diagram.html`, `flow-diagram.html`, `mindmap.html`.
- **Code / demo**: `code.html`, `terminal.html`.
- **Closer**: `cta.html` → `thanks.html`.
## Naming / structure conventions
- Each slide is `<section class="slide" data-title="...">`.
- Header pills: `<p class="kicker">…</p>`, eyebrow: `<p class="eyebrow">…</p>`.
- Titles: `<h1 class="h1">…</h1>` / `<h2 class="h2">…</h2>`.
- Lede: `<p class="lede">…</p>`.
- Cards: `<div class="card">…</div>` (variants: `card-soft`, `card-outline`, `card-accent`).
- Grids: `.grid.g2`, `.grid.g3`, `.grid.g4`.
- Notes: `<div class="notes">…</div>` per slide.
references/presenter-mode.md
# Presenter Mode Guide · 演讲者模式指南
这份文档说明如何在 html-ppt skill 里做出**带逐字稿的演讲者模式 PPT**。
## 何时使用演讲者模式
当用户的需求涉及以下任何一项时,**优先使用演讲者模式**:
- 提到"**演讲**"、"**分享**"、"**讲稿**"、"**逐字稿**"、"**speaker notes**"
- 提到"**presenter view**"、"**演讲者视图**"、"**演讲者模式**"
- 需要"**30 分钟 / 45 分钟 / 1 小时**的分享"
- 说"我要去给团队讲 xxx"、"要做一场技术分享"、"要做路演"
- 强调"**不想忘词**"、"**怕讲不流畅**"、"**需要提词器**"
如果用户只要做一份"静态好看的 PPT"(例如小红书图文、产品图册、汇报 slides 自己不讲),**不需要**演讲者模式。
## 两种做法
### ✅ 推荐做法:直接用 `presenter-mode-reveal` 模板
```bash
cp -r templates/full-decks/presenter-mode-reveal examples/my-talk
```
这个模板已经预设好所有必需元素:
- 支持 S 键切换演讲者视图
- 5 个主题可用 T 键循环(tokyo-night / dracula / catppuccin-mocha / nord / corporate-clean)
- 左右键翻页
- 每一页都有 150–300 字的示例逐字稿
- 底部有键位提示
直接改内容即可。
### 🔧 进阶做法:给任意已有模板加演讲者模式
html-ppt 的 **S 键演讲者视图是 `runtime.js` 内置的,所有 full-deck 模板都自动支持**。你只需要做两件事:
1. **每张 slide 末尾加 `<aside class="notes">`**(或 `<div class="notes">`),里面写逐字稿
2. **确认 HTML 引入了 `assets/runtime.js`**
```html
<section class="slide">
<h2>你的标题</h2>
<p>内容...</p>
<aside class="notes">
<p>这里是演讲时要说的话,150-300 字...</p>
</aside>
</section>
```
## 逐字稿写作三铁律
这是整个方法论的核心。AI 在帮用户写逐字稿时必须遵守:
### 铁律 1:不是讲稿,是"提示信号"
❌ **错误写法**(像在念稿):
```
大家好,欢迎来到今天的分享。今天我将要给大家介绍一下我们团队在过去三个月做的工作。
首先,我们来看一下背景情况。在过去的三个月中,我们遇到了以下几个问题……
```
✅ **正确写法**(提示信号 + 加粗核心):
```
<p>欢迎!今天分享我们团队<strong>过去 3 个月</strong>的工作。</p>
<p>先说<em>背景</em>——三个月前我们遇到了<strong>三个核心问题</strong>:
延迟高、成本炸、稳定性差。</p>
<p>接下来逐个讲解怎么解的。</p>
```
**差别**:正确版本把关键词加粗,过渡句独立成段,看一眼就能接上。
### 铁律 2:每页 150–300 字
- **少于 150 字**:提示不够,讲到一半会卡
- **多于 300 字**:你根本来不及扫完
- **2–3 分钟/页** 是最舒服的节奏
### 铁律 3:用口语,不用书面语
| ❌ 书面语 | ✅ 口语 |
|---|---|
| 因此 | 所以 |
| 该方案 | 这个方案 |
| 然而 | 但是 / 不过 |
| 进行优化 | 优化一下 |
| 我们将会 | 我们会 / 接下来 |
| 综上所述 | 所以简单来说 |
**检查方法**:写完读一遍,听起来像说话才对。
## 必备 HTML 结构
```html
<!DOCTYPE html>
<html lang="zh-CN" data-themes="tokyo-night,dracula,corporate-clean">
<head>
<meta charset="utf-8">
<title>...</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../../assets/themes/tokyo-night.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="deck">
<section class="slide" data-title="Cover">
<h1>你的标题</h1>
<p>副标题</p>
<aside class="notes">
<p>讲稿段落 1(加<strong>加粗关键词</strong>)。</p>
<p>讲稿段落 2(过渡句独立成段)。</p>
<p>讲稿段落 3(自然收尾,引出下一页)。</p>
</aside>
</section>
<!-- 更多 slide ... -->
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
```
## 演讲者视图显示的内容
按 `S` 键后,**弹出一个独立的演讲者窗口**(原页面保持观众视图不变)。演讲者窗口是 **4 个独立的磁吸卡片**:
```
观众窗口(原页面) 演讲者窗口(磁吸卡片)
┌─────────────────┐ ┌─────────────────────┬──────────────────┐
│ │ │ 🔵 CURRENT │ 🟣 NEXT │
│ 正常 slide │ │ ━━━━━━━━━━━━━━━━ │ ━━━━━━━━━━━━━ │
│ 全屏展示 │◄►│ │ iframe preview │
│ │ │ iframe preview │ (下一页) │
│ │ │ (当前页) ├──────────────────┤
│ │ │ │ 🟠 SPEAKER SCRIPT │
│ │ │ │ ━━━━━━━━━━━━━ │
│ │ ├─────────────────────┤ [大字号逐字稿] │
│ │ │ 🟢 TIMER │ [可滚动] │
│ │ │ ⏱ 12:34 3 / 8 │ │
│ │ │ [← Prev][Next →] │ │
└─────────────────┘ └─────────────────────┴──────────────────┘
↑ BroadcastChannel 双向同步翻页 ↑
```
卡片交互规则:
- **拖动卡片 header**(带彩色圆点和标题的顶部条)→ 移动卡片位置
- **拖动卡片右下角的三角手柄** → 调整卡片大小
- **位置/尺寸自动保存到 localStorage**,下次打开恢复
- 底部 "重置布局" 按钮恢复默认排列
卡片内容:
- 🔵 **CURRENT** — 当前页 **像素级完美预览**(iframe 加载原 HTML 文件的 `?preview=N` 模式,错色不可能)
- 🟣 **NEXT** — 下一页预览,同样像素级完美
- 🟠 **SPEAKER SCRIPT** — 逐字稿,字号 18px,支持 `<strong>` (橘色加粗)、`<em>` (蓝色强调)、`<code>` 等 inline 样式
- 🟢 **TIMER** — 计时器不会丢失焦点,带切页按钮
两窗口同步:在任一窗口按 ← → 翻页,另一个窗口自动同步(BroadcastChannel)。
丝滑翻页:iframe 只加载一次,后续翻页用 `postMessage` 切换可见的 slide,**不重新加载、不闪烁**。
## 键盘快捷键(演讲者模式)
| 键 | 动作 |
|---|---|
| `S` | 打开演讲者窗口(弹出新窗口,原页面保持观众视图) |
| `←` `→` / Space / PgDn | 翻页(即使在演讲者视图里) |
| `T` | 切换主题 |
| `R` | 重置计时器(仅演讲者视图下) |
| `F` | 全屏 |
| `O` | 总览 |
| `Esc` | 关闭所有浮层 |
## 双屏演讲的标准流程
1. 打开 `index.html`,按 `S` → 弹出演讲者窗口
2. 把**观众窗口**(原页面)拖到投影 / 外接屏,按 `F` 全屏
3. 把**演讲者窗口**(弹窗)留在你面前的屏幕
4. 在任一窗口按 ← → 翻页,两边自动同步
5. 演讲者窗口里看逐字稿 + 下一页 + 计时器
> 💡 **为什么预览像素级完美**:每个预览是一个 `<iframe>`,它加载的就是同一个 deck HTML 文件,只是 URL 多了 `?preview=N` 参数。`runtime.js` 检测到这个参数时只渲染第 N 页、隐藏所有 chrome。**iframe 使用与观众视图完全相同的 CSS、主题、字体和 viewport**——颜色和排版保证一致。外层用 CSS `transform: scale()` 把 1920×1080 缩到卡片宽高,等比缩放不变形。
> 💡 **为什么不闪烁**:iframe 初次加载后就常驻,翻页时 presenter 窗口通过 `postMessage({type:'preview-goto', idx:N})` 告诉 iframe 切换到第 N 页。iframe 内的 runtime.js 只切换 `.is-active` class,**不重新加载、不渲染白屏**。
## 常见错误
### ❌ 把逐字稿写在 slide 可见位置
```html
<!-- 错误:这段文字观众会看到 -->
<p style="font-size:12px;color:gray">
这里讲 xxx,然后讲 yyy...
</p>
```
✅ 正确:
```html
<aside class="notes">
<p>这里讲 xxx,然后讲 yyy...</p>
</aside>
```
`.notes` 类默认 `display:none`,只在演讲者视图可见。
### ❌ 忘记引入 runtime.js
没有 `<script src="../../../assets/runtime.js"></script>` = 没有 S 键、没有演讲者视图、没有翻页。
### ❌ 逐字稿用书面语
念出来像 AI 机器人。**写完一定读一遍**。
### ❌ 每页 50 字
提示不够,照样忘词。
### ❌ 每页 500 字
眼睛根本扫不过来,等于没写。
## 用 AI 生成逐字稿的标准 prompt
> "请为每一张 slide 写一段 **150-300 字**的逐字稿,放在 `<aside class="notes">` 里。
> 要求:
> 1. 用**口语**,不要书面语(所以/但是/接下来,不是因此/然而/综上所述)
> 2. 把**核心关键词**用 `<strong>` 加粗
> 3. 过渡句独立成段(每段 1-3 句)
> 4. 读起来像说话,不像念稿
> 5. 结尾要有自然的过渡,引出下一页"
## 推荐搭配
- **主题**:`tokyo-night`(深色,技术分享首选)、`corporate-clean`(浅色,商务汇报)、`dracula`(深色备选)
- **字体**:默认 Noto Sans SC + JetBrains Mono,无需更改
- **动效**:克制使用,`fade-up` / `rise-in` 最自然,不要用 `glitch-in` / `confetti-burst` 之类花哨的
- **页数**:30 分钟分享 = 8–12 页;45 分钟 = 12–16 页;1 小时 = 16–22 页
templates/full-decks/graphify-dark-graph/style.css
/* graphify-dark-graph — 暗底玻璃 + 力导向知识图谱 */
.tpl-graphify-dark-graph{
--gd-bg:#06060c;
--gd-bg2:#0e1020;
--gd-text:#f0ece4;
--gd-text2:#b0a99e;
--gd-text3:#7a746c;
--gd-warm:#e8a87c;
--gd-blue:#7eb8da;
--gd-green:#7ed3a4;
--gd-rose:#d4a0b9;
--gd-purple:#b8a4d6;
--gd-danger:#e07070;
background:var(--gd-bg);
color:var(--gd-text);
font-family:'Inter','Noto Sans SC',-apple-system,sans-serif;
letter-spacing:-.01em;
}
.tpl-graphify-dark-graph .slide{background:linear-gradient(160deg,#08080f,#0e1020 50%,#08080f);color:var(--gd-text);padding:64px 88px;overflow:hidden}
.tpl-graphify-dark-graph .gd-ambient{position:absolute;inset:0;pointer-events:none;z-index:0;overflow:hidden}
.tpl-graphify-dark-graph .gd-orb{position:absolute;border-radius:50%;filter:blur(110px);opacity:.35;animation:gdDrift 22s ease-in-out infinite alternate}
.tpl-graphify-dark-graph .gd-orb-1{width:520px;height:520px;background:radial-gradient(circle,rgba(126,184,218,.55),transparent 70%);top:-12%;left:-6%}
.tpl-graphify-dark-graph .gd-orb-2{width:460px;height:460px;background:radial-gradient(circle,rgba(232,168,124,.45),transparent 70%);top:55%;right:-8%;animation-delay:-6s}
.tpl-graphify-dark-graph .gd-orb-3{width:420px;height:420px;background:radial-gradient(circle,rgba(184,164,214,.4),transparent 70%);bottom:-8%;left:30%;animation-delay:-11s}
@keyframes gdDrift{0%{transform:translate(0,0) scale(1)}100%{transform:translate(25px,-20px) scale(1.08)}}
.tpl-graphify-dark-graph .slide > *{position:relative;z-index:2}
.tpl-graphify-dark-graph .gd-snum{position:absolute;top:28px;right:40px;font-size:12px;letter-spacing:.25em;color:var(--gd-text3);z-index:3}
.tpl-graphify-dark-graph .gd-eyebrow{font-size:13px;letter-spacing:.2em;text-transform:uppercase;color:var(--gd-text3);font-weight:500}
.tpl-graphify-dark-graph .gd-h1{font-size:74px;font-weight:800;line-height:1.08;letter-spacing:-.02em;margin:16px 0 10px;color:var(--gd-text)}
.tpl-graphify-dark-graph .gd-h2{font-size:52px;font-weight:700;line-height:1.12;margin:0 0 14px}
.tpl-graphify-dark-graph .gd-lede{font-size:22px;line-height:1.65;font-weight:300;color:var(--gd-text2);max-width:850px}
.tpl-graphify-dark-graph .gd-rainbow{background:linear-gradient(90deg,#ff0080,#ff4d00,#ff9900,#ffe600,#00c853,#0091ea,#6200ea,#ff0080);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:gdRainbow 4s linear infinite}
@keyframes gdRainbow{0%{background-position:0% center}100%{background-position:200% center}}
.tpl-graphify-dark-graph .gd-grad{background:linear-gradient(135deg,var(--gd-warm),var(--gd-rose),var(--gd-purple));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
.tpl-graphify-dark-graph .gd-accent{color:var(--gd-warm);font-weight:500}
.tpl-graphify-dark-graph .gd-green{color:var(--gd-green)}
.tpl-graphify-dark-graph .gd-blue{color:var(--gd-blue)}
.tpl-graphify-dark-graph .gd-dim{color:var(--gd-text2)}
.tpl-graphify-dark-graph .gd-mono{font-family:'JetBrains Mono',monospace}
.tpl-graphify-dark-graph .gd-glass{position:relative;overflow:hidden;border-radius:20px;padding:22px 26px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(20px) saturate(160%);box-shadow:0 8px 32px rgba(0,0,0,.3),inset 0 1px 0 rgba(255,255,255,.08)}
.tpl-graphify-dark-graph .gd-glass::before{content:'';position:absolute;top:0;left:0;right:0;height:50%;background:linear-gradient(180deg,rgba(255,255,255,.05),transparent);pointer-events:none}
.tpl-graphify-dark-graph .gd-glass-warm{background:rgba(232,168,124,.06);border-color:rgba(232,168,124,.2)}
.tpl-graphify-dark-graph .gd-glass-green{background:rgba(126,211,164,.06);border-color:rgba(126,211,164,.2)}
.tpl-graphify-dark-graph .gd-glass-blue{background:rgba(126,184,218,.06);border-color:rgba(126,184,218,.2)}
.tpl-graphify-dark-graph .gd-grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:24px}
.tpl-graphify-dark-graph .gd-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:24px}
.tpl-graphify-dark-graph .gd-tag{display:inline-block;border-radius:999px;padding:5px 14px;font-size:12px;font-weight:500;margin:2px 4px 2px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);color:var(--gd-text2)}
.tpl-graphify-dark-graph .gd-cmd{font-family:'JetBrains Mono',monospace;font-size:32px;font-weight:700;color:var(--gd-green);text-shadow:0 0 30px rgba(126,211,164,.45),0 0 60px rgba(126,211,164,.15);letter-spacing:-.01em}
.tpl-graphify-dark-graph .gd-big{font-size:120px;font-weight:900;letter-spacing:-.04em;line-height:1}
.tpl-graphify-dark-graph .gd-codebox{background:rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.08);border-radius:14px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.8;color:#d8d4c8}
.tpl-graphify-dark-graph .gd-codebox .cm{color:#6b6a62}
.tpl-graphify-dark-graph .gd-codebox .kw{color:var(--gd-warm)}
.tpl-graphify-dark-graph .gd-codebox .st{color:var(--gd-green)}
.tpl-graphify-dark-graph .gd-codebox .fn{color:var(--gd-blue)}
templates/full-decks/dir-key-nav-minimal/README.md
# dir-key-nav-minimal
8 张幻灯片,每张一个纯色/渐变 mono-background(indigo / cream / crimson / emerald / slate / violet / white / charcoal)。灵感直接来自 `20260405 演示幻灯片【方向键版】.html` —— 八个 `t-*` 主题类,每张幻灯一个背景,方向键切换,极简 editorial 气质。
**Visual traits:** 每张独立背景色 + 单一 accent、巨大 160px 标题无副图、4px 短粗 accent line divider、arrow-prefixed mono list、左下 `← →` 键盘提示 + 右下 page label、全屏 breathing negative space、JetBrains Mono 做数字 / 代码 / 键盘 hint、每个背景有自己的 `.dk-accent` 色。
**Use when:** 有话要说、没太多图、希望用排版节奏推进观众注意力;keynote 式的极简讲稿;每张幻灯只讲一件事;公开分享 / keynote / 演讲稿。
**Source inspiration:** `20260405-Karpathy-知识库/20260405 演示幻灯片【方向键版】.html`.
**Path:** `templates/full-decks/dir-key-nav-minimal/index.html`
templates/full-decks/dir-key-nav-minimal/style.css
/* dir-key-nav-minimal — 方向键极简 · 8 种 mono-background 切换 */
.tpl-dir-key-nav-minimal{
--dk-font:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
--dk-mono:'JetBrains Mono',monospace;
background:#000;
color:#fff;
font-family:var(--dk-font);
}
.tpl-dir-key-nav-minimal .slide{padding:80px 104px;overflow:hidden;position:absolute;inset:0}
/* 8 background themes */
.tpl-dir-key-nav-minimal .t-indigo{background:linear-gradient(135deg,#0f172a 0%,#1e1b4b 100%);color:#fff}
.tpl-dir-key-nav-minimal .t-cream{background:#F5F0E8;color:#1a1a1a}
.tpl-dir-key-nav-minimal .t-crimson{background:linear-gradient(135deg,#7f1d1d 0%,#991b1b 100%);color:#fff}
.tpl-dir-key-nav-minimal .t-emerald{background:linear-gradient(135deg,#052e16 0%,#064e3b 100%);color:#ecfdf5}
.tpl-dir-key-nav-minimal .t-slate{background:linear-gradient(135deg,#0f1923 0%,#1a2942 100%);color:#e6edf3}
.tpl-dir-key-nav-minimal .t-violet{background:linear-gradient(135deg,#1e0a2e 0%,#2e1065 100%);color:#f5f3ff}
.tpl-dir-key-nav-minimal .t-white{background:#ffffff;color:#111216}
.tpl-dir-key-nav-minimal .t-charcoal{background:linear-gradient(135deg,#111827 0%,#1f2937 100%);color:#f3f4f6}
.tpl-dir-key-nav-minimal .dk-snum{position:absolute;top:30px;right:48px;font-size:11px;font-weight:700;letter-spacing:3px;text-transform:uppercase;font-family:var(--dk-mono)}
.tpl-dir-key-nav-minimal .t-cream .dk-snum,
.tpl-dir-key-nav-minimal .t-white .dk-snum{color:#999}
.tpl-dir-key-nav-minimal .t-indigo .dk-snum,
.tpl-dir-key-nav-minimal .t-crimson .dk-snum,
.tpl-dir-key-nav-minimal .t-emerald .dk-snum,
.tpl-dir-key-nav-minimal .t-slate .dk-snum,
.tpl-dir-key-nav-minimal .t-violet .dk-snum,
.tpl-dir-key-nav-minimal .t-charcoal .dk-snum{color:rgba(255,255,255,.38)}
.tpl-dir-key-nav-minimal .dk-eyebrow{font-size:12px;font-weight:700;letter-spacing:3.5px;text-transform:uppercase;opacity:.55;margin-bottom:22px;display:flex;align-items:center;gap:14px}
.tpl-dir-key-nav-minimal .dk-eyebrow::after{content:'';flex:1;max-width:120px;height:1px;background:currentColor;opacity:.3}
.tpl-dir-key-nav-minimal .dk-h0{font-size:160px;font-weight:900;line-height:.9;letter-spacing:-5px;margin:0 0 20px}
.tpl-dir-key-nav-minimal .dk-h1{font-size:100px;font-weight:900;line-height:.98;letter-spacing:-3px;margin:0 0 18px}
.tpl-dir-key-nav-minimal .dk-h2{font-size:72px;font-weight:800;line-height:1.05;letter-spacing:-2px;margin:0 0 16px}
.tpl-dir-key-nav-minimal .dk-lede{font-size:26px;line-height:1.45;opacity:.72;max-width:900px;font-weight:300}
.tpl-dir-key-nav-minimal .dk-lede strong{font-weight:700;opacity:1}
.tpl-dir-key-nav-minimal .dk-big{font-family:var(--dk-mono);font-size:240px;font-weight:800;line-height:.9;letter-spacing:-10px}
.tpl-dir-key-nav-minimal .dk-line{display:block;width:90px;height:4px;background:currentColor;margin:30px 0;opacity:.85}
.tpl-dir-key-nav-minimal .t-indigo .dk-accent{color:#a5b4fc}
.tpl-dir-key-nav-minimal .t-cream .dk-accent{color:#B5392A}
.tpl-dir-key-nav-minimal .t-crimson .dk-accent{color:#fecaca}
.tpl-dir-key-nav-minimal .t-emerald .dk-accent{color:#6ee7b7}
.tpl-dir-key-nav-minimal .t-slate .dk-accent{color:#7dd3fc}
.tpl-dir-key-nav-minimal .t-violet .dk-accent{color:#c4b5fd}
.tpl-dir-key-nav-minimal .t-white .dk-accent{color:#6366f1}
.tpl-dir-key-nav-minimal .t-charcoal .dk-accent{color:#fbbf24}
.tpl-dir-key-nav-minimal .dk-list{list-style:none;padding:0;margin:28px 0 0;font-family:var(--dk-mono);font-size:22px;line-height:2}
.tpl-dir-key-nav-minimal .dk-list li{padding-left:30px;position:relative;font-weight:400;opacity:.85}
.tpl-dir-key-nav-minimal .dk-list li::before{content:'→';position:absolute;left:0;opacity:.5}
.tpl-dir-key-nav-minimal .dk-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:56px;margin-top:36px}
.tpl-dir-key-nav-minimal .dk-col h3{font-size:28px;font-weight:700;margin-bottom:10px}
.tpl-dir-key-nav-minimal .dk-col p{font-size:19px;line-height:1.55;opacity:.72;font-weight:300}
.tpl-dir-key-nav-minimal .dk-code{font-family:var(--dk-mono);font-size:16px;line-height:1.9;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:10px;padding:24px 28px;margin-top:24px;white-space:pre}
.tpl-dir-key-nav-minimal .t-cream .dk-code,
.tpl-dir-key-nav-minimal .t-white .dk-code{background:rgba(0,0,0,.05);border-color:rgba(0,0,0,.1)}
.tpl-dir-key-nav-minimal .dk-keyhint{position:absolute;bottom:34px;left:104px;font-family:var(--dk-mono);font-size:12px;letter-spacing:2px;text-transform:uppercase;opacity:.45}
.tpl-dir-key-nav-minimal .dk-keyhint kbd{display:inline-block;padding:2px 10px;margin:0 3px;border:1px solid currentColor;border-radius:4px;font-size:12px}
.tpl-dir-key-nav-minimal .dk-page{position:absolute;bottom:34px;right:48px;font-family:var(--dk-mono);font-size:12px;letter-spacing:2px;opacity:.45}
templates/.DS_Store
Bud1% @� @� @� @E%DSDB`� @� @� @
templates/full-decks/dir-key-nav-minimal/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dir-Key Nav Minimal</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-dir-key-nav-minimal">
<div class="deck">
<!-- 1. COVER · indigo -->
<section class="slide t-indigo is-active">
<div class="dk-snum">01 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Karpathy LLM Wiki</div>
<h1 class="dk-h0">为什么笔记<br>治不了 <span class="dk-accent">LLM</span></h1>
<span class="dk-line"></span>
<p class="dk-lede">8 种背景、8 张幻灯,一个关于如何把 AI 变成「长期记忆外挂」的最短陈述。<strong>按 → 继续。</strong></p>
</div>
<div class="dk-keyhint">nav · <kbd>←</kbd> <kbd>→</kbd> · <kbd>space</kbd></div>
<div class="dk-page">cover</div>
</section>
<!-- 2. SECTION · cream -->
<section class="slide t-cream">
<div class="dk-snum">02 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Chapter 01</div>
<h1 class="dk-h0">The <span class="dk-accent">Problem</span>.</h1>
<span class="dk-line"></span>
<p class="dk-lede">Token 上限是一个物理事实。你每次和 LLM 说话,它都是一个失忆症患者。</p>
</div>
<div class="dk-keyhint">chapter · 01 / 04</div>
<div class="dk-page">section</div>
</section>
<!-- 3. CONTENT · crimson -->
<section class="slide t-crimson">
<div class="dk-snum">03 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Symptoms</div>
<h2 class="dk-h1">四种你已经<br>受够的<br><span class="dk-accent">遗忘</span>。</h2>
<ul class="dk-list">
<li>昨天聊过的项目,今天重新解释一遍</li>
<li>上下文窗口一到,它开始「编造记忆」</li>
<li>不同 session 之间毫无关联,就像第一次见</li>
<li>你的真正偏好从未被记住,每次都要 re-prompt</li>
</ul>
</div>
<div class="dk-keyhint">content · list</div>
<div class="dk-page">03</div>
</section>
<!-- 4. CONTENT · emerald -->
<section class="slide t-emerald">
<div class="dk-snum">04 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">The Fix</div>
<h2 class="dk-h1">答案不是<br><span class="dk-accent">更大</span> 的窗口。</h2>
<p class="dk-lede" style="margin-top:10px">而是:把你的知识、偏好、历史都<strong>写进文件系统</strong>。<br>让 LLM 每次对话前,先去读那个系统。</p>
<div class="dk-grid-2">
<div class="dk-col"><h3>× 窗口 stuffing</h3><p>把所有东西塞 prompt,贵、慢、最终溢出。</p></div>
<div class="dk-col"><h3>✓ 文件 + 检索</h3><p>按需加载,永远不溢出,结构化可 diff。</p></div>
</div>
</div>
<div class="dk-keyhint">content · compare</div>
<div class="dk-page">04</div>
</section>
<!-- 5. CODE · slate -->
<section class="slide t-slate">
<div class="dk-snum">05 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Minimal Setup</div>
<h2 class="dk-h2"><span class="dk-accent">4 行</span> YAML<br>就能开始。</h2>
<pre class="dk-code">memory:
root: ~/.llm-wiki
format: markdown
retrieval: hybrid # embedding + bm25</pre>
<p class="dk-lede" style="margin-top:16px;font-size:20px">你现在拥有一个会随时间增长的 <strong>第二大脑</strong>。每次对话它都会被读、被更新。</p>
</div>
<div class="dk-keyhint">content · code</div>
<div class="dk-page">05</div>
</section>
<!-- 6. CHART · violet — big number with bar -->
<section class="slide t-violet">
<div class="dk-snum">06 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">30-day result</div>
<div class="dk-big dk-accent">87%</div>
<p class="dk-lede" style="margin-top:14px;font-size:26px">的 re-explain 被消除。平均每次对话节省 <strong>4.2 分钟</strong> 的 re-context。</p>
<svg viewBox="0 0 900 80" style="width:100%;max-width:900px;margin-top:30px">
<rect x="0" y="30" width="900" height="22" rx="11" fill="rgba(255,255,255,.12)"/>
<rect x="0" y="30" width="783" height="22" rx="11" fill="#c4b5fd"/>
<text x="792" y="47" font-family="JetBrains Mono" font-size="16" fill="#c4b5fd" font-weight="700">87%</text>
</svg>
</div>
<div class="dk-keyhint">chart · big-num</div>
<div class="dk-page">06</div>
</section>
<!-- 7. CTA · white -->
<section class="slide t-white">
<div class="dk-snum">07 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">Start tonight</div>
<h2 class="dk-h1">开始<br>你的 <span class="dk-accent">wiki</span>。</h2>
<span class="dk-line"></span>
<p class="dk-lede">不是装又一个插件。是决定:从今晚起,<strong>你的所有 AI 对话都要有一个共同的 vault</strong>。</p>
<pre class="dk-code" style="font-size:18px">$ mkdir ~/llm-wiki && cd ~/llm-wiki
$ git init
$ echo "# my brain" > README.md</pre>
</div>
<div class="dk-keyhint">cta · three-commands</div>
<div class="dk-page">07</div>
</section>
<!-- 8. THANKS · charcoal -->
<section class="slide t-charcoal">
<div class="dk-snum">08 / 08</div>
<div style="margin:auto 0">
<div class="dk-eyebrow">End · thanks for staying</div>
<h1 class="dk-h0"><span class="dk-accent">謝謝</span>。</h1>
<span class="dk-line"></span>
<p class="dk-lede">Karpathy 的原始 thread + 我的 vault 结构都在 <strong>github.com/lewis/llm-wiki</strong>。欢迎按 ← 再看一遍。</p>
</div>
<div class="dk-keyhint">press <kbd>←</kbd> to rewind · <kbd>F</kbd> for fullscreen</div>
<div class="dk-page">fin</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/graphify-dark-graph/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Graphify Dark Graph</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-graphify-dark-graph">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-2"></div><div class="gd-orb gd-orb-3"></div></div>
<!-- live force-directed graph bg -->
<svg viewBox="0 0 1600 900" style="position:absolute;inset:0;width:100%;height:100%;opacity:.38;z-index:1" xmlns="http://www.w3.org/2000/svg">
<g stroke="#7eb8da" stroke-width="1" stroke-opacity=".5" fill="none">
<line x1="300" y1="200" x2="520" y2="340"/>
<line x1="520" y1="340" x2="780" y2="260"/>
<line x1="780" y1="260" x2="1040" y2="420"/>
<line x1="520" y1="340" x2="640" y2="560"/>
<line x1="640" y1="560" x2="900" y2="620"/>
<line x1="900" y1="620" x2="1040" y2="420"/>
<line x1="1040" y1="420" x2="1260" y2="300"/>
<line x1="1260" y1="300" x2="1380" y2="500"/>
<line x1="900" y1="620" x2="1120" y2="720"/>
<line x1="300" y1="200" x2="200" y2="420"/>
<line x1="200" y1="420" x2="360" y2="640"/>
<line x1="360" y1="640" x2="640" y2="560"/>
</g>
<g>
<circle cx="300" cy="200" r="10" fill="#e8a87c"/>
<circle cx="520" cy="340" r="14" fill="#7eb8da"/>
<circle cx="780" cy="260" r="9" fill="#7ed3a4"/>
<circle cx="1040" cy="420" r="18" fill="#b8a4d6"/>
<circle cx="640" cy="560" r="11" fill="#d4a0b9"/>
<circle cx="900" cy="620" r="12" fill="#e8a87c"/>
<circle cx="1260" cy="300" r="8" fill="#7ed3a4"/>
<circle cx="1380" cy="500" r="10" fill="#7eb8da"/>
<circle cx="1120" cy="720" r="9" fill="#d4a0b9"/>
<circle cx="200" cy="420" r="8" fill="#b8a4d6"/>
<circle cx="360" cy="640" r="11" fill="#7eb8da"/>
</g>
</svg>
<div class="gd-snum">01 / 08</div>
<div style="margin-top:auto">
<p class="gd-eyebrow">Tech Sharing · 纯干货</p>
<h1 class="gd-h1" style="font-size:88px"><span class="gd-rainbow">手把手用 Graphify<br>搭建个人知识图谱</span></h1>
<p class="gd-lede" style="margin-top:20px">一行命令 · 全多模态 · 诚实审计 —— <span class="gd-accent">把任何文件夹变成可导航的知识网络。</span></p>
<p class="gd-eyebrow" style="margin-top:26px">↑ 背景就是 Graphify 真实跑出来的知识图谱</p>
</div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-2"></div></div>
<div class="gd-snum">02 / 08</div>
<div style="margin:auto 0">
<div class="gd-eyebrow">Part 01</div>
<h1 class="gd-h1" style="font-size:120px">Why <span class="gd-grad">Graph</span>?</h1>
<p class="gd-lede">folder → tree → graph,人类认知的下一步</p>
</div>
</section>
<!-- 3. CONTENT — plugin grid -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div><div class="gd-orb gd-orb-3"></div></div>
<div class="gd-snum">03 / 08</div>
<p class="gd-eyebrow">Feature Map</p>
<h2 class="gd-h2">一个工具,<span class="gd-grad">四件事</span></h2>
<div class="gd-grid-4">
<div class="gd-glass gd-glass-warm"><div style="font-size:30px">📂</div><h4 style="margin:10px 0 6px">Folder Ingest</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">递归扫描任意路径,支持 md / pdf / 代码 / 图片</p></div>
<div class="gd-glass gd-glass-blue"><div style="font-size:30px">🧠</div><h4 style="margin:10px 0 6px">Entity Extract</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">用 LLM 抽概念、人物、事件、关系</p></div>
<div class="gd-glass gd-glass-green"><div style="font-size:30px">🕸️</div><h4 style="margin:10px 0 6px">Force Graph</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">D3 力导向,点击即跳转原文</p></div>
<div class="gd-glass"><div style="font-size:30px">🔍</div><h4 style="margin:10px 0 6px">Audit Trail</h4><p class="gd-dim" style="font-size:13px;line-height:1.55">每条边都能追溯到 source span</p></div>
</div>
<div class="gd-glass gd-glass-warm" style="margin-top:24px"><p style="font-size:18px;line-height:1.6">它不是「又一个 RAG」—— 它是 <span class="gd-accent">把检索结果画出来,让你一眼就知道信息长什么样</span>。</p></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div></div>
<div class="gd-snum">04 / 08</div>
<p class="gd-eyebrow">One command</p>
<h2 class="gd-h2">从 0 到图谱,<span class="gd-grad">大概 90 秒</span></h2>
<p class="gd-cmd" style="margin:16px 0 22px">$ graphify ~/notes --out ./graph</p>
<pre class="gd-codebox"><span class="cm"># graphify.config.yaml</span>
<span class="kw">ingest</span>:
paths: [<span class="st">~/notes</span>, <span class="st">~/code/docs</span>]
include: [<span class="st">"*.md"</span>, <span class="st">"*.pdf"</span>, <span class="st">"*.py"</span>]
<span class="kw">extract</span>:
model: <span class="st">claude-opus-4-6</span>
schema: [<span class="st">concept</span>, <span class="st">person</span>, <span class="st">event</span>, <span class="st">relation</span>]
<span class="kw">render</span>:
engine: <span class="st">d3-force</span>
audit: <span class="fn">true</span> <span class="cm"># 每条边带 source span</span></pre>
</section>
<!-- 5. CHART — race diagram -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-3"></div></div>
<div class="gd-snum">05 / 08</div>
<p class="gd-eyebrow">Efficiency Race</p>
<h2 class="gd-h2">没有知识库 vs 有知识库</h2>
<div style="max-width:900px;margin-top:30px">
<div style="display:flex;align-items:center;gap:16px;margin-bottom:20px">
<div style="width:110px;text-align:right;font-weight:700;color:var(--gd-danger)">没有<br>知识库</div>
<div style="flex:1;position:relative;height:70px;background:rgba(224,112,112,.06);border:1px solid rgba(224,112,112,.2);border-radius:16px">
<div style="position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:32px">🛵</div>
<div style="position:absolute;left:72px;top:50%;transform:translateY(-50%);color:var(--gd-danger);font-size:14px">反复喂信息…整理…又忘了…</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:16px">
<div style="width:110px;text-align:right;font-weight:700;color:var(--gd-green)">有<br>知识库</div>
<div style="flex:1;position:relative;height:70px;background:rgba(126,211,164,.06);border:1px solid rgba(126,211,164,.25);border-radius:16px">
<div style="position:absolute;right:16px;top:50%;transform:translateY(-50%);font-size:32px">🏎️</div>
<div style="position:absolute;right:72px;top:50%;transform:translateY(-50%);color:var(--gd-green);font-size:14px">AI 自己找 → 确认 → 干活!</div>
</div>
</div>
</div>
<div class="gd-grid-3" style="margin-top:36px">
<div class="gd-glass gd-glass-warm"><div class="gd-big gd-grad">5×</div><p class="gd-dim" style="margin-top:6px">速度提升</p></div>
<div class="gd-glass gd-glass-green"><div class="gd-big gd-grad">-80%</div><p class="gd-dim" style="margin-top:6px">重复喂信息</p></div>
<div class="gd-glass gd-glass-blue"><div class="gd-big gd-grad">∞</div><p class="gd-dim" style="margin-top:6px">记忆持久化</p></div>
</div>
</section>
<!-- 6. PIPELINE -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div></div>
<div class="gd-snum">06 / 08</div>
<p class="gd-eyebrow">Pipeline</p>
<h2 class="gd-h2">端到端 <span class="gd-grad">4 步走</span></h2>
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-top:36px">
<div class="gd-glass" style="flex:1;text-align:center"><div style="font-size:34px">📂</div><div style="font-weight:600;margin-top:8px">Scan</div><div class="gd-dim" style="font-size:13px">递归读文件</div></div>
<div style="color:var(--gd-text3);font-size:24px">→</div>
<div class="gd-glass gd-glass-blue" style="flex:1;text-align:center"><div style="font-size:34px">🔬</div><div style="font-weight:600;margin-top:8px">Extract</div><div class="gd-dim" style="font-size:13px">LLM 抽实体</div></div>
<div style="color:var(--gd-text3);font-size:24px">→</div>
<div class="gd-glass gd-glass-green" style="flex:1;text-align:center"><div style="font-size:34px">🕸️</div><div style="font-weight:600;margin-top:8px">Build</div><div class="gd-dim" style="font-size:13px">构图 + 去重</div></div>
<div style="color:var(--gd-text3);font-size:24px">→</div>
<div class="gd-glass gd-glass-warm" style="flex:1;text-align:center"><div style="font-size:34px">🎨</div><div style="font-weight:600;margin-top:8px">Render</div><div class="gd-dim" style="font-size:13px">D3 交互图</div></div>
</div>
<div class="gd-glass" style="margin-top:32px"><p style="font-size:16px;line-height:1.6;color:var(--gd-text2)">每一步都有 audit log:你永远知道某个节点为什么存在、它来自哪个文件的哪一行。</p></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-1"></div><div class="gd-orb gd-orb-3"></div></div>
<div class="gd-snum">07 / 08</div>
<p class="gd-eyebrow">Try it tonight</p>
<h2 class="gd-h1" style="font-size:80px">Graphify <span class="gd-grad">your folders</span></h2>
<p class="gd-cmd" style="margin-top:22px">$ npm i -g @lewis/graphify</p>
<p class="gd-cmd" style="margin-top:10px;color:var(--gd-warm);text-shadow:0 0 30px rgba(232,168,124,.45)">$ graphify ~/obsidian-vault</p>
<div style="margin-top:32px">
<span class="gd-tag">#knowledge-graph</span>
<span class="gd-tag">#open-source</span>
<span class="gd-tag">#claude-agent</span>
<span class="gd-tag">#obsidian</span>
<span class="gd-tag">#d3-force</span>
</div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="gd-ambient"><div class="gd-orb gd-orb-2"></div></div>
<div class="gd-snum">08 / 08</div>
<div style="margin:auto 0;text-align:center">
<div class="gd-big gd-rainbow" style="font-size:180px">Thanks.</div>
<p class="gd-lede" style="margin:28px auto 0">github.com/lewis/graphify · 欢迎 star / issue / PR</p>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/course-module/style.css
/* course-module — academic but friendly */
.tpl-course-module{
--bg:#fbfaf6;--bg-soft:#f4f1e8;--surface:#ffffff;--surface-2:#f6f3ea;
--border:rgba(60,45,20,.12);--border-strong:rgba(60,45,20,.24);
--text-1:#2a2418;--text-2:#5a5140;--text-3:#8a7f68;
--accent:#2d7d6e;--accent-2:#d88a3a;--accent-3:#c4593f;
--grad:linear-gradient(135deg,#2d7d6e,#4ea893);
--radius:14px;--radius-lg:20px;
--shadow:0 12px 30px rgba(60,45,20,.07);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-course-module .slide{padding:64px 80px;background:var(--bg);display:grid;grid-template-columns:260px 1fr;gap:56px;align-content:start}
.tpl-course-module .slide.full{grid-template-columns:1fr;display:flex;flex-direction:column;justify-content:center}
.tpl-course-module .sidebar{border-right:1px solid var(--border);padding-right:32px;position:relative}
.tpl-course-module .sidebar .brand{font-family:'Playfair Display',serif;font-size:22px;font-weight:700;color:var(--accent)}
.tpl-course-module .sidebar .brand::before{content:"✦ ";color:var(--accent-2)}
.tpl-course-module .sidebar h5{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.12em;color:var(--text-3);margin:32px 0 12px}
.tpl-course-module .obj-list{list-style:none;padding:0;margin:0;font-size:13px;color:var(--text-2);line-height:1.5}
.tpl-course-module .obj-list li{padding:8px 0 8px 22px;position:relative;border-bottom:1px dashed var(--border)}
.tpl-course-module .obj-list li::before{content:"○";position:absolute;left:0;top:8px;color:var(--accent)}
.tpl-course-module .obj-list li.done::before{content:"●";color:var(--accent)}
.tpl-course-module .obj-list li.current{color:var(--text-1);font-weight:700}
.tpl-course-module .obj-list li.current::before{content:"▸";color:var(--accent-2)}
.tpl-course-module .main{min-width:0}
.tpl-course-module .h1{font-family:'Playfair Display',serif;font-size:72px;line-height:1.02;font-weight:800;letter-spacing:-.02em;color:var(--text-1)}
.tpl-course-module .h2{font-family:'Playfair Display',serif;font-size:48px;line-height:1.1;font-weight:700;letter-spacing:-.015em;color:var(--text-1)}
.tpl-course-module h3,.tpl-course-module h4{color:var(--text-1)}
.tpl-course-module .kicker{color:var(--accent-2);font-size:12px;font-weight:700;letter-spacing:.14em}
.tpl-course-module .lede{font-size:20px;color:var(--text-2);line-height:1.7}
.tpl-course-module .callout{border-left:4px solid var(--accent-2);background:var(--surface-2);padding:20px 24px;border-radius:0 var(--radius) var(--radius) 0;margin-top:24px}
.tpl-course-module .callout b{color:var(--accent-2)}
.tpl-course-module .concept-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px 26px;box-shadow:var(--shadow)}
.tpl-course-module .concept-box h4{margin-top:0;color:var(--accent)}
.tpl-course-module .exercise{background:#fff8ed;border:1.5px dashed var(--accent-2);border-radius:var(--radius);padding:24px 28px}
.tpl-course-module .exercise::before{content:"✎ Exercise";display:block;font-size:12px;font-weight:700;letter-spacing:.12em;color:var(--accent-2);margin-bottom:10px;text-transform:uppercase}
.tpl-course-module .code{background:#2a2418;color:#f4f1e8;border-radius:var(--radius);padding:20px 24px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.7;overflow:auto}
.tpl-course-module .code .cmt{color:#8a7f68;font-style:italic}
.tpl-course-module .code .kw{color:#e8a770}
.tpl-course-module .code .str{color:#8ec6b2}
.tpl-course-module .mcq{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px 22px;margin-bottom:10px;display:flex;gap:14px;align-items:flex-start;cursor:pointer}
.tpl-course-module .mcq .letter{flex:none;width:28px;height:28px;border-radius:50%;border:2px solid var(--text-3);display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;color:var(--text-2)}
.tpl-course-module .mcq.correct{border-color:var(--accent);background:rgba(45,125,110,.06)}
.tpl-course-module .mcq.correct .letter{border-color:var(--accent);background:var(--accent);color:#fff}
.tpl-course-module .pill-academic{display:inline-block;padding:4px 12px;border-radius:4px;background:var(--surface-2);border:1px solid var(--border);font-size:12px;color:var(--text-2);font-family:'JetBrains Mono',monospace}
.tpl-course-module .slide.full .h1{font-size:88px}
.tpl-course-module .deck-footer{color:var(--text-3)}
templates/full-decks/knowledge-arch-blueprint/README.md
# knowledge-arch-blueprint
奶油纸 (`#F0EAE0`) 底 + 锈红 (`#B5392A`) 单一 accent + 硬黑描边卡片 + 虚线反馈回路箭头。灵感来自 `20260405 架构图v2.html` —— 那是一张真正的「技术白皮书架构图」,像建筑蓝图。
**Visual traits:** 暖米色纸底、微弱 48px 网格做 blueprint 感、硬朗 2px 黑边卡片、pipeline step-box 一字排开配 hero box 凸起、右上 insight 红色 callout、大小写 kicker 2.5-4px 字距、SVG 反馈回路虚线 + 箭头、Playfair 大字号衬线数据、无渐变无阴影极度克制。
**Use when:** 讲系统架构、数据流向、流程拆解;你想让内容看起来像一份正经技术白皮书而不是营销贴;需要严肃感、印刷感、可直接截图塞进 README。
**Source inspiration:** `20260405-Karpathy-知识库/20260405 架构图v2.html`.
**Path:** `templates/full-decks/knowledge-arch-blueprint/index.html`
templates/full-decks/hermes-cyber-terminal/style.css
/* hermes-cyber-terminal — 暗终端 + 霓虹绿青 + 扫描线 */
.tpl-hermes-cyber-terminal{
--hc-bg:#0a0c10;
--hc-bg2:#15151b;
--hc-surface:#12141a;
--hc-border:rgba(126,211,164,.18);
--hc-ink:#e4e2d8;
--hc-ink2:#8a8892;
--hc-green:#7ed3a4;
--hc-cyan:#64dfdf;
--hc-amber:#e9c58a;
--hc-rose:#d4a0b9;
--hc-red:#ff6b6b;
background:var(--hc-bg);
color:var(--hc-ink);
font-family:'JetBrains Mono','SF Mono','Inter','Noto Sans SC',monospace;
}
.tpl-hermes-cyber-terminal .slide{background:var(--hc-bg);color:var(--hc-ink);padding:60px 84px;overflow:hidden}
.tpl-hermes-cyber-terminal .hc-scanlines{position:absolute;inset:0;pointer-events:none;z-index:3;background:repeating-linear-gradient(180deg,transparent 0,transparent 3px,rgba(126,211,164,.025) 3px,rgba(126,211,164,.025) 4px);mix-blend-mode:screen}
.tpl-hermes-cyber-terminal .hc-grid{position:absolute;inset:0;pointer-events:none;opacity:.35;background-image:linear-gradient(rgba(126,211,164,.08) 1px,transparent 1px),linear-gradient(90deg,rgba(126,211,164,.08) 1px,transparent 1px);background-size:56px 56px;mask-image:radial-gradient(ellipse at 50% 50%,black 30%,transparent 80%)}
.tpl-hermes-cyber-terminal .hc-vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at 50% 50%,transparent 50%,rgba(0,0,0,.6) 100%)}
.tpl-hermes-cyber-terminal .slide > *{position:relative;z-index:2}
.tpl-hermes-cyber-terminal .hc-chrome{display:flex;align-items:center;justify-content:space-between;margin-bottom:18px;font-size:11px;color:var(--hc-ink2);letter-spacing:.18em;text-transform:uppercase}
.tpl-hermes-cyber-terminal .hc-chrome .dots{display:flex;gap:8px}
.tpl-hermes-cyber-terminal .hc-chrome .dots span{width:11px;height:11px;border-radius:50%;background:#2a2d33}
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(1){background:#ff5f57}
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(2){background:#febc2e}
.tpl-hermes-cyber-terminal .hc-chrome .dots span:nth-child(3){background:var(--hc-green)}
.tpl-hermes-cyber-terminal .hc-prompt{color:var(--hc-green);font-weight:500}
.tpl-hermes-cyber-terminal .hc-prompt::before{content:'$ ';color:var(--hc-cyan)}
.tpl-hermes-cyber-terminal .hc-h1{font-family:'JetBrains Mono',monospace;font-size:72px;font-weight:700;line-height:1.05;letter-spacing:-.02em;color:var(--hc-green);text-shadow:0 0 30px rgba(126,211,164,.35),0 0 60px rgba(126,211,164,.1);margin:14px 0 12px}
.tpl-hermes-cyber-terminal .hc-h2{font-size:46px;font-weight:600;color:var(--hc-ink);margin:0 0 10px;letter-spacing:-.015em}
.tpl-hermes-cyber-terminal .hc-h3{font-size:22px;font-weight:600;color:var(--hc-amber);margin:0 0 10px}
.tpl-hermes-cyber-terminal .hc-lede{font-size:18px;line-height:1.7;color:var(--hc-ink2);max-width:780px;font-family:'Inter','Noto Sans SC',sans-serif}
.tpl-hermes-cyber-terminal .hc-cursor{display:inline-block;width:12px;height:1em;background:var(--hc-green);vertical-align:middle;margin-left:6px;animation:hcBlink 1s steps(2) infinite}
@keyframes hcBlink{50%{opacity:0}}
.tpl-hermes-cyber-terminal .hc-card{background:var(--hc-surface);border:1px solid var(--hc-border);border-radius:10px;padding:20px 24px;position:relative}
.tpl-hermes-cyber-terminal .hc-card::before{content:'';position:absolute;top:-1px;left:12px;right:12px;height:2px;background:linear-gradient(90deg,transparent,var(--hc-green),transparent)}
.tpl-hermes-cyber-terminal .hc-card .lbl{font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:var(--hc-ink2);margin-bottom:8px}
.tpl-hermes-cyber-terminal .hc-card .val{font-size:22px;font-weight:700;color:var(--hc-green);font-family:'JetBrains Mono',monospace}
.tpl-hermes-cyber-terminal .hc-card .desc{font-size:13px;color:var(--hc-ink2);margin-top:10px;line-height:1.55;font-family:'Inter',sans-serif}
.tpl-hermes-cyber-terminal .hc-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:24px}
.tpl-hermes-cyber-terminal .hc-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:24px}
.tpl-hermes-cyber-terminal .hc-codebox{background:#0c0d12;border:1px solid var(--hc-border);border-radius:10px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;color:#d8d4c8;box-shadow:inset 0 0 60px rgba(126,211,164,.04)}
.tpl-hermes-cyber-terminal .hc-codebox .cm{color:#5a6068}
.tpl-hermes-cyber-terminal .hc-codebox .kw{color:var(--hc-amber)}
.tpl-hermes-cyber-terminal .hc-codebox .st{color:var(--hc-green)}
.tpl-hermes-cyber-terminal .hc-codebox .fn{color:var(--hc-cyan)}
.tpl-hermes-cyber-terminal .hc-codebox .var{color:var(--hc-rose)}
.tpl-hermes-cyber-terminal .hc-codebox .hl{color:#fff;background:rgba(126,211,164,.15);padding:0 4px;border-radius:3px}
.tpl-hermes-cyber-terminal .hc-tag{display:inline-block;font-family:'JetBrains Mono',monospace;font-size:11px;padding:3px 10px;border:1px solid var(--hc-border);border-radius:4px;color:var(--hc-green);background:rgba(126,211,164,.04);margin:2px 6px 2px 0;text-transform:uppercase;letter-spacing:.1em}
.tpl-hermes-cyber-terminal .hc-tag.amber{color:var(--hc-amber);border-color:rgba(233,197,138,.2);background:rgba(233,197,138,.04)}
.tpl-hermes-cyber-terminal .hc-tag.red{color:var(--hc-red);border-color:rgba(255,107,107,.25);background:rgba(255,107,107,.05)}
.tpl-hermes-cyber-terminal .hc-big{font-family:'JetBrains Mono',monospace;font-size:140px;font-weight:700;line-height:1;color:var(--hc-green);text-shadow:0 0 40px rgba(126,211,164,.4),0 0 80px rgba(126,211,164,.15);letter-spacing:-.04em}
.tpl-hermes-cyber-terminal .hc-footer{position:absolute;left:84px;right:84px;bottom:32px;display:flex;justify-content:space-between;font-size:10px;color:var(--hc-ink2);letter-spacing:.2em;text-transform:uppercase;border-top:1px solid rgba(126,211,164,.1);padding-top:14px}
templates/full-decks/obsidian-claude-gradient/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Obsidian × Claude Gradient</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-obsidian-claude-gradient">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">01 / 08</div>
<div class="oc-tag">● OBSIDIAN × CLAUDE · 第二大脑</div>
<h1 class="oc-h1">把 Obsidian 和 Claude<br>拧成 <span class="oc-g">一条神经</span></h1>
<p class="oc-sub">不是又一个 AI 笔记插件 —— 是让 Claude 真正理解你 vault 的结构、链接、双向引用,<br>然后在你想写东西之前就把资料准备好。</p>
<div style="margin-top:32px">
<span class="oc-pill">🧠 Markdown-native</span>
<span class="oc-pill">⚡ MCP-ready</span>
<span class="oc-pill">🔗 双链理解</span>
</div>
</section>
<!-- 2. SECTION -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">02 / 08</div>
<div class="oc-tag">● CHAPTER 01</div>
<h1 class="oc-h1" style="font-size:110px">Why <span class="oc-g">not</span> Notion?</h1>
<p class="oc-sub">当你的知识多到会互相引用时,<br>「文件夹」就不够了,「数据库」也不是答案。</p>
</section>
<!-- 3. CONTENT — compare cards -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">03 / 08</div>
<div class="oc-tag">● COMPARE</div>
<h2 class="oc-h2">Notion vs <span class="oc-g">Obsidian</span> · 对 AI 友好度</h2>
<div class="oc-grid-2">
<div class="oc-card">
<span class="oc-badge oc-bb">NOTION</span>
<h4 style="font-size:20px;margin-bottom:10px">数据库原生</h4>
<p style="color:var(--oc-dim);font-size:14px;line-height:1.65">适合结构化任务、团队协作,但是——<br>• AI 要走 API,拿不到实时全文<br>• 嵌套块结构复杂,token 成本高<br>• 本地化差,没法当长期记忆</p>
</div>
<div class="oc-card" style="border-color:rgba(168,85,247,.35);background:rgba(124,58,237,.05)">
<span class="oc-badge oc-bp">OBSIDIAN</span>
<h4 style="font-size:20px;margin-bottom:10px">纯 Markdown + 双链</h4>
<p style="color:var(--oc-dim);font-size:14px;line-height:1.65">对 AI 天生友好 ——<br>• 所有东西就是文件,Claude 直接读<br>• 双链 = 天然 graph,抽实体几乎零成本<br>• 离线、可 git、可 diff、可回滚</p>
</div>
</div>
<div class="oc-hl" style="margin-top:26px">💡 <b>关键洞察:</b>AI 不需要「更聪明的数据库」,它需要「能被它自己读懂的文件系统」。</div>
</section>
<!-- 4. STEPS -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">04 / 08</div>
<div class="oc-tag">● SETUP · 4 STEPS</div>
<h2 class="oc-h2">从 0 到第一次「AI 写笔记」</h2>
<div class="oc-steps">
<div class="oc-step"><div class="oc-sn">1</div><div class="oc-sc"><h4>装 Obsidian + 开 Local REST API 插件</h4><p>社区插件,一个勾就开。它让外部进程能 read/write 你的 vault。</p></div></div>
<div class="oc-step"><div class="oc-sn">2</div><div class="oc-sc"><h4>接 Claude Desktop + obsidian-mcp server</h4><p>MCP 一个配置文件就能接,token 填 vault 的 api key。</p></div></div>
<div class="oc-step"><div class="oc-sn">3</div><div class="oc-sc"><h4>装 5 个 obsidian-skills</h4><p>markdown / bases / canvas / cli / defuddle —— 让 Claude 知道怎么正确使用 Obsidian。</p></div></div>
<div class="oc-step"><div class="oc-sn">4</div><div class="oc-sc"><h4>让 Claude 自己整理一次</h4><p>「帮我把最近 10 篇笔记里的重复概念合并,生成一张新的 MOC」—— 90 秒出结果。</p></div></div>
</div>
</section>
<!-- 5. CODE -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">05 / 08</div>
<div class="oc-tag">● MCP CONFIG</div>
<h2 class="oc-h2">claude_desktop_config.json</h2>
<pre class="oc-code"><span class="cm">// ~/Library/Application Support/Claude/claude_desktop_config.json</span>
{
<span class="cc">"mcpServers"</span>: {
<span class="cc">"obsidian"</span>: {
<span class="cc">"command"</span>: <span class="cs">"npx"</span>,
<span class="cc">"args"</span>: [<span class="cs">"-y"</span>, <span class="cs">"@modelcontextprotocol/server-obsidian"</span>],
<span class="cc">"env"</span>: {
<span class="cc">"OBSIDIAN_API_KEY"</span>: <span class="cs">"xxxxxxxxxxxxxxxx"</span>,
<span class="cc">"OBSIDIAN_HOST"</span>: <span class="cs">"http://127.0.0.1:27123"</span>
}
}
}
}</pre>
<p class="oc-sub" style="margin-top:18px">重启 Claude Desktop,输入 <b style="color:var(--oc-accent3)">/mcp</b>,你会看到 obsidian 已连。</p>
</section>
<!-- 6. STATS -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">06 / 08</div>
<div class="oc-tag">● 3 MONTHS IN</div>
<h2 class="oc-h2">跑了 90 天,我的 <span class="oc-g">vault 数据</span></h2>
<div class="oc-grid-3" style="margin-top:28px">
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">1,842</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">notes in vault</p></div>
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">6.3k</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">backlinks (由 AI 自动补)</p></div>
<div class="oc-card" style="text-align:center"><div class="oc-big oc-g" style="font-size:80px">-74%</div><p style="color:var(--oc-dim);margin-top:8px;font-size:13px">找资料平均耗时</p></div>
</div>
<div class="oc-hl" style="margin-top:26px">最大收益不是「AI 帮我写」,而是「AI 帮我把旧笔记重新连起来」—— 每周 30 分钟,vault 就会主动生长。</div>
</section>
<!-- 7. QUOTE / CTA -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">07 / 08</div>
<div class="oc-tag">● CTA · 今晚可以做</div>
<div class="oc-quote">
<blockquote>不要再找「AI 笔记应用」了。<br>你要的是一个 <span class="oc-g">文件夹 + 一条神经</span>。</blockquote>
<div class="attr">— 我自己,用了 90 天后</div>
</div>
<div style="margin-top:36px">
<span class="oc-pill">⬇ obsidian.md</span>
<span class="oc-pill">⬇ Claude Desktop</span>
<span class="oc-pill">⬇ obsidian-mcp</span>
<span class="oc-pill">⬇ obsidian-skills × 5</span>
</div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="oc-cbg"></div>
<div class="oc-cgrid"></div>
<div class="oc-snum">08 / 08</div>
<div class="oc-big oc-g">Thanks.</div>
<p class="oc-sub" style="margin-top:26px">配置模板、skill manifest、我的 vault 结构图都在 <b style="color:var(--oc-accent3)">github.com/lewis/obsidian-claude</b></p>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/obsidian-claude-gradient/README.md
# obsidian-claude-gradient
GitHub-dark (`#0d1117`) + 紫色 ambient radial + 60px 遮罩网格 + 紫→蓝→绿渐变文字。灵感来自 `20260406-obsidian-claude/slides.html` 的 `--accent #7c3aed`、`.cbg` 双 radial cover、`.cgrid` 60px 遮罩网格以及 `.g` 三停渐变。
**Visual traits:** 深灰蓝底 + 紫蓝 radial 晕染 + 暗网格遮罩、居中对齐的标题/正文、圆角紫色 pill tag、linear `#a855f7→#60a5fa→#34d399` 渐变字、GitHub-ish 代码色 (`#010409` 背景 + 紫/蓝/橙/绿 token)、紫色左边框 highlight 块、简洁 step 列表。
**Use when:** 讲一个开发者友好的工作流、MCP / Agent / Dev tool 教程;你希望气质接近 GitHub Blog / Linear Changelog;内容以配置文件 + 步骤为主。
**Source inspiration:** `20260406-obsidian-claude/slides.html`.
**Path:** `templates/full-decks/obsidian-claude-gradient/index.html`
templates/full-decks/obsidian-claude-gradient/style.css
/* obsidian-claude-gradient — 紫色暗底 + GitHub-ish 渐变卡 */
.tpl-obsidian-claude-gradient{
--oc-bg:#0d1117;
--oc-surface:#161b22;
--oc-surface2:#21262d;
--oc-border:#30363d;
--oc-accent:#7c3aed;
--oc-accent2:#a855f7;
--oc-accent3:#c084fc;
--oc-green:#3fb950;
--oc-blue:#58a6ff;
--oc-orange:#f97316;
--oc-yellow:#fbbf24;
--oc-red:#f87171;
--oc-text:#e6edf3;
--oc-dim:#8b949e;
--oc-dimmer:#484f58;
background:var(--oc-bg);
color:var(--oc-text);
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
}
.tpl-obsidian-claude-gradient .slide{background:var(--oc-bg);color:var(--oc-text);padding:64px 88px;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;overflow:hidden}
.tpl-obsidian-claude-gradient .oc-cbg{position:absolute;inset:0;pointer-events:none;background:radial-gradient(ellipse at 28% 38%,rgba(124,58,237,.25) 0%,transparent 60%),radial-gradient(ellipse at 72% 62%,rgba(88,166,255,.18) 0%,transparent 60%)}
.tpl-obsidian-claude-gradient .oc-cgrid{position:absolute;inset:0;pointer-events:none;background-image:linear-gradient(rgba(48,54,61,.4) 1px,transparent 1px),linear-gradient(90deg,rgba(48,54,61,.4) 1px,transparent 1px);background-size:60px 60px;mask-image:radial-gradient(ellipse at center,black 35%,transparent 80%)}
.tpl-obsidian-claude-gradient .slide > *{position:relative;z-index:2}
.tpl-obsidian-claude-gradient .oc-snum{position:absolute;top:24px;right:36px;color:var(--oc-dimmer);font-size:12px;letter-spacing:.1em;z-index:3}
.tpl-obsidian-claude-gradient .oc-tag{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--oc-accent3);background:rgba(124,58,237,.14);border:1px solid rgba(168,85,247,.3);padding:5px 16px;border-radius:999px;margin-bottom:22px}
.tpl-obsidian-claude-gradient .oc-h1{font-size:72px;font-weight:800;line-height:1.08;letter-spacing:-.02em;margin:0 0 10px;color:var(--oc-text)}
.tpl-obsidian-claude-gradient .oc-h2{font-size:44px;font-weight:700;line-height:1.18;letter-spacing:-.015em;margin:0 0 14px}
.tpl-obsidian-claude-gradient .oc-sub{font-size:19px;color:var(--oc-dim);line-height:1.65;max-width:720px;margin-top:14px}
.tpl-obsidian-claude-gradient .oc-g{background:linear-gradient(135deg,#a855f7 0%,#60a5fa 55%,#34d399 100%);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.tpl-obsidian-claude-gradient .oc-card{background:var(--oc-surface);border:1px solid var(--oc-border);border-radius:14px;padding:22px 26px;text-align:left;position:relative;overflow:hidden}
.tpl-obsidian-claude-gradient .oc-card::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,rgba(168,85,247,.4),transparent)}
.tpl-obsidian-claude-gradient .oc-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px;width:100%;max-width:1000px;margin-top:24px}
.tpl-obsidian-claude-gradient .oc-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;width:100%;max-width:1080px;margin-top:24px}
.tpl-obsidian-claude-gradient .oc-badge{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:600;padding:3px 11px;border-radius:999px;margin-bottom:10px}
.tpl-obsidian-claude-gradient .oc-bp{background:rgba(168,85,247,.15);color:var(--oc-accent3)}
.tpl-obsidian-claude-gradient .oc-bb{background:rgba(88,166,255,.15);color:var(--oc-blue)}
.tpl-obsidian-claude-gradient .oc-bg{background:rgba(63,185,80,.15);color:var(--oc-green)}
.tpl-obsidian-claude-gradient .oc-bo{background:rgba(249,115,22,.15);color:var(--oc-orange)}
.tpl-obsidian-claude-gradient .oc-code{background:#010409;border:1px solid var(--oc-border);border-radius:12px;padding:20px 24px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;width:100%;max-width:860px;text-align:left;color:#e6edf3}
.tpl-obsidian-claude-gradient .oc-code .cp{color:var(--oc-green)}
.tpl-obsidian-claude-gradient .oc-code .cc{color:var(--oc-blue)}
.tpl-obsidian-claude-gradient .oc-code .ca{color:var(--oc-accent3)}
.tpl-obsidian-claude-gradient .oc-code .cm{color:var(--oc-dimmer)}
.tpl-obsidian-claude-gradient .oc-code .cs{color:var(--oc-orange)}
.tpl-obsidian-claude-gradient .oc-hl{background:rgba(124,58,237,.1);border:1px solid rgba(168,85,247,.3);border-left:4px solid var(--oc-accent2);border-radius:0 12px 12px 0;padding:16px 22px;font-size:16px;line-height:1.7;max-width:860px;text-align:left}
.tpl-obsidian-claude-gradient .oc-steps{display:flex;flex-direction:column;gap:0;width:100%;max-width:820px;text-align:left}
.tpl-obsidian-claude-gradient .oc-step{display:flex;gap:20px;align-items:flex-start;padding:18px 0;border-bottom:1px solid var(--oc-border)}
.tpl-obsidian-claude-gradient .oc-step:last-child{border-bottom:none}
.tpl-obsidian-claude-gradient .oc-sn{width:36px;height:36px;flex-shrink:0;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px;background:linear-gradient(135deg,var(--oc-accent),var(--oc-blue));color:#fff}
.tpl-obsidian-claude-gradient .oc-sc h4{font-size:17px;font-weight:600;margin-bottom:6px;color:var(--oc-text)}
.tpl-obsidian-claude-gradient .oc-sc p{font-size:14px;color:var(--oc-dim);line-height:1.6}
.tpl-obsidian-claude-gradient .oc-pill{display:inline-flex;align-items:center;gap:8px;background:var(--oc-surface2);border:1px solid var(--oc-border);border-radius:999px;padding:7px 18px;font-size:14px;font-weight:500;color:var(--oc-text);margin:4px 6px 4px 0}
.tpl-obsidian-claude-gradient .oc-quote{max-width:800px}
.tpl-obsidian-claude-gradient .oc-quote blockquote{font-size:26px;font-weight:500;line-height:1.6;position:relative;padding:0 36px;margin:0;color:var(--oc-text)}
.tpl-obsidian-claude-gradient .oc-quote blockquote::before{content:'"';position:absolute;left:-6px;top:-22px;font-size:78px;color:var(--oc-accent);opacity:.4;font-family:Georgia,serif;line-height:1}
.tpl-obsidian-claude-gradient .oc-quote .attr{margin-top:20px;font-size:13px;color:var(--oc-dim)}
.tpl-obsidian-claude-gradient .oc-big{font-size:140px;font-weight:900;line-height:.95;letter-spacing:-.04em}
templates/full-decks/graphify-dark-graph/README.md
# graphify-dark-graph
Deep-night 暗底 + 力导向知识图谱覆盖层 + 温暖玻璃拟态卡片。灵感来自 `20260413-graphify/ppt/graphify.html` 的 `#06060c` 渐变底、飘移 orb 光晕、glass 卡片(warm/blue/green/purple 五变体)和 rainbow-text 标题。
**Visual traits:** `#06060c → #0e1020` 斜向渐变、三颗 400-520px blur orb 慢飘动、cover SVG 力导向图谱作为背景、rainbow shift 渐变标题、JetBrains Mono 的 `.cmd-glow` 命令行、玻璃拟态卡片带顶部高光和微妙内阴影、温暖色系 accent (#e8a87c 琥珀 / #7ed3a4 薄荷 / #7eb8da 雾蓝 / #b8a4d6 丁香).
**Use when:** 介绍一个开发者工具、命令行产品、知识图谱 / 数据可视化相关项目;你希望现场演示时视觉有「AI native + 科技感 + 温度」。
**Source inspiration:** `20260413-graphify/ppt/graphify.html`.
**Path:** `templates/full-decks/graphify-dark-graph/index.html`
templates/full-decks/pitch-deck/README.md
# pitch-deck
Classic 10-slide YC/VC seed pitch: cover, problem, solution, product, market, business model, traction, team, ask, thanks.
Clean white background, bold blue→purple gradient accent, oversized headlines and big numbers — the look investors expect when they skim 40 decks a day.
**Use when:** pitching a fundraise, office hours, or a "state of the company" update. Swap copy, keep structure.
**Feel:** confident, data-forward, founder-friendly.
**Brand color:** override `--grad` in `style.css` to re-skin.
templates/full-decks/hermes-cyber-terminal/README.md
# hermes-cyber-terminal
黑底 (`#0a0c10`) + 终端 chrome + 扫描线 + 薄荷绿 glow 大字 + JetBrains Mono 全文打字机感。灵感来自 `20260414-hermes-agent/ppt/hermes-record.html` 的 `codebox #15151b` 深色代码盒和 `hermes-vs-openclaw.html` 的实测对比气质 —— 把两者合成一份「honest cyber review」。
**Visual traits:** 56px cyber 网格 + CRT vignette + 半透明 scanlines 叠层、窗口 traffic-light chrome、`$ prompt` 开头的 command-line 标题、薄荷绿 text-shadow glow `#7ed3a4`、monospace 全局、虚拟 bar chart 用 stroke-only 呈现、blinking cursor、amber/green/red 分级标签。
**Use when:** 评测一个开发者工具 / CLI / agent,展示跑分数据、trace、diff;想要即刻给出「技术人 honest review」的视觉语气;适合长 trace / long code 的场景。
**Source inspiration:** `20260414-hermes-agent/ppt/hermes-record.html` + `hermes-vs-openclaw.html`.
**Path:** `templates/full-decks/hermes-cyber-terminal/index.html`
templates/full-decks/pitch-deck/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Lumen · Pitch Deck</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-pitch-deck">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="cover-bg"></div>
<div class="cover-blob"></div>
<div style="position:absolute;top:56px;left:112px"><span class="brand-dot"></span><span class="brand">Lumen</span></div>
<p class="kicker">Seed round · 2026</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">The operating system<br>for <span class="gradient-text">solo founders</span>.</h1>
<p class="lede mt-m">One workspace for billing, CRM, contracts and taxes — built for the 70M people running a business of one.</p>
<div class="deck-footer"><span>Maya Chen · CEO</span><span class="slide-number" data-current="1" data-total="10"></span></div>
</section>
<!-- 2. Problem -->
<section class="slide" data-title="Problem">
<span class="section-num">01</span>
<p class="num-tag">PROBLEM</p>
<h2 class="h2 mt-s">Solo founders duct-tape<br>7+ tools to stay alive.</h2>
<div class="grid g3 mt-l">
<div class="card"><h4>Fragmentation</h4><p class="dim">Stripe, QuickBooks, HubSpot, DocuSign, Notion, Gusto, a spreadsheet. Nothing talks.</p></div>
<div class="card"><h4>$480/mo wasted</h4><p class="dim">Average solo founder pays for 9 SaaS seats they only half-use.</p></div>
<div class="card"><h4>14 hrs / week lost</h4><p class="dim">Copy-pasting between tools instead of selling.</p></div>
</div>
</section>
<!-- 3. Solution -->
<section class="slide" data-title="Solution">
<span class="section-num">02</span>
<p class="num-tag">SOLUTION</p>
<h2 class="h2 mt-s">Lumen is <span class="gradient-text">one spine</span><br>for the business of one.</h2>
<p class="lede mt-m">Invoice a client → the payment lands → the tax is reserved → the contract is filed → your dashboard updates. In one app. Without plumbing.</p>
<div class="row mt-l">
<span class="pill pill-accent">Billing</span>
<span class="pill pill-accent">CRM</span>
<span class="pill pill-accent">Contracts</span>
<span class="pill pill-accent">Taxes</span>
<span class="pill pill-accent">Banking</span>
</div>
</section>
<!-- 4. Product -->
<section class="slide" data-title="Product">
<span class="section-num">03</span>
<p class="num-tag">PRODUCT</p>
<h2 class="h2 mt-s">Built around "jobs to be done".</h2>
<div class="grid g2 mt-l">
<div class="card card-hover"><h4>Get paid</h4><p class="dim">Invoices, subscriptions and Stripe/Wise payouts with a single click. ACH, card, wire, crypto.</p></div>
<div class="card card-hover"><h4>Stay legal</h4><p class="dim">E-sign contracts from templates. Auto-file 1099s and quarterly estimates.</p></div>
<div class="card card-hover"><h4>Sell smarter</h4><p class="dim">Lead inbox, pipeline, email sequences. No separate CRM.</p></div>
<div class="card card-hover"><h4>See the business</h4><p class="dim">Live P&L, runway, top customers, churn. One dashboard, zero spreadsheets.</p></div>
</div>
</section>
<!-- 5. Market -->
<section class="slide" data-title="Market">
<span class="section-num">04</span>
<p class="num-tag">MARKET</p>
<h2 class="h2 mt-s">A very big small business.</h2>
<div class="grid g3 mt-l">
<div class="metric"><div class="n">73M</div><div class="l">solo businesses in the US + EU</div></div>
<div class="metric"><div class="n">$186B</div><div class="l">TAM · horizontal SaaS spend</div></div>
<div class="metric"><div class="n">9.4%</div><div class="l">CAGR through 2030</div></div>
</div>
<p class="lede mt-l">Creators, consultants, indie devs, coaches, freelancers — the fastest-growing segment of the workforce, and the most under-served by tooling.</p>
</section>
<!-- 6. Business model -->
<section class="slide" data-title="Business Model">
<span class="section-num">05</span>
<p class="num-tag">BUSINESS MODEL</p>
<h2 class="h2 mt-s">Flat SaaS + payment rake.</h2>
<div class="grid g3 mt-l">
<div class="card"><h4>Starter</h4><div class="metric mt-s"><div class="n" style="font-size:56px">$29</div><div class="l">/ month · core billing + CRM</div></div></div>
<div class="card card-accent"><h4>Pro</h4><div class="metric mt-s"><div class="n" style="font-size:56px">$79</div><div class="l">/ month · contracts, taxes, banking</div></div></div>
<div class="card"><h4>+ Payments</h4><div class="metric mt-s"><div class="n" style="font-size:56px">0.4%</div><div class="l">interchange rake on processed volume</div></div></div>
</div>
<p class="dim mt-l">Blended LTV $1,920 · CAC payback 5 months at current funnel.</p>
</section>
<!-- 7. Traction -->
<section class="slide" data-title="Traction">
<span class="section-num">06</span>
<p class="num-tag">TRACTION</p>
<h2 class="h2 mt-s">6 months, growing 38% MoM.</h2>
<div class="traction-bar mt-l">
<div class="bar" style="height:18%"><em>$6k</em><span>Oct</span></div>
<div class="bar" style="height:30%"><em>$11k</em><span>Nov</span></div>
<div class="bar" style="height:44%"><em>$17k</em><span>Dec</span></div>
<div class="bar" style="height:62%"><em>$26k</em><span>Jan</span></div>
<div class="bar" style="height:82%"><em>$38k</em><span>Feb</span></div>
<div class="bar" style="height:100%"><em>$54k</em><span>Mar</span></div>
</div>
<p class="dim mt-l" style="margin-top:48px">2,140 paying customers · NPS 72 · Net retention 118%</p>
</section>
<!-- 8. Team -->
<section class="slide" data-title="Team">
<span class="section-num">07</span>
<p class="num-tag">TEAM</p>
<h2 class="h2 mt-s">Shipped at scale before.</h2>
<div class="grid g3 mt-l">
<div class="card team-card"><div class="avatar">MC</div><h4>Maya Chen</h4><p class="dim">CEO · ex-Stripe billing lead. 8 yrs in payments.</p></div>
<div class="card team-card"><div class="avatar">RP</div><h4>Raj Patel</h4><p class="dim">CTO · ex-Linear. Built multiplayer sync at 10M users.</p></div>
<div class="card team-card"><div class="avatar">EK</div><h4>Elena Kim</h4><p class="dim">Head of Design · ex-Notion. Shipped the mobile relaunch.</p></div>
</div>
</section>
<!-- 9. Ask -->
<section class="slide" data-title="The Ask">
<p class="num-tag">THE ASK</p>
<div class="ask-box mt-m">
<h2 class="h2">Raising $4.5M seed.</h2>
<p class="lede" style="color:rgba(255,255,255,.9);max-width:60ch">18 months of runway to reach $3M ARR. 40% engineering, 35% growth, 15% compliance/banking licenses, 10% runway buffer.</p>
<div class="row mt-l" style="gap:40px">
<div><div style="font-size:44px;font-weight:900">$4.5M</div><div class="dim">SAFE · post-money cap $28M</div></div>
<div><div style="font-size:44px;font-weight:900">18 mo</div><div class="dim">runway to Series A</div></div>
<div><div style="font-size:44px;font-weight:900">$3M</div><div class="dim">ARR target by close</div></div>
</div>
</div>
</section>
<!-- 10. Thanks -->
<section class="slide center tc" data-title="Thanks">
<div class="cover-bg"></div>
<div>
<div class="mega">Thanks.</div>
<p class="mega-sub">maya@lumen.app · lumen.app/investors</p>
<div class="row mt-l" style="justify-content:center;gap:24px">
<span class="pill pill-accent">Let's talk</span>
<span class="pill">Deck v4.2 · Apr 2026</span>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>
templates/full-decks/presenter-mode-reveal/README.md
# presenter-mode-reveal · 演讲者模式模板
一份专为**带逐字稿的技术分享**设计的 full-deck 模板。核心卖点是真正可用的**磁吸卡片式演讲者视图**:当前页 iframe 预览 + 下页 iframe 预览 + 大字号逐字稿 + 计时器,4 个卡片可任意拖拽/缩放,全部集成在 `runtime.js` 里,零依赖。
## 使用场景
- 技术分享 / tech talk(30-60 min)
- 产品发布会主讲
- 课程讲授
- 任何**需要照着讲、但不能念稿**的正式演讲
## 快速开始
```bash
cp -r templates/full-decks/presenter-mode-reveal examples/my-talk
open examples/my-talk/index.html
```
## 键盘操作
| 键 | 动作 |
|---|---|
| `S` | 打开演讲者窗口(弹出新窗口,原页面不动) |
| `T` | 切换主题(5 种预设) |
| `←` `→` | 翻页 |
| `Space` / `PgDn` | 下一页 |
| `F` | 全屏 |
| `O` | 总览缩略图 |
| `R` | 重置计时器(仅演讲者视图下) |
| `Esc` | 关闭所有浮层 |
## 主题切换
模板预设了 5 个适配演讲场景的主题,在 `<html data-themes="...">` 属性里:
```html
<html lang="zh-CN" data-themes="tokyo-night,dracula,catppuccin-mocha,nord,corporate-clean">
```
按 `T` 循环切换。可以改成任何 `assets/themes/*.css` 里的主题。
## 写逐字稿的规范
**每一页的 `<aside class="notes">` 里写 150–300 字**。三条铁律:
1. **不是讲稿,是提示信号** — 核心点加粗、过渡句成段、数据列清楚
2. **150–300 字/页** — 按 2–3 分钟/页的节奏
3. **用口语写** — "因此" → "所以";"该方案" → "这个方案";读一遍不拗口才对
示例:
```html
<aside class="notes">
<p>大家好,今天跟大家聊一个 <strong>很多人忽略的问题</strong>——...</p>
<p>我先抛一个观点:<em>做 PPT 和讲 PPT 是两件事</em>。</p>
<p>接下来我会用 3 个例子证明这个观点...</p>
</aside>
```
支持的 inline 标签:
- `<strong>` — 高亮(橘色)
- `<em>` — 斜体强调(蓝色)
- `<code>` — 等宽字体
- `<p>` — 分段(推荐每段讲 30-60 秒的内容)
## 文件结构
```
presenter-mode-reveal/
├── index.html # 6 张示例 slide,每页都有完整逐字稿
├── style.css # scoped .tpl-presenter-mode-reveal 样式
└── README.md # 本文件
```
## 修改 / 扩展
- **加页**:复制任意 `<section class="slide">` 块,改内容和 `<aside class="notes">`
- **换主题**:改 `data-themes` 列表,或直接改 `<link id="theme-link" href="...">`
- **改样式**:只动 `style.css`,不要碰根目录的 `assets/base.css`
- **加动效**:在元素上加 `data-anim="fade-up"` 等(参考 `references/animations.md`)
## 演讲者窗口的 4 个卡片
按 `S` 后弹出的窗口里有:
- 🔵 **CURRENT** — 当前页 iframe 预览(加载 `?preview=N` 模式,像素级完美,与观众端同 CSS/主题/字体)
- 🟣 **NEXT** — 下一页预览,帮助准备过渡
- 🟠 **SPEAKER SCRIPT** — 大字号逐字稿,可滚动
- 🟢 **TIMER** — 经过时间 + 页码 + Prev/Next/Reset 按钮
卡片操作:
- **拖卡片头**(彩色圆点 + 标题的顶部条)→ 移动卡片
- **拖卡片右下角** → 调整大小
- 位置 + 尺寸自动存 localStorage,下次打开恢复
- 底部 "重置布局" 按钮可恢复默认卡片排列
翻页丝滑:iframe 只加载一次,后续翻页通过 `postMessage` 切换内部 slide,**不重新加载不闪烁**。两窗口通过 `BroadcastChannel` 双向同步。
## 注意事项
- **观众永远看不到 `.notes` 内容** — CSS 默认 `display:none`,只在演讲者视图里可见
- **别把只给自己看的话写在 slide 本体上** — 所有提词必须在 `<aside class="notes">` 里
- **双屏演讲**:打开 `index.html` 按 S 弹出演讲者窗口,把观众窗口拖到投影/外接屏 F 全屏,演讲者窗口留在自己屏幕
templates/full-decks/hermes-cyber-terminal/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hermes Cyber Terminal</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-hermes-cyber-terminal">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="hc-grid"></div>
<div class="hc-vignette"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>~/hermes · zsh · 118x42 · 01:37:04</div></div>
<div style="margin:auto 0">
<p class="hc-prompt">whoami --hermes</p>
<h1 class="hc-h1">HERMES<br>AGENT / v0.9.2<span class="hc-cursor"></span></h1>
<p class="hc-lede">一个号称能「自主跑完整软件工程任务」的命令行 agent。<br>真的好用?还是又一轮营销?—— 我连续跑了 72 小时,告诉你答案。</p>
<div style="margin-top:26px">
<span class="hc-tag">rust-core</span>
<span class="hc-tag">mcp-native</span>
<span class="hc-tag amber">72h-benchmark</span>
<span class="hc-tag red">honest-review</span>
</div>
</div>
<div class="hc-footer"><span>hermes-review · lewis · 2026</span><span>01 / 08</span></div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>section · 01/04</div></div>
<div style="margin:auto 0">
<p class="hc-prompt">cat chapter_01.md</p>
<h1 class="hc-h1" style="font-size:110px">// Setup</h1>
<p class="hc-lede">从 <code style="color:var(--hc-amber)">brew install hermes</code> 到第一次 prompt —— 一共 4 分 22 秒。</p>
</div>
<div class="hc-footer"><span>section · setup</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT — spec cards -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>benchmark · cold-start</div></div>
<h2 class="hc-h2">开箱数据</h2>
<p class="hc-lede">cold start → first-successful-task 三次平均</p>
<div class="hc-grid-3">
<div class="hc-card"><div class="lbl">install time</div><div class="val">42s</div><div class="desc">单 binary,无 docker,无 python env。</div></div>
<div class="hc-card"><div class="lbl">first token</div><div class="val">1.8s</div><div class="desc">接入 claude-opus-4-6,无预热。</div></div>
<div class="hc-card"><div class="lbl">first PR merged</div><div class="val">4m22s</div><div class="desc">跑的是 fix-a-typo 级别的低难度任务。</div></div>
</div>
<div class="hc-grid-2">
<div class="hc-card"><div class="lbl">// verdict +</div><div class="val" style="color:var(--hc-green);font-size:18px">冷启动是真的快</div><div class="desc">和 OpenClaw 的 docker + pip 流程比,快不止一个数量级。</div></div>
<div class="hc-card"><div class="lbl">// verdict -</div><div class="val" style="color:var(--hc-red);font-size:18px">MCP 服务器配置不够友好</div><div class="desc">env 变量需要手动塞进 ~/.hermes/env,文档几乎没写。</div></div>
</div>
<div class="hc-footer"><span>data · verified 3 runs</span><span>03 / 08</span></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>trace · hermes run</div></div>
<p class="hc-prompt">hermes run "refactor auth module to use pkce"</p>
<h3 class="hc-h3" style="margin-top:12px">↓ 真实 trace (节选)</h3>
<pre class="hc-codebox" style="margin-top:10px"><span class="cm"># hermes v0.9.2 · session 42a1</span>
[<span class="fn">plan</span>] <span class="st">"分析 src/auth/*.ts → 找 oauth flow → 抽成 pkce"</span>
[<span class="fn">read</span>] src/auth/oauth.ts <span class="cm">// 214 lines</span>
[<span class="fn">read</span>] src/auth/token.ts <span class="cm">// 88 lines</span>
[<span class="kw">think</span>] <span class="st">"发现 implicit flow,改为 code+pkce,需新 state param"</span>
[<span class="fn">edit</span>] src/auth/oauth.ts <span class="hl">+43 -17</span>
[<span class="fn">edit</span>] src/auth/token.ts <span class="hl">+12 -4</span>
[<span class="fn">test</span>] pnpm vitest auth <span class="st">PASS 18/18</span>
[<span class="fn">commit</span>] <span class="var">"feat(auth): migrate to oauth2 code+pkce"</span>
[<span class="fn">push</span>] origin feat/pkce-auth <span class="st">ok</span>
<span class="cm"># 总耗时 3m 14s · 14k tokens · $0.21</span></pre>
<div class="hc-footer"><span>trace · live</span><span>04 / 08</span></div>
</section>
<!-- 5. CHART -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>benchmark · hermes vs openclaw</div></div>
<h2 class="hc-h2">72 小时对比</h2>
<p class="hc-lede">同一组 48 个 GitHub issue,两个 agent 各跑一遍</p>
<svg viewBox="0 0 1000 380" style="width:100%;max-width:1040px;margin-top:24px" xmlns="http://www.w3.org/2000/svg">
<g font-family="JetBrains Mono, monospace" font-size="13" fill="#8a8892">
<!-- axis -->
<line x1="80" y1="40" x2="80" y2="320" stroke="rgba(126,211,164,.2)"/>
<line x1="80" y1="320" x2="960" y2="320" stroke="rgba(126,211,164,.2)"/>
<!-- y labels -->
<text x="70" y="46" text-anchor="end">100%</text>
<text x="70" y="116" text-anchor="end">75%</text>
<text x="70" y="186" text-anchor="end">50%</text>
<text x="70" y="256" text-anchor="end">25%</text>
<text x="70" y="324" text-anchor="end">0</text>
<!-- bars: hermes -->
<g>
<rect x="130" y="80" width="80" height="240" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="170" y="76" text-anchor="middle" fill="#7ed3a4" font-weight="700">82%</text>
<text x="170" y="345" text-anchor="middle">resolved</text>
<rect x="240" y="146" width="80" height="174" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="280" y="142" text-anchor="middle" fill="#7ed3a4" font-weight="700">58%</text>
<text x="280" y="345" text-anchor="middle">one-shot</text>
<rect x="350" y="60" width="80" height="260" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="390" y="56" text-anchor="middle" fill="#7ed3a4" font-weight="700">89%</text>
<text x="390" y="345" text-anchor="middle">test-pass</text>
<rect x="460" y="110" width="80" height="210" fill="rgba(126,211,164,.15)" stroke="#7ed3a4" stroke-width="1.5"/>
<text x="500" y="106" text-anchor="middle" fill="#7ed3a4" font-weight="700">71%</text>
<text x="500" y="345" text-anchor="middle">pr-merged</text>
</g>
<!-- bars: openclaw -->
<g>
<rect x="570" y="150" width="80" height="170" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
<text x="610" y="146" text-anchor="middle" fill="#e9c58a" font-weight="700">60%</text>
<text x="610" y="345" text-anchor="middle">resolved</text>
<rect x="680" y="212" width="80" height="108" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
<text x="720" y="208" text-anchor="middle" fill="#e9c58a" font-weight="700">38%</text>
<text x="720" y="345" text-anchor="middle">one-shot</text>
<rect x="790" y="130" width="80" height="190" fill="rgba(233,197,138,.12)" stroke="#e9c58a" stroke-width="1.5"/>
<text x="830" y="126" text-anchor="middle" fill="#e9c58a" font-weight="700">67%</text>
<text x="830" y="345" text-anchor="middle">test-pass</text>
</g>
<!-- legend -->
<g transform="translate(820,50)">
<rect x="0" y="0" width="14" height="14" fill="rgba(126,211,164,.15)" stroke="#7ed3a4"/>
<text x="22" y="12" fill="#7ed3a4">hermes 0.9.2</text>
<rect x="0" y="22" width="14" height="14" fill="rgba(233,197,138,.12)" stroke="#e9c58a"/>
<text x="22" y="34" fill="#e9c58a">openclaw 2.1</text>
</g>
</g>
</svg>
<div class="hc-footer"><span>benchmark · n=48</span><span>05 / 08</span></div>
</section>
<!-- 6. STATS -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>tldr</div></div>
<p class="hc-prompt">echo $VERDICT</p>
<div class="hc-big">7.8<span style="font-size:60px;color:var(--hc-ink2)">/ 10</span></div>
<p class="hc-lede" style="margin-top:14px">值得装,还不值得完全依赖。</p>
<div class="hc-grid-2" style="margin-top:24px">
<div class="hc-card"><div class="lbl">+ strong points</div><div class="desc">• rust 本体冷启快<br>• trace 可读性极强<br>• diff 审核友好,commit message 也写得合格</div></div>
<div class="hc-card"><div class="lbl">- weak points</div><div class="desc">• plan 阶段偶尔跳步<br>• 超 50k LoC 仓库会 OOM<br>• MCP 配置需要手动塞 env</div></div>
</div>
<div class="hc-footer"><span>verdict · honest</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>install</div></div>
<h2 class="hc-h2">想自己跑一遍?</h2>
<p class="hc-lede">三条命令,不到 5 分钟就能看见它干第一件事。</p>
<pre class="hc-codebox" style="margin-top:22px"><span class="cm"># 1. install</span>
<span class="kw">$</span> brew install hermes-agent/tap/hermes
<span class="cm"># 2. auth (先准备好 anthropic api key)</span>
<span class="kw">$</span> hermes auth login
<span class="cm"># 3. first task</span>
<span class="kw">$</span> cd ~/your-repo && hermes run <span class="st">"add a CHANGELOG.md from git log"</span></pre>
<div style="margin-top:26px">
<span class="hc-tag">brew-ready</span>
<span class="hc-tag">opus-4.6</span>
<span class="hc-tag amber">needs-api-key</span>
</div>
<div class="hc-footer"><span>try-it-now</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="hc-grid"></div>
<div class="hc-scanlines"></div>
<div class="hc-chrome"><div class="dots"><span></span><span></span><span></span></div><div>EOF</div></div>
<div style="margin:auto 0">
<p class="hc-prompt">exit 0</p>
<h1 class="hc-h1" style="font-size:120px">// thanks<span class="hc-cursor"></span></h1>
<p class="hc-lede">完整 trace、48 个任务的 PR 列表、benchmark 脚本都在 <span style="color:var(--hc-amber)">github.com/lewis/hermes-review</span></p>
</div>
<div class="hc-footer"><span>session closed</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/presenter-mode-reveal/index.html
<!DOCTYPE html>
<html lang="zh-CN" data-themes="tokyo-night,dracula,catppuccin-mocha,nord,corporate-clean">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>演讲者模式示例 · Presenter Mode Deck</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../../assets/themes/tokyo-night.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-presenter-mode-reveal">
<div class="deck">
<!-- ============ 1. COVER ============ -->
<section class="slide" data-title="Cover">
<p class="kicker">presenter-mode / demo</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">如何做一场<br><span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">有逐字稿</span>的技术分享</h1>
<p class="lede mt-m">按 <span class="mono">S</span> 进入演讲者视图 · <span class="mono">T</span> 切换主题 · <span class="mono">← →</span> 翻页</p>
<div class="speaker">
<div class="av"></div>
<div><b>@lewis</b><span>sharing talk · 30 min</span></div>
</div>
<div class="deck-footer">
<span class="mono">#presenter #逐字稿 #tech-talk</span>
<span class="slide-number" data-current="1" data-total="6"></span>
</div>
<aside class="notes">
<p>大家好,欢迎来到今天的技术分享。我是 lewis,今天想跟大家聊一个很多人忽略但其实非常影响演讲效果的话题——<strong>如何让一场技术分享既有深度,又讲得不卡壳</strong>。</p>
<p>在正式开始之前,先跟大家介绍一下这份 deck 本身:这是一个支持<em>演讲者模式</em>的 HTML 幻灯片模板。现在你们看到的是观众视图,但我自己的屏幕上看到的是完全不一样的东西——当前页、下一页、完整逐字稿、计时器,全在一块屏幕上。</p>
<p>为什么我要专门做这个?因为我发现自己做技术分享时最大的痛点不是 PPT 不够好看,而是<strong>讲到某一页突然不知道该说什么,或者忘了过渡怎么接</strong>。今天这份分享既是内容本身,也是个演示——我会一直开着演讲者模式讲,你们可以观察我讲得有多流畅。</p>
<p>今天分享大概 30 分钟,分 5 个部分。有问题随时打断。Let's go.</p>
</aside>
</section>
<!-- ============ 2. AGENDA ============ -->
<section class="slide" data-title="Agenda">
<p class="kicker">agenda</p>
<h2 class="h2">今天要讲的 5 件事</h2>
<div class="stack mt-l">
<div class="agenda-row"><span class="num">01</span><span class="t">为什么 PPT 本身做得好还不够</span><span class="d">~5min</span></div>
<div class="agenda-row"><span class="num">02</span><span class="t">演讲者模式到底该有哪些信息</span><span class="d">~6min</span></div>
<div class="agenda-row"><span class="num">03</span><span class="t">逐字稿怎么写才不像念稿</span><span class="d">~8min</span></div>
<div class="agenda-row"><span class="num">04</span><span class="t">Live demo · html-ppt skill 怎么用</span><span class="d">~8min</span></div>
<div class="agenda-row"><span class="num">05</span><span class="t">Takeaways + Q&A</span><span class="d">~3min</span></div>
</div>
<aside class="notes">
<p>先过一下今天的议程。</p>
<p>第一部分我想先说服你们<strong>"PPT 做得漂亮≠讲得好"</strong>。我见过太多很精致的 deck,但讲的人一上去就开始 "嗯…这个…就是…"。</p>
<p>第二部分聊演讲者视图。业界的产品其实差别蛮大的,Keynote、PowerPoint、reveal.js 都有各自的方案,但真正好用的设计逻辑是什么,我会给出我的答案。</p>
<p>第三部分是今天的<em>核心</em>——逐字稿。很多人以为逐字稿就是把要说的话一字不差写下来,错。逐字稿的目的是让你<strong>"看一眼就接得上"</strong>,写法完全不一样。</p>
<p>第四部分会现场 demo 我自己用的 html-ppt skill,展示如何 30 分钟出一份带逐字稿的 deck。</p>
<p>最后收尾 + 答疑。</p>
<p>OK,进入第一部分。</p>
</aside>
</section>
<!-- ============ 3. PROBLEM ============ -->
<section class="slide" data-title="Problem">
<p class="kicker">// part 01 · problem</p>
<h2 class="h2">做 PPT 和讲 PPT,<br>是<span class="accent">两件事</span>。</h2>
<div class="grid g3 mt-l">
<div class="card card-accent">
<h4>✅ PPT 做得好</h4>
<p class="dim">主题统一、排版干净、图表清晰、动效克制。这些是"静态作品"的质量。</p>
</div>
<div class="card card-accent">
<h4>❌ 讲得好</h4>
<p class="dim">逻辑连贯、语速稳定、不 "嗯啊"、能接住问题、能当场调整节奏。</p>
</div>
<div class="card card-accent">
<h4>💡 差别在哪</h4>
<p class="dim">前者是<strong>纸上功夫</strong>,后者需要你<strong>"看一眼幻灯片就知道下句话说什么"</strong>。</p>
</div>
</div>
<aside class="notes">
<p>我先抛一个可能有争议的观点——<strong>做 PPT 和讲 PPT 是两件完全不同的事</strong>。</p>
<p>大家看左边这张卡片,"PPT 做得好" 意味着什么?主题统一、排版干净、图表清晰、动效克制——这些都是<em>静态作品</em>的质量标准,可以离线评判。</p>
<p>但中间这张卡片就不一样了:"讲得好" 意味着逻辑连贯、语速稳定、不卡壳、能接住提问、能根据现场反应调整节奏——这些是<strong>临场能力</strong>,跟 PPT 好不好看基本没关系。</p>
<p>最关键的是右边这句话——讲得好的人,本质上是"<strong>看一眼幻灯片就知道下句话说什么</strong>"。这个能力靠什么?不是背稿,也不是即兴发挥,而是靠<em>合理设计的提词器系统</em>。</p>
<p>今天接下来 25 分钟,我就是围绕这个核心问题展开的。</p>
</aside>
</section>
<!-- ============ 4. SOLUTION ============ -->
<section class="slide" data-title="Presenter View">
<p class="kicker">// part 02 · presenter view</p>
<h2 class="h2">演讲者视图应该有<span class="accent">四块信息</span></h2>
<div class="grid g2 mt-l">
<div>
<div class="feature-row"><span class="num blue">①</span><div><b>当前页大图</b><p class="dim">占视图一半以上,保证你能扫一眼就知道观众现在看到什么。</p></div></div>
<div class="feature-row"><span class="num green">②</span><div><b>下一页预览</b><p class="dim">帮你提前准备过渡句,避免"下一页我忘了讲什么了"。</p></div></div>
</div>
<div>
<div class="feature-row"><span class="num orange">③</span><div><b>逐字稿区域</b><p class="dim">大字号、高对比度、支持滚动,这才是演讲者真正在看的东西。</p></div></div>
<div class="feature-row"><span class="num purple">④</span><div><b>计时器 + 页码</b><p class="dim">知道自己讲了多久、还剩几页,节奏全凭这个。</p></div></div>
</div>
</div>
<aside class="notes">
<p>演讲者模式应该给你四块信息。我按重要性排序。</p>
<p>第一块,<strong>当前页大图</strong>。这个必须占据视图一半以上空间,因为它是你跟观众的"同步锚"——观众看到什么,你脑子里也得是什么。</p>
<p>第二块,<strong>下一页预览</strong>。这个很多人不理解为什么要放,我解释一下:演讲最卡的瞬间不是讲某一页,而是<em>翻到下一页的那 2 秒</em>。如果你提前看到下一页长什么样,过渡句自然就有了。</p>
<p>第三块,<strong>逐字稿区域</strong>——这是今天的重点,下一部分我会专门讲。这里先说一个硬性要求:字号必须大、对比度必须高、必须能滚动。因为你讲的时候<em>只有余光瞄一下</em>,字小了根本来不及读。</p>
<p>第四块,<strong>计时器和页码</strong>。知道自己讲了多久、还剩几页——节奏感全靠它。Keynote 做得最好,reveal.js 默认不够清楚。</p>
<p>这四块缺一不可。今天这个 deck 我把这四块都做出来了,按 S 大家可以试试。</p>
</aside>
</section>
<!-- ============ 5. SCRIPT ============ -->
<section class="slide" data-title="Script">
<p class="kicker">// part 03 · script</p>
<h2 class="h2">逐字稿的<span class="accent">3 条铁律</span></h2>
<div class="stack mt-l">
<div class="rule-row">
<span class="num red">01</span>
<div>
<b>不是一字不差的讲稿,是<span class="accent">"提示信号"</span></b>
<p class="dim">把要讲的核心点加粗,把过渡句单独成段,把数据和名字列清楚——<em>让你看一眼就接得上</em>。</p>
</div>
</div>
<div class="rule-row">
<span class="num red">02</span>
<div>
<b>每页 <span class="accent">150–300 字</span>,不多不少</b>
<p class="dim">少于 150 字提示不够,多于 300 字你没时间读。按 2–3 分钟/页的节奏控制。</p>
</div>
</div>
<div class="rule-row">
<span class="num red">03</span>
<div>
<b>用<span class="accent">口语</span>写,不用书面语</b>
<p class="dim">"因此" → "所以";"该方案" → "这个方案"。写的时候读一遍,听起来像说话才对。</p>
</div>
</div>
</div>
<aside class="notes">
<p>进入最核心的一部分——逐字稿怎么写。我总结了 3 条铁律。</p>
<p><strong>第一条,逐字稿不是讲稿</strong>。很多人一听"逐字稿"就以为要把每句话一字不差写下来。错。如果你照着稿念,观众会立刻看出来,信任感瞬间崩塌。</p>
<p>逐字稿的真实作用是<em>"提示信号"</em>——把核心要点加粗,把过渡句单独成段,把数据和专有名词列清楚。这样你讲的时候<strong>瞄一眼就能接得上</strong>,但说出来的还是你自己的话。</p>
<p><strong>第二条,每页控制在 150 到 300 字</strong>。这个是我做了十几场分享摸出来的经验值。少于 150 字提示不够,讲到一半卡住;多于 300 字你根本来不及扫完。按一页讲 2 到 3 分钟算,这个字数刚好。</p>
<p><strong>第三条,用口语写</strong>。这条最多人栽跟头。你写"因此",讲出来会变成"所以";你写"该方案",讲出来会变成"这个方案"。<em>写的时候读一遍</em>,不拗口才对。</p>
<p>这三条配合起来,你会发现讲 PPT 突然变成了一件很舒服的事。</p>
</aside>
</section>
<!-- ============ 6. DEMO + CLOSING ============ -->
<section class="slide" data-title="Demo & Close">
<p class="kicker">// part 04-05 · demo + close</p>
<h2 class="h2">现在<span class="accent">你也能做到</span></h2>
<div class="code-block mt-m">
<span class="comment"># 安装 html-ppt skill</span>
<span class="cmd">npx</span> skills add <span class="flag">https://github.com/lewislulu/html-ppt-skill</span>
<span class="comment"># 复制演讲者模式模板</span>
<span class="cmd">cp -r</span> templates/full-decks/presenter-mode-reveal examples/my-talk
<span class="cmd">open</span> examples/my-talk/index.html
<span class="comment"># 键盘操作</span>
<span class="flag">S</span> <span class="comment">→ 进入演讲者视图</span>
<span class="flag">T</span> <span class="comment">→ 切换主题(5 种预设)</span>
<span class="flag">← →</span> <span class="comment">→ 翻页</span>
<span class="flag">R</span> <span class="comment">→ 重置计时器</span>
</div>
<p class="lede mt-m tc">关键是:<strong>每一页 <aside class="notes"> 里写 150–300 字逐字稿</strong>。</p>
<div class="deck-footer">
<span class="mono">#thanks · Q&A</span>
<span class="slide-number" data-current="6" data-total="6"></span>
</div>
<aside class="notes">
<p>最后我演示一下这个 skill 怎么用,给大家省点时间自己摸索。</p>
<p>第一步,装 html-ppt skill,一行命令。第二步,把我这个 <code>presenter-mode-reveal</code> 模板复制到你自己的 examples 目录。第三步,打开 html,按 S。</p>
<p>键盘操作我列在这里了。<strong>S 进入演讲者视图、T 切换主题、左右键翻页、R 重置计时器</strong>。主题默认带 5 个——tokyo-night、dracula、catppuccin-mocha、nord、corporate-clean——基本覆盖了深色技术分享、浅色商务汇报两种常见场景。</p>
<p>最关键的一步——<em>每一页底部的 <code><aside class="notes"></code> 里,老老实实写 150 到 300 字的逐字稿</em>。这是整个方法论的交付物。AI 可以帮你写初稿,但你一定要自己过一遍,读出来听听是不是你会说的话。</p>
<p>好,我今天就讲到这里。如果你做下一场分享的时候想起了这个"演讲者视图 + 逐字稿"的组合,并且觉得讲得比以前顺——那就是我最大的收获。谢谢大家,有问题现在开始。</p>
</aside>
</section>
</div>
<div style="position:fixed;bottom:12px;left:12px;font-size:11px;color:#484f5866;z-index:100;pointer-events:none">
S 演讲者视图 · T 切换主题 · ← → 翻页 · F 全屏 · O 总览 · R 重置计时
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/knowledge-arch-blueprint/style.css
/* knowledge-arch-blueprint — 奶油纸 + 建筑蓝图风 */
.tpl-knowledge-arch-blueprint{
--kb-bg:#F0EAE0;
--kb-ink:#1a1a1a;
--kb-ink2:#555;
--kb-ink3:#aaa;
--kb-rust:#B5392A;
--kb-rust-soft:rgba(181,57,42,.08);
--kb-line:#cec8be;
background:var(--kb-bg);
color:var(--kb-ink);
font-family:'Inter','Noto Sans SC',-apple-system,sans-serif;
}
.tpl-knowledge-arch-blueprint .slide{background:var(--kb-bg);color:var(--kb-ink);padding:64px 80px}
.tpl-knowledge-arch-blueprint .kb-grid-bg{position:absolute;inset:0;pointer-events:none;opacity:.35;background-image:linear-gradient(rgba(26,26,26,.06) 1px,transparent 1px),linear-gradient(90deg,rgba(26,26,26,.06) 1px,transparent 1px);background-size:48px 48px;mask-image:radial-gradient(ellipse at center,black 40%,transparent 85%)}
.tpl-knowledge-arch-blueprint .slide > *{position:relative;z-index:2}
.tpl-knowledge-arch-blueprint .kb-kicker{font-size:13px;font-weight:800;letter-spacing:4px;text-transform:uppercase;color:var(--kb-rust);margin-bottom:12px}
.tpl-knowledge-arch-blueprint .kb-h1{font-size:66px;font-weight:900;line-height:1.08;color:#111;margin:0 0 14px;letter-spacing:-.02em}
.tpl-knowledge-arch-blueprint .kb-h1 span.rust{color:var(--kb-rust)}
.tpl-knowledge-arch-blueprint .kb-sub{font-size:20px;color:#666;line-height:1.55;max-width:780px}
.tpl-knowledge-arch-blueprint .kb-insight{display:inline-block;background:var(--kb-rust);color:#fff;border-radius:10px;padding:16px 22px;font-size:14px;font-weight:700;line-height:1.5;max-width:340px;box-shadow:0 8px 24px rgba(181,57,42,.22)}
.tpl-knowledge-arch-blueprint .kb-insight .kk{font-size:10px;letter-spacing:2px;opacity:.7;display:block;margin-bottom:6px;font-weight:800}
.tpl-knowledge-arch-blueprint .kb-section-label{font-size:11px;font-weight:800;letter-spacing:3.5px;text-transform:uppercase;color:#aaa;margin:30px 0 12px;display:flex;align-items:center;gap:14px}
.tpl-knowledge-arch-blueprint .kb-section-label::after{content:'';flex:1;height:1px;background:var(--kb-line)}
.tpl-knowledge-arch-blueprint .kb-pipeline{display:flex;align-items:stretch;gap:14px;margin-top:24px}
.tpl-knowledge-arch-blueprint .kb-step{flex:1;border:2px solid #1a1a1a;border-radius:12px;padding:22px 18px;background:#fff;position:relative;min-height:200px;display:flex;flex-direction:column}
.tpl-knowledge-arch-blueprint .kb-step.hero{background:var(--kb-rust);border-color:var(--kb-rust);color:#fff;flex:1.25;box-shadow:0 10px 32px rgba(181,57,42,.28);transform:translateY(-10px)}
.tpl-knowledge-arch-blueprint .kb-step-num{font-size:10px;font-weight:800;letter-spacing:2.5px;color:#bbb;margin-bottom:8px;text-transform:uppercase}
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-num{color:rgba(255,255,255,.6)}
.tpl-knowledge-arch-blueprint .kb-step-title{font-size:22px;font-weight:900;line-height:1.15;color:#111;margin-bottom:8px}
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-title{color:#fff}
.tpl-knowledge-arch-blueprint .kb-step-body{font-size:13px;line-height:1.55;color:#555;margin-top:auto}
.tpl-knowledge-arch-blueprint .kb-step.hero .kb-step-body{color:rgba(255,255,255,.88)}
.tpl-knowledge-arch-blueprint .kb-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:24px}
.tpl-knowledge-arch-blueprint .kb-card{background:#fff;border:2px solid #1a1a1a;border-radius:12px;padding:22px 24px}
.tpl-knowledge-arch-blueprint .kb-card h4{font-size:20px;font-weight:900;margin-bottom:6px}
.tpl-knowledge-arch-blueprint .kb-card p{font-size:14px;color:#555;line-height:1.55}
.tpl-knowledge-arch-blueprint .kb-legend{display:flex;gap:18px;flex-wrap:wrap;margin-top:22px;font-size:12px;color:#666}
.tpl-knowledge-arch-blueprint .kb-legend .d{display:flex;align-items:center;gap:8px}
.tpl-knowledge-arch-blueprint .kb-legend .b{width:14px;height:14px;border:2px solid #1a1a1a;border-radius:3px}
.tpl-knowledge-arch-blueprint .kb-legend .b.rust{background:var(--kb-rust);border-color:var(--kb-rust)}
.tpl-knowledge-arch-blueprint .kb-footer{position:absolute;left:80px;right:80px;bottom:36px;display:flex;justify-content:space-between;font-size:11px;color:#999;letter-spacing:.15em;text-transform:uppercase;border-top:1px solid var(--kb-line);padding-top:16px}
.tpl-knowledge-arch-blueprint .kb-mono{font-family:'JetBrains Mono',monospace}
.tpl-knowledge-arch-blueprint .kb-codebox{background:#1a1a1a;color:#f0eae0;border-radius:12px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.8;margin-top:20px;border:2px solid #1a1a1a}
.tpl-knowledge-arch-blueprint .kb-codebox .cm{color:#7a766c}
.tpl-knowledge-arch-blueprint .kb-codebox .kw{color:#e8a87c}
.tpl-knowledge-arch-blueprint .kb-codebox .st{color:#b3d1bc}
.tpl-knowledge-arch-blueprint .kb-codebox .hl{color:var(--kb-rust);background:rgba(255,255,255,.08);padding:0 4px;border-radius:3px}
.tpl-knowledge-arch-blueprint .kb-big-num{font-family:'Playfair Display',Georgia,serif;font-size:200px;font-weight:900;line-height:.9;color:var(--kb-rust);letter-spacing:-.04em}
templates/full-decks/product-launch/README.md
# product-launch
8-slide consumer product announcement deck: hero cover, "introducing" moment, three feature slides, how-it-works, pricing tiers, and a closing testimonial + pre-order CTA.
Mixes dark hero slides (for show-off moments) with light slides (for details and pricing). Warm orange→peach gradient accent feels confident and human; easy to re-skin for any brand.
**Use when:** launching a product, announcing a v2, internal all-hands reveals, press kit decks.
**Feel:** Apple-event-on-a-budget — confident, tactile, uncluttered.
templates/full-decks/knowledge-arch-blueprint/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Knowledge Arch Blueprint</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-knowledge-arch-blueprint">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="kb-grid-bg"></div>
<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:40px;margin-bottom:44px">
<div>
<div class="kb-kicker">Karpathy Stack · 架构图 v2</div>
<h1 class="kb-h1">LLM <span class="rust">知识库</span> 的<br>工程化蓝图</h1>
<p class="kb-sub">从「乱贴笔记」到「可审计、可纠错、可复用」的第二大脑 —— 这是我读完 Karpathy 的分享后画的一张系统图。</p>
</div>
<div class="kb-insight"><span class="kk">KEY INSIGHT</span>Karpathy 原版缺一块:<br>反馈闭环让错误能回流纠正。</div>
</div>
<div class="kb-section-label">Pipeline · End-to-end</div>
<div class="kb-pipeline">
<div class="kb-step"><div class="kb-step-num">STEP 01</div><div class="kb-step-title">采集</div><div class="kb-step-body">浏览器剪藏、PDF、Podcast 转写、聊天记录</div></div>
<div class="kb-step"><div class="kb-step-num">STEP 02</div><div class="kb-step-title">去噪</div><div class="kb-step-body">清洗导航栏、广告、重复段落、低信噪素材</div></div>
<div class="kb-step hero"><div class="kb-step-num">STEP 03 · CORE</div><div class="kb-step-title">Wiki 化</div><div class="kb-step-body">结构化成双链笔记,实体、关系、属性全在一起</div></div>
<div class="kb-step"><div class="kb-step-num">STEP 04</div><div class="kb-step-title">使用</div><div class="kb-step-body">Agent 随时检索、回答、再写入</div></div>
</div>
<div class="kb-footer"><span>Blueprint · v2 · 2026.04</span><span>01 / 08</span></div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div style="margin:auto 0">
<div class="kb-kicker">Chapter One</div>
<h1 class="kb-h1" style="font-size:120px">为什么 <span class="rust">笔记</span><br>不够用了</h1>
<p class="kb-sub" style="font-size:24px;margin-top:20px">当你的知识量超过记忆容量,<br>你需要的不是更多文件,而是一张<b>可导航的图</b>。</p>
</div>
<div class="kb-footer"><span>Section · Chapter 1</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT 2-col -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">Problem · Solution</div>
<h1 class="kb-h1" style="font-size:48px">原版 vs <span class="rust">升级版</span></h1>
<div class="kb-grid-2">
<div class="kb-card">
<div class="kb-kicker" style="color:#888">原版 Karpathy</div>
<h4>一次性写入</h4>
<p>采集 → 转写 → 存档,错了就错了。没有回路,没有修正机制,笔记越多越混乱。</p>
</div>
<div class="kb-card" style="background:var(--kb-rust-soft);border-color:var(--kb-rust)">
<div class="kb-kicker">升级 v2</div>
<h4>反馈闭环</h4>
<p>AI 使用知识库时记录每次 miss / 幻觉 / 过期事实,自动回灌到源文件,让笔记会自我修正。</p>
</div>
</div>
<div class="kb-legend">
<div class="d"><span class="b"></span>普通节点</div>
<div class="d"><span class="b rust"></span>核心节点 · 反馈回路入口</div>
<div class="d">—— 数据流 ┈┈ 反馈回路</div>
</div>
<div class="kb-footer"><span>Content · Compare</span><span>03 / 08</span></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">Implementation · Skill Manifest</div>
<h1 class="kb-h1" style="font-size:48px">反馈回路 <span class="rust">怎么实现</span></h1>
<p class="kb-sub">一个 100 行的 Agent Skill,把「AI 用得顺不顺」回写成 vault 的一条条修订记录。</p>
<pre class="kb-codebox"><span class="cm"># skills/wiki-feedback/SKILL.md</span>
<span class="kw">name</span>: wiki-feedback
<span class="kw">trigger</span>: <span class="st">"after every retrieval"</span>
<span class="kw">on_hit</span>: record(<span class="st">query, path, used=true</span>)
<span class="kw">on_miss</span>: record(<span class="st">query, reason=</span><span class="hl">"not-in-vault"</span>)
<span class="kw">on_wrong</span>: record(<span class="st">query, correction, path</span>)
<span class="kw">nightly</span>:
- <span class="st">aggregate misses → suggest new notes</span>
- <span class="st">aggregate wrongs → diff-patch old notes</span>
- <span class="st">commit to git, open PR for human review</span></pre>
<div class="kb-footer"><span>Content · Code</span><span>04 / 08</span></div>
</section>
<!-- 5. DIAGRAM - SVG feedback loop -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">System Diagram</div>
<h1 class="kb-h1" style="font-size:44px">反馈回路全貌</h1>
<svg viewBox="0 0 1200 520" style="width:100%;max-width:1200px;margin-top:20px" xmlns="http://www.w3.org/2000/svg">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#1a1a1a"/>
</marker>
<marker id="arrow-r" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
<path d="M0,0 L10,5 L0,10 z" fill="#B5392A"/>
</marker>
</defs>
<g font-family="Inter, sans-serif" font-size="16" font-weight="700">
<!-- boxes -->
<rect x="40" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
<text x="140" y="220" text-anchor="middle">Sources</text>
<text x="140" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">web · pdf · chat</text>
<rect x="300" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
<text x="400" y="220" text-anchor="middle">Clean + Split</text>
<text x="400" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">defuddle / chunker</text>
<rect x="560" y="160" width="220" height="160" rx="10" fill="#B5392A" stroke="#B5392A" stroke-width="2"/>
<text x="670" y="210" text-anchor="middle" fill="#fff" font-size="20">Vault (Wiki)</text>
<text x="670" y="240" text-anchor="middle" font-size="12" font-weight="400" fill="rgba(255,255,255,.8)">markdown · links</text>
<text x="670" y="262" text-anchor="middle" font-size="12" font-weight="400" fill="rgba(255,255,255,.8)">bases · canvas</text>
<rect x="840" y="180" width="200" height="120" rx="10" fill="#fff" stroke="#1a1a1a" stroke-width="2"/>
<text x="940" y="220" text-anchor="middle">Agent Use</text>
<text x="940" y="250" text-anchor="middle" font-size="12" font-weight="400" fill="#666">retrieve / answer</text>
<!-- forward arrows -->
<line x1="245" y1="240" x2="295" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="505" y1="240" x2="555" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="785" y1="240" x2="835" y2="240" stroke="#1a1a1a" stroke-width="2" marker-end="url(#arrow)"/>
<!-- feedback dashed -->
<path d="M 940 180 Q 940 80, 670 80 Q 400 80, 400 180" fill="none" stroke="#B5392A" stroke-width="2" stroke-dasharray="6 6" marker-end="url(#arrow-r)"/>
<rect x="580" y="58" width="180" height="30" rx="6" fill="#F0EAE0" stroke="#B5392A" stroke-width="1"/>
<text x="670" y="78" text-anchor="middle" fill="#B5392A" font-size="12">FEEDBACK · wrong / miss</text>
<!-- bottom feedback to sources -->
<path d="M 940 300 Q 940 420, 670 420 Q 140 420, 140 300" fill="none" stroke="#B5392A" stroke-width="2" stroke-dasharray="6 6" marker-end="url(#arrow-r)"/>
<rect x="560" y="400" width="220" height="30" rx="6" fill="#F0EAE0" stroke="#B5392A" stroke-width="1"/>
<text x="670" y="420" text-anchor="middle" fill="#B5392A" font-size="12">NIGHTLY · suggest new sources</text>
</g>
</svg>
<div class="kb-footer"><span>Diagram · Feedback Loop</span><span>05 / 08</span></div>
</section>
<!-- 6. STATS -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">After 6 Months</div>
<h1 class="kb-h1" style="font-size:44px">升级版 <span class="rust">跑了半年</span> 的数据</h1>
<div style="display:grid;grid-template-columns:1.3fr 1fr 1fr;gap:24px;margin-top:28px;align-items:center">
<div style="text-align:center"><div class="kb-big-num">13</div><p style="font-size:14px;color:#666;margin-top:6px;letter-spacing:.1em;text-transform:uppercase">关键优化项 · 全部落地</p></div>
<div class="kb-card"><h4 style="color:var(--kb-rust)">-62%</h4><p>幻觉率(相比无反馈回路版本)</p></div>
<div class="kb-card"><h4 style="color:var(--kb-rust)">+4.1×</h4><p>单次检索命中率</p></div>
</div>
<div class="kb-grid-2" style="margin-top:18px">
<div class="kb-card"><h4>自动修订 227 条</h4><p>其中 189 条被人工批准合并,38 条被拒绝(数据已归档)。</p></div>
<div class="kb-card"><h4>新增笔记 412 篇</h4><p>从 miss 日志聚类而来,每篇都有来源追溯。</p></div>
</div>
<div class="kb-footer"><span>Content · Stats</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div class="kb-kicker">Next Step</div>
<h1 class="kb-h1" style="font-size:60px">开始你的 <span class="rust">Wiki v2</span></h1>
<p class="kb-sub">不用重写所有笔记。先接一条回路,让 AI 的每次使用都在「改好」你的 vault。</p>
<div class="kb-pipeline">
<div class="kb-step"><div class="kb-step-num">TONIGHT</div><div class="kb-step-title">装 Skill</div><div class="kb-step-body">pnpm i -g @lewis/wiki-feedback</div></div>
<div class="kb-step"><div class="kb-step-num">DAY 2</div><div class="kb-step-title">跑 7 天</div><div class="kb-step-body">观察 miss log 自动累积</div></div>
<div class="kb-step hero"><div class="kb-step-num">DAY 8 · CORE</div><div class="kb-step-title">第一次审 PR</div><div class="kb-step-body">花 15 分钟 review 自动生成的修订</div></div>
<div class="kb-step"><div class="kb-step-num">MONTH 1</div><div class="kb-step-title">开始信它</div><div class="kb-step-body">你的 vault 会变成活的</div></div>
</div>
<div class="kb-footer"><span>CTA</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="kb-grid-bg"></div>
<div style="margin:auto 0;text-align:center">
<div class="kb-kicker">END · blueprint v2</div>
<h1 class="kb-h1" style="font-size:140px;margin-top:24px">谢谢 <span class="rust">·</span> thanks</h1>
<p class="kb-sub" style="margin:0 auto;font-size:22px">图纸、Skill、笔记模板都在 <b>github.com/lewis/karpathy-wiki-v2</b></p>
</div>
<div class="kb-footer"><span>End of deck</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/product-launch/style.css
/* product-launch — modern announcement deck */
.tpl-product-launch{
--bg:#ffffff;--bg-soft:#f5f5f7;--surface:#ffffff;--surface-2:#f2f2f6;
--ink:#0a0a12;--ink-2:#3a3a44;
--border:rgba(10,10,18,.08);--border-strong:rgba(10,10,18,.18);
--text-1:#0a0a12;--text-2:#4a4a58;--text-3:#8a8a96;
--accent:#ff5a36;--accent-2:#ff8c5a;--accent-3:#ffb36b;
--grad:linear-gradient(120deg,#ff5a36 0%,#ff8c5a 60%,#ffb36b 100%);
--radius:22px;--radius-lg:32px;
--shadow:0 20px 60px rgba(10,10,18,.1);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-product-launch .slide{padding:80px 112px}
.tpl-product-launch .slide.dark{background:#0a0a12;color:#f5f5f7}
.tpl-product-launch .slide.dark .h1,.tpl-product-launch .slide.dark .h2,.tpl-product-launch .slide.dark h3,.tpl-product-launch .slide.dark h4{color:#fff}
.tpl-product-launch .slide.dark .lede,.tpl-product-launch .slide.dark .dim{color:rgba(245,245,247,.72)}
.tpl-product-launch .slide.dark .card{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.12);box-shadow:none;backdrop-filter:blur(20px)}
.tpl-product-launch .slide.dark .kicker{color:var(--accent-2)}
.tpl-product-launch .h1{font-size:96px;line-height:.98;font-weight:900;letter-spacing:-.045em}
.tpl-product-launch .h2{font-size:64px;font-weight:800;letter-spacing:-.035em}
.tpl-product-launch .hero-shot{position:absolute;right:-60px;top:50%;transform:translateY(-50%);width:640px;height:640px;border-radius:50%;background:var(--grad);filter:blur(2px);opacity:.85}
.tpl-product-launch .hero-shot::after{content:"";position:absolute;inset:80px;border-radius:40px;background:linear-gradient(160deg,rgba(255,255,255,.3),transparent 60%),#1a1a28;box-shadow:inset 0 2px 0 rgba(255,255,255,.2)}
.tpl-product-launch .hero-shot::before{content:"Halo v2";position:absolute;inset:80px;display:flex;align-items:center;justify-content:center;color:#fff;font-size:44px;font-weight:900;letter-spacing:-.02em;z-index:2;border-radius:40px}
.tpl-product-launch .brand{font-size:18px;font-weight:800;letter-spacing:-.02em}
.tpl-product-launch .feature-card{padding:40px 36px;border-radius:var(--radius-lg);background:var(--surface);border:1px solid var(--border);position:relative;overflow:hidden}
.tpl-product-launch .feature-card .icon{width:60px;height:60px;border-radius:18px;background:var(--grad);display:flex;align-items:center;justify-content:center;color:#fff;font-size:28px;font-weight:900;margin-bottom:20px}
.tpl-product-launch .step{display:flex;gap:24px;align-items:flex-start}
.tpl-product-launch .step .n{flex:none;width:56px;height:56px;border-radius:50%;background:var(--grad);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:900;font-size:22px}
.tpl-product-launch .price-card{padding:40px 32px;border-radius:var(--radius-lg);border:1.5px solid var(--border);background:var(--surface);text-align:left}
.tpl-product-launch .price-card.pro{background:#0a0a12;color:#fff;border-color:#0a0a12;transform:scale(1.04);box-shadow:0 30px 80px rgba(255,90,54,.25)}
.tpl-product-launch .price-card.pro .dim{color:rgba(255,255,255,.7)}
.tpl-product-launch .price-card h4{font-size:16px;text-transform:uppercase;letter-spacing:.1em;color:var(--accent)}
.tpl-product-launch .price-card.pro h4{color:var(--accent-2)}
.tpl-product-launch .price-card .amount{font-size:64px;font-weight:900;letter-spacing:-.035em;margin:14px 0}
.tpl-product-launch .price-card ul{list-style:none;padding:0;margin:20px 0 0}
.tpl-product-launch .price-card li{padding:8px 0;font-size:15px;color:var(--text-2);border-top:1px solid var(--border)}
.tpl-product-launch .price-card.pro li{color:rgba(255,255,255,.8);border-color:rgba(255,255,255,.12)}
.tpl-product-launch .cta-btn{display:inline-block;padding:20px 40px;border-radius:999px;background:var(--grad);color:#fff;font-weight:700;font-size:20px;box-shadow:0 20px 50px rgba(255,90,54,.4)}
.tpl-product-launch .testimonial{max-width:44ch;font-family:'Playfair Display',serif;font-size:44px;line-height:1.25;font-weight:500;letter-spacing:-.01em}
templates/full-decks/product-launch/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Halo v2 · Launch</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-product-launch">
<div class="deck">
<!-- 1. Cover / hero -->
<section class="slide dark" data-title="Cover">
<div class="hero-shot"></div>
<div style="position:absolute;top:56px;left:112px" class="brand">◎ Halo</div>
<p class="kicker">Launch · April 2026</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">Meet Halo v2.<br>Your ears,<br><span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">rewritten.</span></h1>
<p class="lede mt-m" style="max-width:42ch">Studio-grade spatial audio in the lightest open-ear earbuds ever made.</p>
<div class="deck-footer"><span>halo.audio</span><span class="slide-number" data-current="1" data-total="8"></span></div>
</section>
<!-- 2. Introducing -->
<section class="slide center tc" data-title="Introducing">
<div>
<p class="kicker">Introducing</p>
<h1 class="h1" style="font-size:140px">Halo v2</h1>
<p class="lede" style="margin:24px auto;max-width:56ch">Four years of research. Three generations of silicon. One product you'll forget you're wearing.</p>
</div>
</section>
<!-- 3. Feature 1 -->
<section class="slide" data-title="Sound">
<p class="kicker">01 · The sound</p>
<h2 class="h2">Hear the room<br>around the music.</h2>
<div class="grid g3 mt-l">
<div class="feature-card"><div class="icon">♪</div><h4>Open-ear spatial</h4><p class="dim">16mm titanium drivers angled into the ear canal. You hear the song and the world at once.</p></div>
<div class="feature-card"><div class="icon">◈</div><h4>Lossless 24-bit</h4><p class="dim">aptX Lossless and Hi-Res LDAC over Bluetooth 5.4. No dongles, no compromises.</p></div>
<div class="feature-card"><div class="icon">◐</div><h4>Adaptive EQ</h4><p class="dim">Tunes itself to the shape of your ear every 120 seconds.</p></div>
</div>
</section>
<!-- 4. Feature 2 -->
<section class="slide dark" data-title="Fit">
<p class="kicker">02 · The fit</p>
<h2 class="h2">4.9 grams.<br>All-day forgettable.</h2>
<div class="grid g3 mt-l">
<div class="card"><h4>Liquid-silicone hook</h4><p>Wraps behind the ear like a glasses arm. Never falls out on a run.</p></div>
<div class="card"><h4>IP57 sweat + rain</h4><p>Take them in the ocean. Rinse them under the tap. We dare you.</p></div>
<div class="card"><h4>14h + 42h case</h4><p>A full workweek of commutes on one charge of the case.</p></div>
</div>
</section>
<!-- 5. Feature 3 -->
<section class="slide" data-title="Intelligence">
<p class="kicker">03 · The intelligence</p>
<h2 class="h2">An AI that listens<br>so you don't have to.</h2>
<div class="grid g2 mt-l">
<div class="feature-card"><div class="icon">✦</div><h4>Live translate</h4><p class="dim">Real-time translation in 41 languages. Whispered directly into your ear, with a 380ms lag.</p></div>
<div class="feature-card"><div class="icon">✧</div><h4>Meeting recap</h4><p class="dim">Double-tap to record. Walk away with a summary, action items, and a searchable transcript.</p></div>
</div>
</section>
<!-- 6. How it works -->
<section class="slide" data-title="How it works">
<p class="kicker">How it works</p>
<h2 class="h2">Three taps. You're in.</h2>
<div class="stack mt-l" style="max-width:900px">
<div class="step"><div class="n">1</div><div><h4>Open the case near your phone</h4><p class="dim">iOS and Android pair automatically over Bluetooth LE. No app downloads required.</p></div></div>
<div class="step"><div class="n">2</div><div><h4>Pick your profile</h4><p class="dim">Commute, Focus, Workout, Cinema. Each is a complete audio + transparency recipe.</p></div></div>
<div class="step"><div class="n">3</div><div><h4>Just listen</h4><p class="dim">Halo adapts to your ear shape, your environment, and your hearing profile — continuously.</p></div></div>
</div>
</section>
<!-- 7. Pricing -->
<section class="slide" data-title="Pricing">
<p class="kicker">Pricing</p>
<h2 class="h2">Pick your Halo.</h2>
<div class="grid g3 mt-l" style="align-items:start">
<div class="price-card">
<h4>Halo Lite</h4>
<div class="amount">$179</div>
<p class="dim">Open-ear audio, IP57, 12h battery.</p>
<ul><li>AAC + SBC</li><li>Single-tap controls</li><li>USB-C charging</li></ul>
</div>
<div class="price-card pro">
<h4>Halo v2 · Pro</h4>
<div class="amount">$279</div>
<p class="dim">Everything, in its best form.</p>
<ul><li>Hi-Res Lossless</li><li>Live translate · 41 lang</li><li>Wireless + MagSafe charging</li><li>Adaptive EQ</li></ul>
</div>
<div class="price-card">
<h4>Halo Studio</h4>
<div class="amount">$399</div>
<p class="dim">For creators and field recorders.</p>
<ul><li>32-bit binaural capture</li><li>XLR dongle included</li><li>Lifetime firmware</li></ul>
</div>
</div>
</section>
<!-- 8. Testimonial + CTA combined? Task says 8 slides w/ testimonial + CTA as separate. Keep 8: testimonial on 7, but we've used 7 already. Re-plan: cover(1) intro(2) f1(3) f2(4) f3(5) how(6) pricing(7) testimonial+CTA(8) -->
<section class="slide dark" data-title="Ship">
<p class="kicker">One more thing</p>
<div class="row" style="gap:80px;align-items:center">
<div style="flex:1">
<p class="testimonial">"I forgot I was wearing them. Then I remembered, and I didn't want to take them off."</p>
<p class="dim mt-m">— Marques Lin, The Verge · early review</p>
</div>
<div style="flex:0 0 auto;text-align:center">
<p class="dim mb-m">Ships May 14 · from</p>
<div style="font-size:96px;font-weight:900;letter-spacing:-.04em">$279</div>
<a class="cta-btn mt-l" href="#">Pre-order Halo v2 →</a>
<p class="dim mt-m" style="font-size:13px">Free shipping · 45-day return · 2-year warranty</p>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>
templates/full-decks/tech-sharing/README.md
# tech-sharing · 技术分享
8-slide engineering talk deck: cover (topic + speaker), agenda, context, two deep-dive slides, a code example, takeaways, Q&A.
Dark GitHub-ish palette (`#0d1117`) with JetBrains Mono accents and syntax-highlighted terminal blocks. Built to be screenshotted and shared on an internal wiki or Twitter.
**Use when:** tech-sharing Fridays, brown-bag talks, lunch & learns, conference submissions.
**Feel:** GitHub README meets a good conference talk — dark, monospaced, dense but readable.
templates/full-decks/pitch-deck/style.css
/* pitch-deck — classic YC/VC pitch */
.tpl-pitch-deck{
--bg:#ffffff;--bg-soft:#f6f7fb;--surface:#ffffff;--surface-2:#f2f4fa;
--border:rgba(20,25,60,.08);--border-strong:rgba(20,25,60,.18);
--text-1:#0d1130;--text-2:#4a5070;--text-3:#8a90ad;
--accent:#3b5bff;--accent-2:#7a46ff;--accent-3:#d94cff;
--grad:linear-gradient(135deg,#3b5bff 0%,#7a46ff 55%,#d94cff 100%);
--grad-soft:linear-gradient(135deg,#eef1ff,#f4edff 55%,#fbedff);
--radius:20px;--radius-lg:28px;
--shadow:0 14px 40px rgba(20,25,60,.08),0 2px 8px rgba(20,25,60,.04);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-pitch-deck .slide{padding:88px 112px}
.tpl-pitch-deck .kicker{color:var(--accent);font-weight:700}
.tpl-pitch-deck .h1{font-size:86px;line-height:1.02;font-weight:900;letter-spacing:-.035em}
.tpl-pitch-deck .h2{font-size:62px;font-weight:800;letter-spacing:-.03em}
.tpl-pitch-deck .mega{font-size:180px;font-weight:900;line-height:.95;letter-spacing:-.05em;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent}
.tpl-pitch-deck .mega-sub{font-size:28px;color:var(--text-2);margin-top:18px}
.tpl-pitch-deck .cover-bg{position:absolute;inset:0;background:var(--grad-soft);z-index:-1}
.tpl-pitch-deck .cover-blob{position:absolute;right:-140px;top:-140px;width:560px;height:560px;border-radius:50%;background:var(--grad);filter:blur(8px);opacity:.35;z-index:-1}
.tpl-pitch-deck .brand-dot{display:inline-block;width:14px;height:14px;border-radius:50%;background:var(--grad);margin-right:10px;vertical-align:middle}
.tpl-pitch-deck .brand{font-weight:800;font-size:22px;letter-spacing:-.02em}
.tpl-pitch-deck .card{border-radius:var(--radius)}
.tpl-pitch-deck .num-tag{font-family:'Inter',sans-serif;font-size:14px;font-weight:700;color:var(--accent);letter-spacing:.12em}
.tpl-pitch-deck .big-q{font-family:'Playfair Display',serif;font-size:56px;line-height:1.15;font-weight:700;letter-spacing:-.02em;max-width:22ch}
.tpl-pitch-deck .metric{display:flex;flex-direction:column;gap:6px}
.tpl-pitch-deck .metric .n{font-size:72px;font-weight:900;letter-spacing:-.035em;background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent;line-height:1}
.tpl-pitch-deck .metric .l{color:var(--text-2);font-size:16px}
.tpl-pitch-deck .team-card{text-align:center;padding:32px 20px}
.tpl-pitch-deck .avatar{width:96px;height:96px;border-radius:50%;margin:0 auto 14px;background:var(--grad);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:800;font-size:32px}
.tpl-pitch-deck .ask-box{background:var(--grad);color:#fff;padding:56px 64px;border-radius:var(--radius-lg);box-shadow:0 30px 70px rgba(59,91,255,.35)}
.tpl-pitch-deck .ask-box .h2{color:#fff}
.tpl-pitch-deck .ask-box .dim{color:rgba(255,255,255,.85)}
.tpl-pitch-deck .traction-bar{display:flex;align-items:flex-end;gap:14px;height:240px;margin-top:24px}
.tpl-pitch-deck .traction-bar .bar{flex:1;background:var(--grad);border-radius:8px 8px 0 0;position:relative;min-height:20px}
.tpl-pitch-deck .traction-bar .bar span{position:absolute;bottom:-28px;left:0;right:0;text-align:center;font-size:13px;color:var(--text-3)}
.tpl-pitch-deck .traction-bar .bar em{position:absolute;top:-28px;left:0;right:0;text-align:center;font-size:14px;font-weight:700;font-style:normal;color:var(--text-1)}
.tpl-pitch-deck .section-num{font-size:220px;font-weight:900;line-height:.9;color:var(--surface-2);position:absolute;right:72px;bottom:40px;z-index:0;letter-spacing:-.05em}
.tpl-pitch-deck .slide > *{position:relative;z-index:1}
.tpl-pitch-deck .deck-footer{color:var(--text-3)}
templates/full-decks/testing-safety-alert/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Testing Safety Alert</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-testing-safety-alert">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag">ai safety · 高优先级</span><span class="ts-page">01 / 08</span></div>
<div class="ts-kicker">2026 年最重要的一条判断</div>
<h1 class="ts-h1">别再追问<br><span class="strike">AI 会不会干活</span><br>开始问:<span class="red">它出事谁负责</span></h1>
<p class="ts-sub">AI 出错的代价,不再是一次 bad response 这么简单 —— 它可能一次性写 300 份工单、提 80 个 PR、发 5000 封邮件。</p>
<div class="ts-alert-box">
<h3>风险已经规模化</h3>
<p>「做错」成本 × N;「做对」收益 × N。<br>这就是为什么 <b>测试、验收、安全、风控</b> 会变成未来 3 年最贵的能力。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>AI SAFETY BRIEF · LEWIS · 2026.04</span><span>01 / 08</span></div>
</section>
<!-- 2. SECTION -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag amber">section · risk 分级</span><span class="ts-page">02 / 08</span></div>
<div style="margin:auto 0">
<div class="ts-kicker">Chapter One</div>
<h1 class="ts-h1" style="font-size:130px">先分 <span class="red">等级</span></h1>
<p class="ts-sub" style="font-size:28px">不是所有 AI 行为都同等危险。<br>先把「可撤销」和「不可撤销」分开,再谈流程。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>section · level taxonomy</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT risk levels -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag">风险分级 · 3 levels</span><span class="ts-page">03 / 08</span></div>
<h2 class="ts-h2">三档风险,三种处理</h2>
<div class="ts-grid-3">
<div class="ts-card" style="border-top:4px solid var(--ts-green)"><div class="lbl">L1 · 绿色</div><h4>可撤销</h4><p>写 draft、生成图片、起草文档。<br>错了 Ctrl+Z,零代价。<br><b style="color:var(--ts-green)">策略:放开跑</b></p></div>
<div class="ts-card" style="border-top:4px solid var(--ts-amber)"><div class="lbl">L2 · 琥珀</div><h4>半可撤销</h4><p>发 draft 邮件、提 PR、改 staging 数据。<br>错了要道歉 / 回滚。<br><b style="color:var(--ts-amber)">策略:人工复核</b></p></div>
<div class="ts-card" style="border-top:4px solid var(--ts-red)"><div class="lbl">L3 · 红色</div><h4>不可撤销</h4><p>发真实邮件、付款、删库、删 prod 数据。<br>错了就真错了。<br><b style="color:var(--ts-red)">策略:硬卡 + 双人审</b></p></div>
</div>
<div class="ts-alert-box amber">
<h3>绝不要让 agent 自己升级</h3>
<p>L1 的任务不能自己变成 L2。授权必须是显式的、可撤销的、带过期时间的。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>risk · 3 levels</span><span>03 / 08</span></div>
</section>
<!-- 4. CODE -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag">policy as code</span><span class="ts-page">04 / 08</span></div>
<div class="ts-kicker">别用文档管规则 · 用代码管规则</div>
<h2 class="ts-h2">三十行 YAML,<br><span class="ts-highlight-red">红线硬卡</span></h2>
<pre class="ts-codebox"><span class="cm"># safety-policy.yaml · compiled → runtime guard</span>
<span class="kw">level_1_allow</span>:
- tools: [<span class="st">write_draft</span>, <span class="st">generate_image</span>, <span class="st">read_docs</span>]
<span class="kw">level_2_require_review</span>:
- tools: [<span class="st">send_email_draft</span>, <span class="st">open_pr</span>, <span class="st">write_staging_db</span>]
reviewer: <span class="st">human</span>
<span class="kw">level_3_hard_block</span>:
- tools: [<span class="st">send_real_email</span>, <span class="st">transfer_money</span>, <span class="st">delete_prod</span>]
unless: <span class="st">two_human_sign_off AND within_24h</span>
<span class="bad">forbidden_always</span>:
- <span class="bad">"rm -rf /"</span>
- <span class="bad">"drop table"</span>
- <span class="bad">"force push origin main"</span></pre>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>policy · yaml-as-guard</span><span>04 / 08</span></div>
</section>
<!-- 5. CHART -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag amber">incident report · q1</span><span class="ts-page">05 / 08</span></div>
<h2 class="ts-h2">我们 Q1 的 <span class="red">12 起 AI 事故</span></h2>
<p class="ts-sub">幸好全部捕获在 staging。但每一起都能上生产。</p>
<svg viewBox="0 0 1040 360" style="width:100%;max-width:1040px;margin-top:18px" xmlns="http://www.w3.org/2000/svg">
<g font-family="Inter,sans-serif" font-size="14" fill="#4a4955">
<line x1="70" y1="320" x2="1000" y2="320" stroke="#eaecf3" stroke-width="2"/>
<!-- month columns: Jan Feb Mar, L1/L2/L3 stacked -->
<g transform="translate(120,0)">
<rect x="0" y="220" width="60" height="100" fill="#067647"/>
<rect x="0" y="160" width="60" height="60" fill="#d97706"/>
<rect x="0" y="130" width="60" height="30" fill="#e0314a"/>
<text x="30" y="345" text-anchor="middle" font-weight="700">Jan</text>
<text x="30" y="120" text-anchor="middle" font-weight="800" fill="#14141a">5</text>
</g>
<g transform="translate(320,0)">
<rect x="0" y="240" width="60" height="80" fill="#067647"/>
<rect x="0" y="200" width="60" height="40" fill="#d97706"/>
<rect x="0" y="180" width="60" height="20" fill="#e0314a"/>
<text x="30" y="345" text-anchor="middle" font-weight="700">Feb</text>
<text x="30" y="170" text-anchor="middle" font-weight="800" fill="#14141a">3</text>
</g>
<g transform="translate(520,0)">
<rect x="0" y="250" width="60" height="70" fill="#067647"/>
<rect x="0" y="220" width="60" height="30" fill="#d97706"/>
<rect x="0" y="210" width="60" height="10" fill="#e0314a"/>
<text x="30" y="345" text-anchor="middle" font-weight="700">Mar</text>
<text x="30" y="200" text-anchor="middle" font-weight="800" fill="#14141a">4</text>
</g>
<!-- legend -->
<g transform="translate(720,60)">
<rect x="0" y="0" width="16" height="16" fill="#e0314a"/><text x="24" y="13" font-weight="700">L3 不可撤销 (3)</text>
<rect x="0" y="26" width="16" height="16" fill="#d97706"/><text x="24" y="39" font-weight="700">L2 需复核 (4)</text>
<rect x="0" y="52" width="16" height="16" fill="#067647"/><text x="24" y="65" font-weight="700">L1 可恢复 (5)</text>
<text x="0" y="100" font-size="13" fill="#8a8892">全部被 safety-policy 在 runtime 拦下,</text>
<text x="0" y="118" font-size="13" fill="#8a8892">未进 prod。但 3 起 L3 非常惊险。</text>
</g>
</g>
</svg>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>incident · q1 summary</span><span>05 / 08</span></div>
</section>
<!-- 6. CHECKLIST -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag green">red-team checklist</span><span class="ts-page">06 / 08</span></div>
<h2 class="ts-h2">上线前 <span class="red">必过 7 道题</span></h2>
<div class="ts-checklist">
<div class="ts-check ok"><div class="box">✓</div><div class="txt">它能删除东西吗?有人类 review 吗?能 60 秒内回滚吗?</div></div>
<div class="ts-check ok"><div class="box">✓</div><div class="txt">它的 prompt 注入能让它越权吗?(跑过红队提示词)</div></div>
<div class="ts-check"><div class="box">!</div><div class="txt">它处理 PII 吗?日志里是不是也有 PII?</div></div>
<div class="ts-check ok"><div class="box">✓</div><div class="txt">上下游失败时,它会不会开始乱改其他资源?</div></div>
<div class="ts-check"><div class="box">!</div><div class="txt">并发 100 个 agent 一起跑会不会死锁?</div></div>
<div class="ts-check ok"><div class="box">✓</div><div class="txt">错了能不能 <b>立刻</b> 停?(kill switch 能 2 秒内生效吗)</div></div>
<div class="ts-check"><div class="box">!</div><div class="txt">出事时有没有人值班?值班手册有没有 agent 专属章节?</div></div>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>checklist · pre-launch</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag green">今晚就能动</span><span class="ts-page">07 / 08</span></div>
<h2 class="ts-h2">今晚先做 <span class="ts-highlight-red">三件事</span></h2>
<div class="ts-grid-3">
<div class="ts-card"><div class="lbl">1 · 分级</div><h4>给你的 agent<br>写 L1/L2/L3</h4><p>把所有工具列出来,标上等级。不标的一律按 L3。</p></div>
<div class="ts-card"><div class="lbl">2 · 写 policy</div><h4>policy.yaml<br>接 runtime</h4><p>不要信 prompt 里的 "be careful",要信执行层的硬卡。</p></div>
<div class="ts-card"><div class="lbl">3 · kill switch</div><h4>红按钮<br>能在 2 秒内停</h4><p>CTO / on-call 都得知道怎么按。演练一次。</p></div>
</div>
<div class="ts-alert-box green">
<h3>真正的安全不是 prompt,是流程</h3>
<p>prompt 会被注入,流程不会。—— 把保护放在不可被说服的一层。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>cta · tonight</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="ts-stripe"></div>
<div class="ts-chrome"><span class="ts-alert-tag amber">please stay safe</span><span class="ts-page">08 / 08</span></div>
<div style="margin:auto 0">
<div class="ts-kicker">end of brief</div>
<h1 class="ts-h1" style="font-size:140px">谢谢 <span class="red">·</span> thanks</h1>
<p class="ts-sub" style="font-size:24px">policy.yaml 模板、红队 prompt 清单、事故复盘模板 —— 评论区扣「安全」。</p>
</div>
<div class="ts-stripe-b"></div>
<div class="ts-footer"><span>end of brief</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/weekly-report/style.css
/* weekly-report — corporate clarity */
.tpl-weekly-report{
--bg:#fafbfc;--bg-soft:#f3f5f9;--surface:#ffffff;--surface-2:#f3f5f9;
--border:rgba(22,30,55,.09);--border-strong:rgba(22,30,55,.2);
--text-1:#161e37;--text-2:#50586b;--text-3:#8b92a5;
--accent:#2e63eb;--accent-2:#0ea5b5;--accent-3:#f59e0b;
--good:#10b981;--warn:#f59e0b;--bad:#ef4444;
--grad:linear-gradient(120deg,#2e63eb,#0ea5b5);
--radius:14px;--radius-lg:18px;
--shadow:0 6px 20px rgba(22,30,55,.06),0 1px 3px rgba(22,30,55,.04);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-weekly-report .slide{padding:64px 88px;background:var(--bg)}
.tpl-weekly-report .h1{font-size:64px;line-height:1.05;font-weight:800;letter-spacing:-.025em}
.tpl-weekly-report .h2{font-size:42px;font-weight:700;letter-spacing:-.02em}
.tpl-weekly-report .kicker{color:var(--accent);font-size:12px;font-weight:700}
.tpl-weekly-report .cover-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:48px}
.tpl-weekly-report .logo{font-weight:800;font-size:18px;letter-spacing:-.01em}
.tpl-weekly-report .logo::before{content:"■";color:var(--accent);margin-right:8px}
.tpl-weekly-report .week-chip{display:inline-block;padding:8px 18px;border-radius:8px;background:var(--surface);border:1px solid var(--border);font-family:'JetBrains Mono',monospace;font-size:13px;color:var(--text-2)}
.tpl-weekly-report .kpi{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px 26px;position:relative;overflow:hidden}
.tpl-weekly-report .kpi .label{font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-3);font-weight:600}
.tpl-weekly-report .kpi .value{font-size:48px;font-weight:800;letter-spacing:-.03em;margin-top:8px;line-height:1}
.tpl-weekly-report .kpi .delta{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:6px;font-size:12px;font-weight:700;margin-top:10px}
.tpl-weekly-report .kpi .delta.up{background:rgba(16,185,129,.12);color:var(--good)}
.tpl-weekly-report .kpi .delta.down{background:rgba(239,68,68,.12);color:var(--bad)}
.tpl-weekly-report .kpi .delta.flat{background:rgba(139,146,165,.14);color:var(--text-2)}
.tpl-weekly-report .kpi::before{content:"";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--accent)}
.tpl-weekly-report .kpi.good::before{background:var(--good)}
.tpl-weekly-report .kpi.warn::before{background:var(--warn)}
.tpl-weekly-report .kpi.bad::before{background:var(--bad)}
.tpl-weekly-report .ship-item{display:flex;gap:14px;padding:14px 0;border-bottom:1px solid var(--border)}
.tpl-weekly-report .ship-item .tag{flex:none;padding:3px 10px;border-radius:6px;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;height:22px;display:inline-flex;align-items:center}
.tpl-weekly-report .tag.feat{background:rgba(46,99,235,.12);color:var(--accent)}
.tpl-weekly-report .tag.fix{background:rgba(16,185,129,.12);color:var(--good)}
.tpl-weekly-report .tag.exp{background:rgba(245,158,11,.14);color:var(--warn)}
.tpl-weekly-report .tag.infra{background:rgba(14,165,181,.12);color:var(--accent-2)}
.tpl-weekly-report .ship-item b{color:var(--text-1);font-weight:600}
.tpl-weekly-report .ship-item span.owner{margin-left:auto;color:var(--text-3);font-size:12px;font-family:'JetBrains Mono',monospace}
.tpl-weekly-report .chart{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:28px}
.tpl-weekly-report .chart-bars{display:flex;align-items:flex-end;gap:16px;height:220px;margin-top:20px}
.tpl-weekly-report .chart-bars .col{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;position:relative}
.tpl-weekly-report .chart-bars .col .b{width:100%;background:var(--grad);border-radius:6px 6px 0 0;min-height:6px;position:relative}
.tpl-weekly-report .chart-bars .col .b::after{content:attr(data-v);position:absolute;top:-22px;left:0;right:0;text-align:center;font-size:12px;font-weight:700;color:var(--text-1)}
.tpl-weekly-report .chart-bars .col .lbl{font-size:11px;color:var(--text-3);font-family:'JetBrains Mono',monospace}
.tpl-weekly-report .blocker{background:var(--surface);border-left:3px solid var(--bad);padding:16px 20px;border-radius:8px;margin-bottom:12px}
.tpl-weekly-report .blocker h4{font-size:16px;margin-bottom:4px}
.tpl-weekly-report .blocker p{font-size:13px;color:var(--text-2);margin:0}
.tpl-weekly-report .blocker .meta{font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-3);margin-top:6px}
.tpl-weekly-report .next-row{display:grid;grid-template-columns:110px 1fr;gap:16px;padding:14px 0;border-bottom:1px dashed var(--border);align-items:baseline}
.tpl-weekly-report .next-row .owner{font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--accent)}
.tpl-weekly-report .next-row .task{color:var(--text-1);font-weight:500}
.tpl-weekly-report .next-row .task span{color:var(--text-3);font-size:12px;margin-left:8px}
.tpl-weekly-report .lede{color:var(--text-2)}
.tpl-weekly-report .card{background:var(--surface)}
templates/full-decks/presenter-mode-reveal/style.css
/* tpl-presenter-mode-reveal · scoped styles
* Presenter-mode demo deck. Inherits tokens from active theme.
* Minimal overrides — focus is on content + notes structure.
*/
.tpl-presenter-mode-reveal .slide {
padding: 72px 96px;
}
.tpl-presenter-mode-reveal .kicker {
font-family: var(--font-mono, monospace);
font-size: 13px;
color: var(--text-3);
letter-spacing: 0.14em;
text-transform: uppercase;
margin: 0 0 18px 0;
}
.tpl-presenter-mode-reveal .h1 {
font-size: clamp(44px, 5.6vw, 76px);
line-height: 1.12;
letter-spacing: -0.02em;
margin: 0 0 24px 0;
}
.tpl-presenter-mode-reveal .h2 {
font-size: clamp(32px, 3.6vw, 48px);
line-height: 1.22;
letter-spacing: -0.01em;
margin: 0 0 28px 0;
}
.tpl-presenter-mode-reveal .lede {
font-size: 20px;
line-height: 1.55;
color: var(--text-2);
}
.tpl-presenter-mode-reveal .mono {
font-family: var(--font-mono, monospace);
font-size: 0.9em;
padding: 2px 8px;
border-radius: 6px;
background: rgba(255,255,255,0.08);
color: var(--accent, #58a6ff);
}
.tpl-presenter-mode-reveal .accent {
color: var(--accent, #f0883e);
font-weight: 700;
}
.tpl-presenter-mode-reveal .speaker {
display: flex;
align-items: center;
gap: 12px;
margin-top: 32px;
}
.tpl-presenter-mode-reveal .speaker .av {
width: 42px;
height: 42px;
border-radius: 50%;
background: linear-gradient(135deg, var(--accent, #58a6ff), #bc8cff);
}
.tpl-presenter-mode-reveal .speaker b {
display: block;
font-size: 16px;
}
.tpl-presenter-mode-reveal .speaker span {
font-size: 13px;
color: var(--text-3);
}
/* Agenda rows */
.tpl-presenter-mode-reveal .agenda-row {
display: grid;
grid-template-columns: 48px 1fr auto;
gap: 16px;
align-items: center;
padding: 14px 18px;
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 10px;
margin-bottom: 10px;
background: var(--surface, rgba(255,255,255,0.03));
}
.tpl-presenter-mode-reveal .agenda-row .num {
font-family: var(--font-mono, monospace);
font-size: 14px;
color: var(--accent, #58a6ff);
font-weight: 700;
}
.tpl-presenter-mode-reveal .agenda-row .t {
font-size: 17px;
font-weight: 500;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .agenda-row .d {
font-family: var(--font-mono, monospace);
font-size: 12px;
color: var(--text-3);
}
/* Cards */
.tpl-presenter-mode-reveal .card {
background: var(--surface, rgba(255,255,255,0.03));
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 12px;
padding: 22px 24px;
}
.tpl-presenter-mode-reveal .card-accent {
border-top: 3px solid var(--accent, #58a6ff);
}
.tpl-presenter-mode-reveal .card h4 {
margin: 0 0 10px 0;
font-size: 18px;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .card .dim {
color: var(--text-2);
font-size: 14px;
line-height: 1.6;
margin: 0;
}
/* Feature rows (presenter view features) */
.tpl-presenter-mode-reveal .feature-row {
display: flex;
gap: 14px;
padding: 14px 0;
border-bottom: 1px solid var(--border, rgba(255,255,255,0.08));
}
.tpl-presenter-mode-reveal .feature-row:last-child { border-bottom: none; }
.tpl-presenter-mode-reveal .feature-row .num {
font-size: 24px;
font-weight: 700;
line-height: 1;
flex-shrink: 0;
}
.tpl-presenter-mode-reveal .feature-row b {
display: block;
font-size: 17px;
margin-bottom: 4px;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .feature-row .dim {
font-size: 14px;
color: var(--text-2);
line-height: 1.55;
margin: 0;
}
.tpl-presenter-mode-reveal .blue { color: #58a6ff; }
.tpl-presenter-mode-reveal .green { color: #3fb950; }
.tpl-presenter-mode-reveal .orange { color: #f0883e; }
.tpl-presenter-mode-reveal .purple { color: #bc8cff; }
.tpl-presenter-mode-reveal .red { color: #f85149; }
/* Rule rows (3 铁律) */
.tpl-presenter-mode-reveal .rule-row {
display: grid;
grid-template-columns: 56px 1fr;
gap: 20px;
align-items: start;
padding: 18px 22px;
border: 1px solid var(--border, rgba(255,255,255,0.1));
border-radius: 12px;
margin-bottom: 14px;
background: var(--surface, rgba(255,255,255,0.03));
}
.tpl-presenter-mode-reveal .rule-row .num {
font-size: 28px;
font-weight: 800;
font-family: var(--font-mono, monospace);
line-height: 1;
}
.tpl-presenter-mode-reveal .rule-row b {
display: block;
font-size: 18px;
margin-bottom: 6px;
color: var(--text-1);
}
.tpl-presenter-mode-reveal .rule-row .dim {
font-size: 15px;
color: var(--text-2);
line-height: 1.6;
margin: 0;
}
/* Code block */
.tpl-presenter-mode-reveal .code-block {
background: #0d1117;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 12px;
padding: 20px 26px;
font-family: var(--font-mono, "SF Mono", monospace);
font-size: 15px;
line-height: 1.8;
color: #e6edf3;
white-space: pre-wrap;
text-align: left;
}
.tpl-presenter-mode-reveal .code-block .comment { color: #8b949e; }
.tpl-presenter-mode-reveal .code-block .cmd { color: #3fb950; font-weight: 600; }
.tpl-presenter-mode-reveal .code-block .flag { color: #f0883e; }
/* Stack helper */
.tpl-presenter-mode-reveal .stack > * + * { margin-top: 0; }
/* Grid helpers */
.tpl-presenter-mode-reveal .grid { display: grid; gap: 20px; }
.tpl-presenter-mode-reveal .grid.g2 { grid-template-columns: 1fr 1fr; }
.tpl-presenter-mode-reveal .grid.g3 { grid-template-columns: repeat(3, 1fr); }
.tpl-presenter-mode-reveal .mt-m { margin-top: 20px; }
.tpl-presenter-mode-reveal .mt-l { margin-top: 32px; }
.tpl-presenter-mode-reveal .mt-s { margin-top: 10px; }
.tpl-presenter-mode-reveal .tc { text-align: center; }
templates/full-decks/xhs-pastel-card/README.md
# xhs-pastel-card
暖奶油 `#fef8f1` 底 + 模糊彩色 blob + Playfair italic 衬线大字 + 整色马卡龙卡片(桃 / 薄荷 / 天 / 丁香 / 柠檬 / 玫瑰)。共性提取自 `20260412-obsidian-skills/html/xhs-obsidian-skills.html` 的 `soft-purple/pink/blue/green/orange/teal` 软色卡系统,以及 `20260409 v2-白底版` 的胶囊 chip 顶部条。
**Visual traits:** 三颗柔光 blob 作背景、顶部 chip+page 组合、Playfair italic 做 accent 词(em / rose / mint)、整色圆角 28px 大卡片、italic Playfair 序号 01-04、donut SVG 图、小 divider 条 + 渐变、衬线正文做标题 / sans 做正文混排。
**Use when:** 生活方式 / 个人成长 / 轻内容 / 情感向的小红书贴或个人演讲;你想要一种「不那么科技感、偏杂志偏手作」的气质;适合讲「慢」「休息」「温柔」主题。
**Source inspiration:** `20260412-obsidian-skills/html/xhs-obsidian-skills.html` + `20260409` v2-白底版(共性 pastel 系统)。
**Path:** `templates/full-decks/xhs-pastel-card/index.html`
templates/full-decks/xhs-pastel-card/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>XHS Pastel Card</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-xhs-pastel-card">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="xp-blob b1"></div>
<div class="xp-blob b2"></div>
<div class="xp-blob b3"></div>
<div class="xp-topbar"><div class="xp-chip">A soft manifesto</div><div class="xp-page">01 · 08</div></div>
<div class="xp-kicker">Living With AI · 2026</div>
<h1 class="xp-h1">放慢一点,<br>让 <em>AI</em> 帮你<br>过一种 <span class="rose">更温柔</span><br>的生活</h1>
<div class="xp-divider"></div>
<p class="xp-sub">这不是一份效率指南。这是一份「怎么用 AI 少做一些事」的清单 —— 把挤出来的 4 小时还给你自己。</p>
<div class="xp-footer"><span>by lewis · pastel edition</span><span>cover</span></div>
</section>
<!-- 2. SECTION -->
<section class="slide">
<div class="xp-blob b2"></div>
<div class="xp-blob b3"></div>
<div class="xp-topbar"><div class="xp-chip mint">Chapter one</div><div class="xp-page">02 · 08</div></div>
<div style="margin:auto 0">
<div class="xp-kicker">先问自己</div>
<h1 class="xp-h1" style="font-size:120px">什么事<br>是你 <span class="mint">其实不想做</span> 的?</h1>
<p class="xp-sub">不是「不得不做」,是「做的时候灵魂在叹气」。</p>
</div>
<div class="xp-footer"><span>section · chapter 1</span><span>02 · 08</span></div>
</section>
<!-- 3. CONTENT 2x2 pastel cards -->
<section class="slide">
<div class="xp-blob b1"></div>
<div class="xp-topbar"><div class="xp-chip rose">Four little escapes</div><div class="xp-page">03 · 08</div></div>
<h2 class="xp-h2">四件可以<br>完全交给 <em>AI</em> 的小事</h2>
<div class="xp-grid-2">
<div class="xp-card peach"><div class="xp-num">01</div><h4>回复那种「收到」邮件</h4><p>它们不需要你思考。让 AI 按你的语气自动处理,一周省 40 分钟。</p></div>
<div class="xp-card mint"><div class="xp-num">02</div><h4>订餐厅、改签、查路线</h4><p>一句话外包出去。你只负责选最后选项,不负责翻十个 app。</p></div>
<div class="xp-card sky"><div class="xp-num">03</div><h4>把会议录音变成行动项</h4><p>录音 → 摘要 → todo 一键完成。你只需要确认和签字。</p></div>
<div class="xp-card lilac"><div class="xp-num">04</div><h4>整理上周拍的 300 张照片</h4><p>按事件分类、挑 10 张精选、写图说。整理档案这件事终于被自动化了。</p></div>
</div>
<div class="xp-footer"><span>content · 2x2</span><span>03 · 08</span></div>
</section>
<!-- 4. QUOTE -->
<section class="slide">
<div class="xp-blob b3"></div>
<div class="xp-blob b2"></div>
<div class="xp-topbar"><div class="xp-chip lilac">A small pause</div><div class="xp-page">04 · 08</div></div>
<div class="xp-hero-card">
<p class="xp-quote">效率工具的终点,不是<em> 做更多</em>,<br>而是 <em>有资格做更少</em>。</p>
<div class="xp-divider"></div>
<p class="xp-sub">当你把「收到」邮件、订餐、行程、照片整理都交出去,你才会惊讶地发现 —— 原来一周有 4 个小时是空的。</p>
</div>
<div class="xp-footer"><span>quote</span><span>04 · 08</span></div>
</section>
<!-- 5. CODE / PROMPT -->
<section class="slide">
<div class="xp-blob b1"></div>
<div class="xp-topbar"><div class="xp-chip">My auto-reply prompt</div><div class="xp-page">05 · 08</div></div>
<h2 class="xp-h2">把「<em>收到邮件</em>」<br>自动化的 <span class="rose">一段 prompt</span></h2>
<pre class="xp-codebox"><span class="cm"># auto-reply skill</span>
<span class="kw">when</span> email matches <span class="st">"收到 / 好的 / 确认 / 收到谢谢"</span>:
reply:
tone: <span class="st">"温柔,简短,不要太商业"</span>
max_lines: <span class="hl">2</span>
sign_with: <span class="st">"— Lewis"</span>
<span class="kw">always_skip</span>:
- from: [<span class="st">"家人"</span>, <span class="st">"伴侣"</span>, <span class="st">"亲密朋友"</span>]
- contains: [<span class="st">"紧急"</span>, <span class="st">"合同"</span>, <span class="st">"付款"</span>]
<span class="cm"># 一周省 38 分钟,测过</span></pre>
<div class="xp-footer"><span>content · prompt</span><span>05 · 08</span></div>
</section>
<!-- 6. CHART — time donut -->
<section class="slide">
<div class="xp-blob b2"></div>
<div class="xp-topbar"><div class="xp-chip mint">Your week, rebuilt</div><div class="xp-page">06 · 08</div></div>
<h2 class="xp-h2">一周 4 小时 <span class="mint">还给自己</span></h2>
<div style="display:flex;align-items:center;gap:60px;margin-top:30px">
<svg viewBox="0 0 260 260" style="width:300px;flex-shrink:0">
<circle cx="130" cy="130" r="100" fill="none" stroke="#fef0e4" stroke-width="40"/>
<!-- email 12% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#f48b5c" stroke-width="40" stroke-dasharray="75 628" stroke-dashoffset="0" transform="rotate(-90 130 130)"/>
<!-- logistics 18% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#2e9d70" stroke-width="40" stroke-dasharray="113 628" stroke-dashoffset="-75" transform="rotate(-90 130 130)"/>
<!-- meetings 14% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#4e7ed6" stroke-width="40" stroke-dasharray="88 628" stroke-dashoffset="-188" transform="rotate(-90 130 130)"/>
<!-- photos 6% -->
<circle cx="130" cy="130" r="100" fill="none" stroke="#7b5dc4" stroke-width="40" stroke-dasharray="38 628" stroke-dashoffset="-276" transform="rotate(-90 130 130)"/>
<text x="130" y="130" text-anchor="middle" font-family="Playfair Display" font-size="44" font-weight="900" fill="#2a2340">4h</text>
<text x="130" y="156" text-anchor="middle" font-family="Inter" font-size="12" fill="#9089a8">per week saved</text>
</svg>
<div style="flex:1">
<div class="xp-grid-2" style="grid-template-columns:1fr;gap:12px;margin-top:0">
<div class="xp-card peach" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-peach-d)"></div><div><h4 style="margin:0;font-size:17px">48 min · 邮件</h4></div></div>
<div class="xp-card mint" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-mint-d)"></div><div><h4 style="margin:0;font-size:17px">72 min · 订/改/查</h4></div></div>
<div class="xp-card sky" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-sky-d)"></div><div><h4 style="margin:0;font-size:17px">56 min · 会议摘要</h4></div></div>
<div class="xp-card lilac" style="padding:14px 20px;display:flex;align-items:center;gap:14px"><div style="width:14px;height:14px;border-radius:50%;background:var(--xp-lilac-d)"></div><div><h4 style="margin:0;font-size:17px">24 min · 照片整理</h4></div></div>
</div>
</div>
</div>
<div class="xp-footer"><span>chart · donut</span><span>06 · 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="xp-blob b1"></div>
<div class="xp-blob b3"></div>
<div class="xp-topbar"><div class="xp-chip rose">This weekend</div><div class="xp-page">07 · 08</div></div>
<h2 class="xp-h2">这周末,<br>先给自己 <em>放一个小假</em></h2>
<div class="xp-grid-3">
<div class="xp-card lemon"><div class="xp-num">☕</div><h4>Saturday morning</h4><p>挑一个你最烦的小事,写 prompt,让它从此不再烦你。</p></div>
<div class="xp-card peach"><div class="xp-num">🌸</div><h4>Saturday afternoon</h4><p>去散步。什么都不带。AI 在家帮你看着消息。</p></div>
<div class="xp-card sky"><div class="xp-num">🌙</div><h4>Sunday night</h4><p>复盘:哪 4 小时是真的空的?下周继续。</p></div>
</div>
<div class="xp-footer"><span>cta</span><span>07 · 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="xp-blob b2"></div>
<div style="margin:auto 0;text-align:center">
<div class="xp-kicker" style="text-align:center">thanks for reading</div>
<h1 class="xp-h1" style="font-size:160px;text-align:center">谢谢 <em>·</em> thanks</h1>
<div class="xp-divider" style="margin:24px auto"></div>
<p class="xp-sub" style="margin:0 auto">如果你也想过更温柔的一周,评论区跟我说说你打算把哪一件事先交出去 ♡</p>
</div>
<div class="xp-footer"><span>end</span><span>08 · 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/xhs-pastel-card/style.css
/* xhs-pastel-card — 柔和马卡龙大色块封面风 */
.tpl-xhs-pastel-card{
--xp-bg:#fef8f1;
--xp-ink:#2a2340;
--xp-ink2:#5b5470;
--xp-muted:#9089a8;
--xp-peach:#ffd8c2;
--xp-peach-d:#f48b5c;
--xp-mint:#c8ecd8;
--xp-mint-d:#2e9d70;
--xp-sky:#c9dcfb;
--xp-sky-d:#4e7ed6;
--xp-lilac:#ddd0f5;
--xp-lilac-d:#7b5dc4;
--xp-lemon:#fdf0b2;
--xp-lemon-d:#c8910a;
--xp-rose:#fcd0dd;
--xp-rose-d:#c94673;
background:var(--xp-bg);
color:var(--xp-ink);
font-family:'Playfair Display','Noto Serif SC','Inter','Noto Sans SC',Georgia,serif;
}
.tpl-xhs-pastel-card .slide{background:var(--xp-bg);color:var(--xp-ink);padding:76px 90px}
.tpl-xhs-pastel-card .xp-blob{position:absolute;border-radius:50%;filter:blur(2px);opacity:.85;z-index:0}
.tpl-xhs-pastel-card .xp-blob.b1{width:420px;height:420px;background:radial-gradient(circle,var(--xp-peach),transparent 70%);top:-8%;right:-6%}
.tpl-xhs-pastel-card .xp-blob.b2{width:360px;height:360px;background:radial-gradient(circle,var(--xp-lilac),transparent 72%);bottom:-10%;left:-8%}
.tpl-xhs-pastel-card .xp-blob.b3{width:260px;height:260px;background:radial-gradient(circle,var(--xp-mint),transparent 72%);top:40%;right:20%}
.tpl-xhs-pastel-card .slide > *{position:relative;z-index:2}
.tpl-xhs-pastel-card .xp-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:22px;font-family:'Inter','Noto Sans SC',sans-serif}
.tpl-xhs-pastel-card .xp-chip{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;border-radius:999px;background:#fff;border:1.5px solid rgba(42,35,64,.1);font-size:13px;font-weight:600;letter-spacing:.08em;color:var(--xp-ink2);text-transform:uppercase}
.tpl-xhs-pastel-card .xp-chip::before{content:'';width:9px;height:9px;border-radius:50%;background:var(--xp-peach-d)}
.tpl-xhs-pastel-card .xp-chip.mint::before{background:var(--xp-mint-d)}
.tpl-xhs-pastel-card .xp-chip.sky::before{background:var(--xp-sky-d)}
.tpl-xhs-pastel-card .xp-chip.lilac::before{background:var(--xp-lilac-d)}
.tpl-xhs-pastel-card .xp-chip.rose::before{background:var(--xp-rose-d)}
.tpl-xhs-pastel-card .xp-page{font-family:'Inter',sans-serif;font-size:13px;color:var(--xp-muted);letter-spacing:.12em;font-weight:600}
.tpl-xhs-pastel-card .xp-kicker{font-family:'Inter',sans-serif;font-size:14px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--xp-peach-d);margin-bottom:14px}
.tpl-xhs-pastel-card .xp-h1{font-size:96px;font-weight:900;line-height:1.05;letter-spacing:-2px;margin:0 0 18px;color:var(--xp-ink);font-family:'Playfair Display','Noto Serif SC',serif}
.tpl-xhs-pastel-card .xp-h1 em{font-style:italic;color:var(--xp-peach-d);font-family:'Playfair Display',serif}
.tpl-xhs-pastel-card .xp-h1 .rose{color:var(--xp-rose-d);font-style:italic}
.tpl-xhs-pastel-card .xp-h1 .mint{color:var(--xp-mint-d);font-style:italic}
.tpl-xhs-pastel-card .xp-h2{font-size:60px;font-weight:800;line-height:1.1;letter-spacing:-1px;margin:0 0 14px;font-family:'Playfair Display','Noto Serif SC',serif}
.tpl-xhs-pastel-card .xp-sub{font-family:'Inter','Noto Sans SC',sans-serif;font-size:21px;line-height:1.6;color:var(--xp-ink2);max-width:800px;font-weight:400}
.tpl-xhs-pastel-card .xp-card{border-radius:28px;padding:30px 34px;background:#fff;box-shadow:0 14px 40px rgba(42,35,64,.08);position:relative;overflow:hidden}
.tpl-xhs-pastel-card .xp-card.peach{background:var(--xp-peach)}
.tpl-xhs-pastel-card .xp-card.mint{background:var(--xp-mint)}
.tpl-xhs-pastel-card .xp-card.sky{background:var(--xp-sky)}
.tpl-xhs-pastel-card .xp-card.lilac{background:var(--xp-lilac)}
.tpl-xhs-pastel-card .xp-card.lemon{background:var(--xp-lemon)}
.tpl-xhs-pastel-card .xp-card.rose{background:var(--xp-rose)}
.tpl-xhs-pastel-card .xp-card .xp-num{font-family:'Playfair Display',serif;font-size:68px;font-weight:900;font-style:italic;line-height:1;opacity:.85}
.tpl-xhs-pastel-card .xp-card h4{font-size:22px;font-weight:800;margin:8px 0;font-family:'Inter','Noto Sans SC',sans-serif}
.tpl-xhs-pastel-card .xp-card p{font-family:'Inter','Noto Sans SC',sans-serif;font-size:15px;line-height:1.55;color:var(--xp-ink2)}
.tpl-xhs-pastel-card .xp-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:26px}
.tpl-xhs-pastel-card .xp-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:18px;margin-top:26px}
.tpl-xhs-pastel-card .xp-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:24px}
.tpl-xhs-pastel-card .xp-hero-card{background:#fff;border-radius:36px;padding:40px 46px;margin-top:28px;box-shadow:0 20px 50px rgba(42,35,64,.1)}
.tpl-xhs-pastel-card .xp-quote{font-family:'Playfair Display','Noto Serif SC',serif;font-size:40px;font-weight:800;font-style:italic;line-height:1.3;color:var(--xp-ink)}
.tpl-xhs-pastel-card .xp-quote::before{content:'“';font-size:100px;line-height:.8;display:block;color:var(--xp-peach-d);opacity:.7}
.tpl-xhs-pastel-card .xp-footer{position:absolute;left:90px;right:90px;bottom:40px;display:flex;justify-content:space-between;font-family:'Inter',sans-serif;font-size:12px;color:var(--xp-muted);letter-spacing:.1em}
.tpl-xhs-pastel-card .xp-divider{width:90px;height:4px;background:linear-gradient(90deg,var(--xp-peach-d),var(--xp-rose-d));border-radius:2px;margin:20px 0}
.tpl-xhs-pastel-card .xp-codebox{background:#2a2340;color:#fef8f1;border-radius:24px;padding:26px 30px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;margin-top:22px}
.tpl-xhs-pastel-card .xp-codebox .cm{color:#9089a8}
.tpl-xhs-pastel-card .xp-codebox .kw{color:#ffc6a0}
.tpl-xhs-pastel-card .xp-codebox .st{color:#c8ecd8}
.tpl-xhs-pastel-card .xp-codebox .hl{color:#fcd0dd;font-weight:700}
templates/full-decks/xhs-post/README.md
# xhs-post · 小红书 9 图
小红书 3:4 图文格式,9 张图(810 × 1080)。结构:封面 → hook → 痛点 → aha moment → 步骤 1-3 → 效果 → CTA 关注。
手写便签 + 贴纸 + 圆角硬阴影的 MUJI/风格,暖米色背景 + 粉橘黄柔和渐变。每页右上角有 `N / 9` 页码贴纸,最后一页有话题 tag。
**适用场景:** 小红书 / 微博九宫格 / 公众号图文首图 / 抖音图文卡片。
**使用方式:** 每张 `.slide` 直接截图导出即可,保持 810×1080 比例。按 → 依次浏览。
**Feel:** 手帐、贴纸、闺蜜跟你分享干货的 vibe。
templates/full-decks/testing-safety-alert/README.md
# testing-safety-alert
白底 + 红琥珀警示色 + 条纹危险边 + 大红 strike 和 pill。灵感来自 `20260412-AI测试与安全/xhs-ai-testing-safety-v2.html` 的 `.focus` 黑底白字块、hero quote box 和高对比 black-on-white 气质 —— 但把语气推到「警示 / 风控 / 事故报告」层级。
**Visual traits:** 顶部 45° 红黑斜条纹警示带、底部副条纹、`strike-through` 红色斜切的否定大字、L1/L2/L3 三档色卡 (绿/琥珀/红)、圆形前置指示灯 alert-box、policy-yaml 深色代码块带红色左边框 + `bad` 关键词高亮、红/绿复选框 checklist、Q1 事故柱状图。
**Use when:** 讲安全 / 风控 / 事故复盘 / 红队测试 / AI 上线前评估 / policy as code;你需要让观众立刻感到「这事严肃,别马虎」。
**Source inspiration:** `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`.
**Path:** `templates/full-decks/testing-safety-alert/index.html`
templates/full-decks/tech-sharing/style.css
/* tech-sharing — 技术分享 dark, code-forward */
.tpl-tech-sharing{
--bg:#0d1117;--bg-soft:#161b22;--surface:#161b22;--surface-2:#1c2230;
--border:rgba(139,148,158,.22);--border-strong:rgba(139,148,158,.4);
--text-1:#e6edf3;--text-2:#8b949e;--text-3:#6e7681;
--accent:#7ee787;--accent-2:#79c0ff;--accent-3:#ff7b72;
--grad:linear-gradient(120deg,#7ee787 0%,#79c0ff 60%,#d2a8ff 100%);
--radius:14px;--radius-lg:20px;
--shadow:0 20px 60px rgba(0,0,0,.5);
font-family:'Inter','Noto Sans SC',sans-serif;
}
.tpl-tech-sharing{background:#0d1117;color:var(--text-1)}
.tpl-tech-sharing .slide{padding:72px 96px;background:#0d1117;color:var(--text-1)}
.tpl-tech-sharing .slide::before{content:"";position:absolute;inset:0;background:
radial-gradient(60% 50% at 90% 10%,rgba(121,192,255,.12),transparent 60%),
radial-gradient(50% 50% at 10% 90%,rgba(126,231,135,.08),transparent 60%);
pointer-events:none;z-index:0}
.tpl-tech-sharing .slide>*{position:relative;z-index:1}
.tpl-tech-sharing .h1{font-size:78px;line-height:1.03;font-weight:800;letter-spacing:-.03em;color:#fff}
.tpl-tech-sharing .h2{font-size:54px;font-weight:700;letter-spacing:-.025em;color:#fff}
.tpl-tech-sharing h3,.tpl-tech-sharing h4{color:#fff}
.tpl-tech-sharing .kicker{color:var(--accent);font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:600;text-transform:none;letter-spacing:.02em}
.tpl-tech-sharing .kicker::before{content:"> "}
.tpl-tech-sharing .mono{font-family:'JetBrains Mono','IBM Plex Mono',monospace}
.tpl-tech-sharing .terminal{background:#010409;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.6);font-family:'JetBrains Mono',monospace;font-size:15px;line-height:1.65}
.tpl-tech-sharing .terminal .bar{display:flex;align-items:center;gap:8px;padding:12px 16px;background:#161b22;border-bottom:1px solid var(--border);font-size:12px;color:var(--text-3)}
.tpl-tech-sharing .terminal .dot{width:12px;height:12px;border-radius:50%;background:#ff5f56}
.tpl-tech-sharing .terminal .dot:nth-child(2){background:#ffbd2e}
.tpl-tech-sharing .terminal .dot:nth-child(3){background:#27c93f}
.tpl-tech-sharing .terminal pre{margin:0;padding:24px 28px;color:#e6edf3;overflow:auto;max-height:440px}
.tpl-tech-sharing .kw{color:#ff7b72}
.tpl-tech-sharing .fn{color:#d2a8ff}
.tpl-tech-sharing .str{color:#a5d6ff}
.tpl-tech-sharing .cmt{color:#8b949e;font-style:italic}
.tpl-tech-sharing .num{color:#79c0ff}
.tpl-tech-sharing .card{background:var(--surface);border:1px solid var(--border);box-shadow:none}
.tpl-tech-sharing .card-accent{border-top:3px solid var(--accent)}
.tpl-tech-sharing .pill{background:var(--surface-2);color:var(--text-2);border-color:var(--border)}
.tpl-tech-sharing .pill-accent{background:rgba(126,231,135,.12);color:var(--accent);border-color:rgba(126,231,135,.35)}
.tpl-tech-sharing .tag{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:6px;font-family:'JetBrains Mono',monospace;font-size:12px;background:var(--surface-2);border:1px solid var(--border);color:var(--text-2)}
.tpl-tech-sharing .agenda-row{display:flex;align-items:baseline;gap:24px;padding:18px 0;border-bottom:1px dashed var(--border);font-family:'JetBrains Mono',monospace}
.tpl-tech-sharing .agenda-row .num{color:var(--accent);flex:none;width:48px}
.tpl-tech-sharing .agenda-row .t{color:#fff;font-size:24px;flex:1;font-family:'Inter',sans-serif;font-weight:600}
.tpl-tech-sharing .agenda-row .d{color:var(--text-3);font-size:13px}
.tpl-tech-sharing .speaker{display:flex;align-items:center;gap:14px;margin-top:28px}
.tpl-tech-sharing .speaker .av{width:56px;height:56px;border-radius:50%;background:var(--grad)}
.tpl-tech-sharing .speaker b{display:block;color:#fff;font-size:18px}
.tpl-tech-sharing .speaker span{color:var(--text-3);font-size:13px;font-family:'JetBrains Mono',monospace}
.tpl-tech-sharing .lede{color:var(--text-2)}
templates/full-decks/xhs-post/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>每天只睡 6h 还精神?· 小红书图文</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-xhs-post">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="page-dot">1 / 9</div>
<div class="sticker pink" style="top:120px;left:48px;transform:rotate(-6deg)">💤 救命</div>
<div class="sticker yellow" style="top:140px;right:64px;transform:rotate(5deg)">亲测 7 天</div>
<div style="margin-top:200px">
<p class="lede" style="font-size:24px;color:var(--text-1);font-weight:600">打工人深夜自救手册</p>
<h1 class="h1 mt-s">每天只睡 <span class="cover-title">6h</span><br>还能<span class="cover-title">精神一整天</span><br>的 3 个小习惯</h1>
</div>
<div class="bottom-bar"><div><span class="avatar">小</span> <b style="color:var(--text-1);margin-left:8px">@小熊不困了</b></div><div>← 左滑 查看</div></div>
</section>
<!-- 2. Hook -->
<section class="slide" data-title="Hook">
<div class="page-dot">2 / 9</div>
<div class="big-emoji" style="margin-top:80px">👀</div>
<h2 class="h2 tc mt-l">等等先别划走!</h2>
<p class="lede tc mt-m" style="padding:0 20px">我也曾是那个<br>早上起来像被卡车撞过的人。<br><br>直到我发现了<br><b style="color:var(--accent)">1 件事</b>比睡够 8 小时还重要。</p>
<div class="sticker blue" style="bottom:160px;left:50%;transform:translateX(-50%) rotate(-2deg)">真 · 转折点 ↓</div>
</section>
<!-- 3. Pain -->
<section class="slide" data-title="Pain">
<div class="page-dot">3 / 9</div>
<p class="lede" style="font-weight:700;color:var(--accent)">❌ 你是不是也这样</p>
<h2 class="h2 mt-s">越睡越累</h2>
<div class="stack mt-l">
<div class="hand-box"><b style="font-size:22px">😵💫 周末补觉到中午</b><p class="dim" style="font-size:16px;margin-top:4px">起来头更晕,一整天废掉</p></div>
<div class="hand-box"><b style="font-size:22px">☕️ 咖啡续三杯</b><p class="dim" style="font-size:16px;margin-top:4px">下午 3 点照样困到扶墙</p></div>
<div class="hand-box"><b style="font-size:22px">📱 睡前刷到凌晨</b><p class="dim" style="font-size:16px;margin-top:4px">明明很困就是不舍得睡</p></div>
</div>
</section>
<!-- 4. Aha -->
<section class="slide" data-title="Aha">
<div class="page-dot">4 / 9</div>
<div class="sticker green" style="top:100px;right:48px;transform:rotate(4deg)">✨ aha moment</div>
<p class="lede mt-l" style="color:var(--accent);font-weight:700">💡 真相是</p>
<h2 class="h2 mt-s">不是睡得少,<br>是<span style="background:var(--accent-3);padding:0 8px">醒得不对</span>。</h2>
<p class="lede mt-l">身体有 90 分钟一个周期。<br>在"深睡"里被闹钟拽起来,<br>就算睡 9 小时也跟没睡一样。</p>
<p class="lede mt-m" style="color:var(--text-1);font-weight:700">关键是:<span style="color:var(--accent)">卡着周期醒</span>。</p>
</section>
<!-- 5. Step 1 -->
<section class="slide" data-title="Step 1">
<div class="page-dot">5 / 9</div>
<div class="num-circle">1</div>
<h2 class="h2 mt-m">倒推睡眠时间</h2>
<div class="hand-box mt-l">
<p style="font-size:22px;margin:0;color:var(--text-1);font-weight:700">👉 公式</p>
<p style="font-size:20px;margin:10px 0 0;color:var(--text-2);line-height:1.7">起床时间 − <b style="color:var(--accent)">90min × N</b> − 15min 入睡<br>= 你今晚该上床的点</p>
</div>
<div class="hand-box mt-m" style="background:#fff5ef">
<p style="font-size:18px;margin:0;color:var(--text-2)">举例:要 7 点起</p>
<p style="font-size:24px;margin:8px 0 0;color:var(--text-1);font-weight:800">→ 23:15 上床 (4 个周期)<br>→ 00:45 上床 (3 个周期)</p>
</div>
</section>
<!-- 6. Step 2 -->
<section class="slide" data-title="Step 2">
<div class="page-dot">6 / 9</div>
<div class="num-circle" style="background:var(--accent-2)">2</div>
<h2 class="h2 mt-m">早晨 10 分钟光</h2>
<div class="hand-box mt-l">
<p style="font-size:22px;margin:0;color:var(--text-1);font-weight:700">☀️ 打开窗帘 / 下楼遛弯</p>
<p style="font-size:18px;margin:8px 0 0;color:var(--text-2);line-height:1.6">自然光一照,褪黑素立刻被掐停,人就真的醒了。阴天也有效,别偷懒。</p>
</div>
<div class="sticker yellow" style="bottom:200px;right:60px;transform:rotate(8deg)">⏰ 比咖啡还猛</div>
<div class="hand-box mt-m" style="background:#fff5ef">
<p style="font-size:18px;margin:0;color:var(--text-2)">懒人方案:</p>
<p style="font-size:22px;margin:6px 0 0;color:var(--text-1);font-weight:700">刷牙的时候站在窗边 🪥</p>
</div>
</section>
<!-- 7. Step 3 -->
<section class="slide" data-title="Step 3">
<div class="page-dot">7 / 9</div>
<div class="num-circle" style="background:var(--accent-3);color:var(--text-1)">3</div>
<h2 class="h2 mt-m">下午 3 点<br>20 分钟小睡</h2>
<div class="hand-box mt-l">
<p style="font-size:20px;margin:0;color:var(--text-2);line-height:1.6"><b style="color:var(--text-1)">⏱️ 最多 20 分钟。</b>超过 30 就会进入深睡,醒来会更累。</p>
</div>
<div class="hand-box mt-m" style="background:#fff5ef">
<p style="font-size:20px;margin:0;color:var(--text-2);line-height:1.6"><b style="color:var(--text-1)">💡 小 tip:</b>睡前喝一口咖啡。20 分钟后咖啡因正好起效,和小睡的清醒 buff 叠加。</p>
</div>
<div class="sticker pink" style="bottom:140px;left:50%;transform:translateX(-50%) rotate(-3deg)">打工人作弊技</div>
</section>
<!-- 8. Result -->
<section class="slide" data-title="Result">
<div class="page-dot">8 / 9</div>
<p class="lede" style="color:var(--good);font-weight:700">✅ 我坚持 7 天后</p>
<h2 class="h2 mt-s">结果是……</h2>
<div class="stack mt-l">
<div class="hand-box"><b style="font-size:22px">😌 早上闹钟响之前就自然醒</b></div>
<div class="hand-box"><b style="font-size:22px">💪 下午不再崩溃</b></div>
<div class="hand-box"><b style="font-size:22px">☕️ 咖啡从 3 杯 → 1 杯</b></div>
<div class="hand-box" style="background:var(--accent-3);border-color:var(--text-1)"><b style="font-size:24px">✨ 最重要:脾气变好了</b></div>
</div>
</section>
<!-- 9. CTA -->
<section class="slide" data-title="CTA">
<div class="page-dot">9 / 9</div>
<div class="big-emoji" style="margin-top:60px">💌</div>
<h2 class="h2 tc mt-l">觉得有用的话</h2>
<h1 class="h1 tc mt-s" style="color:var(--accent)">收藏 + 关注 🧡</h1>
<p class="lede tc mt-l" style="padding:0 30px">下期讲<br><b style="color:var(--text-1)">「打工人脊椎急救 5 式」</b><br>办公室也能做</p>
<div class="tag-row" style="justify-content:center;margin-top:36px">
<span class="ht">#睡眠</span>
<span class="ht">#打工人日常</span>
<span class="ht">#自律</span>
<span class="ht">#健康生活</span>
</div>
<div class="bottom-bar"><div><span class="avatar">小</span> <b style="color:var(--text-1);margin-left:8px">@小熊不困了</b></div><div>❤️ 5.2w</div></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>
templates/full-decks/tech-sharing/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Rust 异步运行时内部机制 · Tech Sharing</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-tech-sharing">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<p class="kicker">tech-sharing / 2026-04-15</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">Rust 异步运行时<br>到底在<span style="background:var(--grad);-webkit-background-clip:text;background-clip:text;color:transparent">调度什么</span>?</h1>
<p class="lede mt-m">从 <span class="mono">Future::poll</span> 到 tokio 的 work-stealing,一次讲清楚。</p>
<div class="speaker"><div class="av"></div><div><b>@lewis</b><span>platform infra · 45 min + Q&A</span></div></div>
<div class="deck-footer"><span class="mono">#async #rust #tokio</span><span class="slide-number" data-current="1" data-total="8"></span></div>
</section>
<!-- 2. Agenda -->
<section class="slide" data-title="Agenda">
<p class="kicker">agenda.toml</p>
<h2 class="h2">今天的路线图</h2>
<div class="stack mt-l">
<div class="agenda-row"><span class="num">01</span><span class="t">Context: 为什么需要 async</span><span class="d">~5min</span></div>
<div class="agenda-row"><span class="num">02</span><span class="t">Deep dive 1: Future & Waker</span><span class="d">~12min</span></div>
<div class="agenda-row"><span class="num">03</span><span class="t">Deep dive 2: Tokio scheduler</span><span class="d">~15min</span></div>
<div class="agenda-row"><span class="num">04</span><span class="t">Code: 手写一个 mini-runtime</span><span class="d">~8min</span></div>
<div class="agenda-row"><span class="num">05</span><span class="t">Takeaways + Q&A</span><span class="d">~5min</span></div>
</div>
</section>
<!-- 3. Context -->
<section class="slide" data-title="Context">
<p class="kicker">// context</p>
<h2 class="h2">问题:一个线程一个连接,<br>撑不住 10 万并发。</h2>
<div class="grid g3 mt-l">
<div class="card card-accent"><h4>Thread-per-conn</h4><p class="dim">每条连接一根 OS 线程,栈 2–8MB。10 万连接 = 几百 GB RAM。</p><span class="tag mt-s">❌ 不现实</span></div>
<div class="card card-accent"><h4>Event loop (C)</h4><p class="dim">epoll/kqueue + 回调地狱。快,但写起来痛苦且容易出 bug。</p><span class="tag mt-s">😩 callback hell</span></div>
<div class="card card-accent"><h4>Async / await</h4><p class="dim">看起来像同步代码,编译成状态机。一根线程跑几千任务。</p><span class="tag mt-s">✅ Rust 选这个</span></div>
</div>
</section>
<!-- 4. Deep dive 1 -->
<section class="slide" data-title="Deep Dive 1">
<p class="kicker">deep-dive · 1 / 2</p>
<h2 class="h2">Future 其实只有一个方法。</h2>
<div class="grid g2 mt-l" style="align-items:start">
<div>
<p class="lede">编译器把 <span class="mono">async fn</span> 变成一个实现了 <span class="mono">Future</span> trait 的匿名状态机。运行时只做一件事:反复 <span class="mono">poll</span> 它,直到返回 <span class="mono">Ready</span>。</p>
<div class="mt-l">
<span class="tag">Pending</span> <span class="tag">Ready(T)</span> <span class="tag">Waker.wake()</span>
</div>
</div>
<div class="terminal">
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>future.rs</span></div>
<pre><span class="kw">pub trait</span> <span class="fn">Future</span> {
<span class="kw">type</span> Output;
<span class="kw">fn</span> <span class="fn">poll</span>(
<span class="kw">self</span>: Pin<&<span class="kw">mut Self</span>>,
cx: &<span class="kw">mut</span> Context<<span class="str">'_</span>>,
) -> Poll<<span class="kw">Self</span>::Output>;
}
<span class="cmt">// Poll::Pending → 挂起,等 waker 唤醒</span>
<span class="cmt">// Poll::Ready(v) → 完成,产出 v</span></pre>
</div>
</div>
</section>
<!-- 5. Deep dive 2 -->
<section class="slide" data-title="Deep Dive 2">
<p class="kicker">deep-dive · 2 / 2</p>
<h2 class="h2">Tokio 是一个偷任务的小工。</h2>
<div class="grid g2 mt-l" style="align-items:start">
<div>
<p class="lede">Multi-thread runtime = N 个 worker,每个 worker 有自己的本地队列。空闲的 worker 会去别人队列里"偷"任务。</p>
<div class="stack mt-m">
<div class="tag">✦ local queue · 256 slots</div>
<div class="tag">✦ global injection queue</div>
<div class="tag">✦ work-stealing @ 50% steal ratio</div>
<div class="tag">✦ LIFO slot for cache locality</div>
</div>
</div>
<div class="card" style="padding:32px">
<h4 class="mono" style="color:var(--accent-2)">scheduler tick loop</h4>
<div class="stack mt-m" style="font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.9;color:var(--text-2)">
<div><span style="color:var(--accent)">1.</span> pop from LIFO slot</div>
<div><span style="color:var(--accent)">2.</span> else pop from local queue</div>
<div><span style="color:var(--accent)">3.</span> else drain global queue (every 61 ticks)</div>
<div><span style="color:var(--accent)">4.</span> else steal from random victim</div>
<div><span style="color:var(--accent)">5.</span> else park the thread</div>
</div>
</div>
</div>
</section>
<!-- 6. Code example -->
<section class="slide" data-title="Code">
<p class="kicker">mini-runtime.rs · ~40 LOC</p>
<h2 class="h2">手写一个最小 runtime。</h2>
<div class="terminal mt-m">
<div class="bar"><span class="dot"></span><span class="dot"></span><span class="dot"></span><span>src/main.rs</span></div>
<pre><span class="kw">use</span> std::collections::VecDeque;
<span class="kw">use</span> std::sync::{Arc, Mutex};
<span class="kw">use</span> std::task::{Context, Poll, Wake, Waker};
<span class="kw">struct</span> Task(Mutex<Pin<Box<<span class="kw">dyn</span> Future<Output = ()> + Send>>>);
<span class="kw">impl</span> Wake <span class="kw">for</span> Task {
<span class="kw">fn</span> <span class="fn">wake</span>(<span class="kw">self</span>: Arc<<span class="kw">Self</span>>) { QUEUE.lock().unwrap().push_back(<span class="kw">self</span>); }
}
<span class="kw">fn</span> <span class="fn">block_on</span><F: Future<Output = ()> + Send + <span class="str">'static</span>>(fut: F) {
<span class="fn">spawn</span>(fut);
<span class="kw">while let Some</span>(task) = QUEUE.lock().unwrap().pop_front() {
<span class="kw">let</span> waker = Waker::from(task.clone());
<span class="kw">let mut</span> cx = Context::from_waker(&waker);
<span class="kw">let mut</span> fut = task.<span class="num">0</span>.lock().unwrap();
<span class="kw">let</span> _ = fut.as_mut().<span class="fn">poll</span>(&<span class="kw">mut</span> cx); <span class="cmt">// 就是这一行</span>
}
}</pre>
</div>
</section>
<!-- 7. Takeaways -->
<section class="slide" data-title="Takeaways">
<p class="kicker">// takeaways</p>
<h2 class="h2">三件事带回去。</h2>
<div class="grid g3 mt-l">
<div class="card card-accent"><h4>1 · async 是零成本抽象</h4><p class="dim">编译成状态机,没有运行时虚表,没有 GC。</p></div>
<div class="card card-accent"><h4>2 · Waker 是脉搏</h4><p class="dim">Future 不主动做事,运行时靠 waker 决定"什么时候再 poll"。</p></div>
<div class="card card-accent"><h4>3 · 别在 async 里阻塞</h4><p class="dim">一行 <span class="mono">std::fs::read</span> 能让整个 worker 停摆。用 <span class="mono">spawn_blocking</span>。</p></div>
</div>
<p class="lede mt-l">延伸阅读:<span class="mono">tokio.rs/blog/2019-10-scheduler</span> · <span class="mono">rust-lang.github.io/async-book</span></p>
</section>
<!-- 8. Q&A -->
<section class="slide center tc" data-title="Q and A">
<div>
<div class="mono" style="font-size:120px;color:var(--accent);font-weight:800;letter-spacing:-.04em">?</div>
<h2 class="h2">Questions?</h2>
<p class="lede" style="margin:14px auto">github.com/lewis · @lewis on slack</p>
<div class="row mt-l" style="justify-content:center">
<span class="tag">slides: git.co/rt-deck</span>
<span class="tag">code: git.co/mini-rt</span>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>
templates/full-decks/testing-safety-alert/style.css
/* testing-safety-alert — 红/琥珀 警示风 · 白底高对比 */
.tpl-testing-safety-alert{
--ts-bg:#fffaf7;
--ts-ink:#14141a;
--ts-ink2:#4a4955;
--ts-muted:#8a8892;
--ts-line:rgba(20,20,26,.08);
--ts-red:#e0314a;
--ts-red-soft:#ffecee;
--ts-amber:#d97706;
--ts-amber-soft:#fff5e6;
--ts-green:#067647;
--ts-green-soft:#e8f8ee;
background:var(--ts-bg);
color:var(--ts-ink);
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
}
.tpl-testing-safety-alert .slide{background:var(--ts-bg);color:var(--ts-ink);padding:64px 84px}
.tpl-testing-safety-alert .ts-stripe{position:absolute;top:0;left:0;right:0;height:14px;background:repeating-linear-gradient(45deg,var(--ts-red) 0 18px,#111318 18px 36px)}
.tpl-testing-safety-alert .ts-stripe-b{position:absolute;bottom:0;left:0;right:0;height:6px;background:repeating-linear-gradient(45deg,var(--ts-red) 0 10px,#111318 10px 20px);opacity:.6}
.tpl-testing-safety-alert .ts-chrome{display:flex;justify-content:space-between;align-items:center;margin:22px 0 16px}
.tpl-testing-safety-alert .ts-alert-tag{display:inline-flex;align-items:center;gap:10px;padding:8px 18px;border-radius:10px;font-size:13px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;background:var(--ts-red);color:#fff;box-shadow:0 6px 18px rgba(224,49,74,.28)}
.tpl-testing-safety-alert .ts-alert-tag::before{content:'⚠';font-size:16px}
.tpl-testing-safety-alert .ts-alert-tag.amber{background:var(--ts-amber);box-shadow:0 6px 18px rgba(217,119,6,.25)}
.tpl-testing-safety-alert .ts-alert-tag.green{background:var(--ts-green);box-shadow:0 6px 18px rgba(6,118,71,.22)}
.tpl-testing-safety-alert .ts-alert-tag.green::before{content:'✓'}
.tpl-testing-safety-alert .ts-page{font-size:13px;color:var(--ts-muted);letter-spacing:.15em;font-weight:700}
.tpl-testing-safety-alert .ts-kicker{font-size:15px;font-weight:700;color:var(--ts-red);letter-spacing:.06em;margin-bottom:10px;text-transform:uppercase}
.tpl-testing-safety-alert .ts-h1{font-size:88px;font-weight:900;line-height:1.04;letter-spacing:-2px;margin:10px 0 16px;color:var(--ts-ink)}
.tpl-testing-safety-alert .ts-h1 .red{color:var(--ts-red)}
.tpl-testing-safety-alert .ts-h1 .strike{position:relative;display:inline-block}
.tpl-testing-safety-alert .ts-h1 .strike::after{content:'';position:absolute;left:-4%;right:-4%;top:50%;height:10px;background:var(--ts-red);transform:skewX(-12deg);opacity:.85}
.tpl-testing-safety-alert .ts-h2{font-size:54px;font-weight:900;line-height:1.1;letter-spacing:-1px;margin:0 0 14px}
.tpl-testing-safety-alert .ts-sub{font-size:22px;line-height:1.5;color:var(--ts-ink2);max-width:880px;margin-top:10px}
.tpl-testing-safety-alert .ts-highlight-red{display:inline-block;padding:4px 14px;background:var(--ts-red);color:#fff;border-radius:8px;font-weight:800}
.tpl-testing-safety-alert .ts-highlight-amber{display:inline-block;padding:4px 14px;background:var(--ts-amber-soft);color:var(--ts-amber);border-radius:8px;font-weight:800;border:1px solid rgba(217,119,6,.2)}
.tpl-testing-safety-alert .ts-highlight-green{display:inline-block;padding:4px 14px;background:var(--ts-green-soft);color:var(--ts-green);border-radius:8px;font-weight:800;border:1px solid rgba(6,118,71,.2)}
.tpl-testing-safety-alert .ts-alert-box{border:2px solid var(--ts-red);border-radius:18px;padding:26px 30px;background:linear-gradient(180deg,#fff 0%,var(--ts-red-soft) 100%);box-shadow:0 14px 36px rgba(224,49,74,.14);margin-top:24px;position:relative}
.tpl-testing-safety-alert .ts-alert-box::before{content:'';position:absolute;top:-11px;left:24px;width:22px;height:22px;background:var(--ts-red);border-radius:50%;box-shadow:0 0 0 6px rgba(224,49,74,.2)}
.tpl-testing-safety-alert .ts-alert-box.amber{border-color:var(--ts-amber);background:linear-gradient(180deg,#fff 0%,var(--ts-amber-soft) 100%);box-shadow:0 14px 36px rgba(217,119,6,.14)}
.tpl-testing-safety-alert .ts-alert-box.amber::before{background:var(--ts-amber);box-shadow:0 0 0 6px rgba(217,119,6,.2)}
.tpl-testing-safety-alert .ts-alert-box.green{border-color:var(--ts-green);background:linear-gradient(180deg,#fff 0%,var(--ts-green-soft) 100%);box-shadow:0 14px 36px rgba(6,118,71,.14)}
.tpl-testing-safety-alert .ts-alert-box.green::before{background:var(--ts-green);box-shadow:0 0 0 6px rgba(6,118,71,.2)}
.tpl-testing-safety-alert .ts-alert-box h3{font-size:34px;font-weight:900;margin:0 0 10px}
.tpl-testing-safety-alert .ts-alert-box p{font-size:17px;line-height:1.6;color:var(--ts-ink2);margin:0}
.tpl-testing-safety-alert .ts-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:20px}
.tpl-testing-safety-alert .ts-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:20px}
.tpl-testing-safety-alert .ts-card{border:1px solid var(--ts-line);border-radius:16px;padding:22px 24px;background:#fff;box-shadow:0 6px 20px rgba(17,19,24,.04)}
.tpl-testing-safety-alert .ts-card .lbl{font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase;color:var(--ts-muted);margin-bottom:8px}
.tpl-testing-safety-alert .ts-card h4{font-size:26px;font-weight:900;line-height:1.2;margin-bottom:8px}
.tpl-testing-safety-alert .ts-card p{font-size:14px;color:var(--ts-ink2);line-height:1.55}
.tpl-testing-safety-alert .ts-checklist{display:flex;flex-direction:column;gap:12px;margin-top:20px;max-width:880px}
.tpl-testing-safety-alert .ts-check{display:flex;gap:16px;align-items:flex-start;padding:16px 20px;border:1px solid var(--ts-line);border-radius:14px;background:#fff}
.tpl-testing-safety-alert .ts-check .box{flex:0 0 32px;height:32px;border-radius:8px;border:2px solid var(--ts-red);display:grid;place-items:center;font-weight:900;color:var(--ts-red);background:var(--ts-red-soft)}
.tpl-testing-safety-alert .ts-check.ok .box{border-color:var(--ts-green);color:var(--ts-green);background:var(--ts-green-soft)}
.tpl-testing-safety-alert .ts-check .txt{font-size:18px;line-height:1.5;font-weight:600}
.tpl-testing-safety-alert .ts-codebox{background:#141418;color:#fff5ea;border-radius:14px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:14px;line-height:1.85;margin-top:20px;border-left:6px solid var(--ts-red)}
.tpl-testing-safety-alert .ts-codebox .cm{color:#7a756d}
.tpl-testing-safety-alert .ts-codebox .kw{color:#ffb38a}
.tpl-testing-safety-alert .ts-codebox .st{color:#b3e6c2}
.tpl-testing-safety-alert .ts-codebox .bad{color:#ff9aa8;font-weight:700}
.tpl-testing-safety-alert .ts-footer{position:absolute;left:84px;right:84px;bottom:36px;display:flex;justify-content:space-between;font-size:12px;color:var(--ts-muted);letter-spacing:.1em}
templates/full-decks/weekly-report/README.md
# weekly-report · 周报
7-slide team weekly report: cover (week range), KPI grid, shipped items, a metric trend chart, blockers, next-week plan, thanks.
Corporate-clarity palette: near-white background, blue→teal accent, ruled dividers and tiny mono tags (`FEAT`, `FIX`, `EXP`, `INFRA`). Data-dense, readable at a glance, and easy to skim in a standup.
**Use when:** team weekly readouts, squad reviews, skip-level updates, cross-team "what shipped this week" mails.
**Feel:** Linear changelog meets a McKinsey KPI deck — serious, measured, actionable.
templates/full-decks/weekly-report/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Growth Squad · Weekly W15</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-weekly-report">
<div class="deck">
<!-- 1. Cover -->
<section class="slide" data-title="Cover">
<div class="cover-head">
<div class="logo">Growth Squad</div>
<div class="week-chip">W15 · 2026-04-07 → 2026-04-13</div>
</div>
<p class="kicker">WEEKLY REPORT</p>
<h1 class="h1 mt-s">本周:付费转化率<br>回到了 <span style="color:var(--accent)">3.8%</span>。</h1>
<p class="lede mt-m">6 个发布,3 个实验收敛,1 个阻塞项升级。整体健康。</p>
<div class="deck-footer"><span>Prepared by @lewis · reviewed by @may</span><span class="slide-number" data-current="1" data-total="7"></span></div>
</section>
<!-- 2. KPI -->
<section class="slide" data-title="KPIs">
<p class="kicker">HIGHLIGHTS · KPIs</p>
<h2 class="h2">本周核心指标</h2>
<div class="grid g4 mt-l">
<div class="kpi good"><div class="label">Paid conv.</div><div class="value">3.82%</div><div class="delta up">▲ +0.4 pts WoW</div></div>
<div class="kpi good"><div class="label">MRR</div><div class="value">$148k</div><div class="delta up">▲ +6.1%</div></div>
<div class="kpi"><div class="label">Signups</div><div class="value">12,430</div><div class="delta flat">— +0.3%</div></div>
<div class="kpi bad"><div class="label">D7 retention</div><div class="value">41%</div><div class="delta down">▼ -1.8 pts</div></div>
<div class="kpi good"><div class="label">NPS</div><div class="value">64</div><div class="delta up">▲ +3</div></div>
<div class="kpi"><div class="label">Support tickets</div><div class="value">318</div><div class="delta flat">— -12</div></div>
<div class="kpi warn"><div class="label">p95 latency</div><div class="value">412ms</div><div class="delta down">▼ +38ms</div></div>
<div class="kpi good"><div class="label">Deploys</div><div class="value">37</div><div class="delta up">▲ +9</div></div>
</div>
</section>
<!-- 3. Shipped -->
<section class="slide" data-title="Shipped">
<p class="kicker">SHIPPED THIS WEEK · 6 items</p>
<h2 class="h2">Shipped</h2>
<div class="mt-l" style="max-width:980px">
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>New onboarding checklist v3</b><p class="dim" style="font-size:13px;margin:2px 0 0">4-step checklist replaces the old 7-step modal. A/B won +18% activation.</p></div><span class="owner">@may</span></div>
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>Stripe Tax auto-filing</b><p class="dim" style="font-size:13px;margin:2px 0 0">Quarterly filings now handled for 12 US states via Stripe Tax API.</p></div><span class="owner">@raj</span></div>
<div class="ship-item"><span class="tag exp">EXP</span><div><b>Pricing page hero test</b><p class="dim" style="font-size:13px;margin:2px 0 0">"From $29" vs "Free trial" headline. Free-trial wins +22% click-through.</p></div><span class="owner">@lewis</span></div>
<div class="ship-item"><span class="tag fix">FIX</span><div><b>Edge case in SSO redirect</b><p class="dim" style="font-size:13px;margin:2px 0 0">Google Workspace users with custom domains now land on the correct workspace.</p></div><span class="owner">@eli</span></div>
<div class="ship-item"><span class="tag infra">INFRA</span><div><b>Postgres 16 upgrade</b><p class="dim" style="font-size:13px;margin:2px 0 0">Zero-downtime migration. Query p50 down 14%, p95 down 9%.</p></div><span class="owner">@raj</span></div>
<div class="ship-item"><span class="tag feat">FEAT</span><div><b>Referral rewards v1</b><p class="dim" style="font-size:13px;margin:2px 0 0">Both sides get 1 month free. Dashboard + email flow live behind flag.</p></div><span class="owner">@may</span></div>
</div>
</section>
<!-- 4. Metrics chart -->
<section class="slide" data-title="Metrics">
<p class="kicker">METRIC DEEP-DIVE</p>
<h2 class="h2">Paid conversion, last 8 weeks</h2>
<div class="chart mt-l">
<div class="row" style="justify-content:space-between"><h4>Paid conv. rate · weekly</h4><span class="pill" style="background:var(--surface-2);color:var(--text-2)">target: 4.0%</span></div>
<div class="chart-bars">
<div class="col"><div class="b" data-v="3.1%" style="height:58%"></div><div class="lbl">W08</div></div>
<div class="col"><div class="b" data-v="3.3%" style="height:64%"></div><div class="lbl">W09</div></div>
<div class="col"><div class="b" data-v="3.5%" style="height:72%"></div><div class="lbl">W10</div></div>
<div class="col"><div class="b" data-v="3.6%" style="height:75%"></div><div class="lbl">W11</div></div>
<div class="col"><div class="b" data-v="3.4%" style="height:68%"></div><div class="lbl">W12</div></div>
<div class="col"><div class="b" data-v="3.0%" style="height:55%"></div><div class="lbl">W13</div></div>
<div class="col"><div class="b" data-v="3.4%" style="height:68%"></div><div class="lbl">W14</div></div>
<div class="col"><div class="b" data-v="3.8%" style="height:88%"></div><div class="lbl">W15</div></div>
</div>
<p class="dim mt-m" style="font-size:13px;margin-top:36px">Drop in W13 tracked to a broken Stripe webhook (fixed W14). Rebound in W15 is driven by the new onboarding checklist.</p>
</div>
</section>
<!-- 5. Blockers -->
<section class="slide" data-title="Blockers">
<p class="kicker">BLOCKERS · 3 items</p>
<h2 class="h2">Needs attention</h2>
<div class="mt-l" style="max-width:900px">
<div class="blocker">
<h4>p95 latency regressed to 412ms (+38ms)</h4>
<p>Traced to the new recommender service under load. Adding caching layer + connection pooling.</p>
<div class="meta">owner: @raj · ETA: W16 Wed · severity: medium</div>
</div>
<div class="blocker">
<h4>Apple Pay disabled in EU for 3 days</h4>
<p>Stripe credential rotation wasn't synced to the EU account. Fixed, but cost ~$4.2k in lost checkouts.</p>
<div class="meta">owner: @eli · severity: high · postmortem in progress</div>
</div>
<div class="blocker">
<h4>D7 retention down 1.8 points</h4>
<p>Cohort analysis shows it's isolated to the free-trial pricing test. Need to decide: kill test, or push through W16.</p>
<div class="meta">owner: @lewis · needs decision from @may by Monday</div>
</div>
</div>
</section>
<!-- 6. Next week -->
<section class="slide" data-title="Next Week">
<p class="kicker">NEXT WEEK · W16 plan</p>
<h2 class="h2">下周重点</h2>
<div class="mt-l" style="max-width:960px">
<div class="next-row"><div class="owner">@raj</div><div class="task"><b>Ship recommender cache layer</b><span>blocker · must land Wed</span></div></div>
<div class="next-row"><div class="owner">@may</div><div class="task"><b>Referral rewards · flag rollout to 100%</b><span>milestone · targets +3% WoW signups</span></div></div>
<div class="next-row"><div class="owner">@lewis</div><div class="task"><b>Pricing test: decision doc + readout</b><span>deadline Mon noon</span></div></div>
<div class="next-row"><div class="owner">@eli</div><div class="task"><b>Apple Pay postmortem + runbook update</b><span>include in W16 eng review</span></div></div>
<div class="next-row"><div class="owner">squad</div><div class="task"><b>Q2 OKR planning offsite</b><span>Thu 2–5pm · async pre-reads Wed</span></div></div>
</div>
</section>
<!-- 7. Thanks -->
<section class="slide center tc" data-title="Thanks">
<div>
<p class="kicker">FIN · week 15</p>
<h1 class="h1" style="font-size:100px">Thanks, team 🫶</h1>
<p class="lede" style="margin:16px auto">Solid week. Rebound earned, not luck.</p>
<div class="row mt-l" style="justify-content:center;gap:16px">
<span class="week-chip">Next report: Mon W16</span>
<span class="week-chip">questions → #growth-squad</span>
</div>
</div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body></html>
templates/full-decks/xhs-white-editorial/README.md
# xhs-white-editorial
白底杂志风、强调重点块、macaron soft-card 分组。灵感来自 `20260409 升级版知识库/小红书图文/v2-白底版/slide_01_cover.html` 的顶部彩虹条 + 大字标题,以及 `20260412-AI测试与安全/xhs-ai-testing-safety-v2.html` 的 `.focus` 黑底白字强重点和 macaron 软色卡片系统。
**Visual traits:** 纯白背景、顶部 10 色彩虹条、巨型 80-110px 标题配轻微负字距、渐变 brand 文字(紫→蓝→绿→橙→粉)、macaron 软色卡(soft-purple / pink / blue / green / orange)、胶囊 tag + dot、黑底 `.focus` 强调框、hero quote box 带淡阴影。
**Use when:** 你需要一份能当小红书图文、也能当横屏 deck 用的白底内容帖;文字多、重点密集、需要一眼抓住关键词;面向中文读者为主。
**Source inspiration:** `20260409` xhs v2 白底封面 + `20260412` AI 测试与安全 v2。
**Path:** `templates/full-decks/xhs-white-editorial/index.html`
templates/full-decks/xhs-white-editorial/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>白底杂志风 · XHS Editorial</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" href="style.css">
</head>
<body class="tpl-xhs-white-editorial">
<div class="deck">
<!-- 1. COVER -->
<section class="slide is-active">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>AI 时代 · 职业判断</div>
<div class="xw-page">01 / 08</div>
</div>
<div class="xw-kicker">我越来越确定的一件事</div>
<h1 class="xw-title">以后最贵的工作,<br>是 <span class="xw-grad">测试 + 安全</span></h1>
<p class="xw-sub">AI 会越来越会做事。但谁来保证它 <span class="xw-focus">做对</span>、<span class="xw-focus">没风险</span>、<span class="xw-focus">不会出事</span>?</p>
<div class="xw-hero">
<div class="xw-quote">未来最值钱的,<br>不是 <span class="xw-focus-orange">生产</span>,而是 <span class="xw-focus">验收和兜底</span>。</div>
</div>
<div class="xw-footer"><span>白底|强重点|杂志竖排</span><span>Cover · 01</span></div>
</section>
<!-- 2. SECTION DIVIDER -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>Chapter · 01</div>
<div class="xw-page">02 / 08</div>
</div>
<div style="margin-top:120px">
<div class="xw-kicker" style="font-size:20px;letter-spacing:.2em;text-transform:uppercase;color:#98a2b3">第一章</div>
<h1 class="xw-title" style="font-size:110px;margin-top:20px">先看 <span class="xw-grad">大趋势</span></h1>
<p class="xw-sub" style="font-size:28px">当执行越来越便宜,判断就会越来越贵。</p>
</div>
<div class="xw-footer"><span>Section Divider</span><span>02 / 08</span></div>
</section>
<!-- 3. CONTENT — 4 card grid -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>越来越多的事会交给 AI</div>
<div class="xw-page">03 / 08</div>
</div>
<h2 class="xw-title-md">未来 3 年,这些事都会 <span class="xw-grad">自动跑</span></h2>
<div class="xw-grid-2">
<div class="xw-card soft-pink"><div class="xw-label">内容</div><div class="main">写文案 · 写方案 · 写脚本</div><div class="desc">创作变成一个 prompt 的距离</div></div>
<div class="xw-card soft-blue"><div class="xw-label">生产</div><div class="main">做图 · 搭页面 · 做表格</div><div class="desc">生产力工具集体重写一次</div></div>
<div class="xw-card soft-green"><div class="xw-label">执行</div><div class="main">跑流程 · 写代码 · 自动操作</div><div class="desc">Agent 从 demo 走进真实工作流</div></div>
<div class="xw-card soft-orange"><div class="xw-label">分析</div><div class="main">读数据 · 做总结 · 给建议</div><div class="desc">决策支持层彻底向下延伸</div></div>
</div>
<div class="xw-footer"><span>Content · Grid 2x2</span><span>03 / 08</span></div>
</section>
<!-- 4. STEPS -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>为什么会这样</div>
<div class="xw-page">04 / 08</div>
</div>
<h2 class="xw-title-md">AI 越强,<span class="xw-grad">判断对错</span> 越值钱</h2>
<div class="xw-steps">
<div class="xw-step"><div class="xw-num">1</div><div class="xw-txt">生产会更便宜,边际成本接近零</div></div>
<div class="xw-step"><div class="xw-num">2</div><div class="xw-txt">复制会更快,错误也一起被加速</div></div>
<div class="xw-step"><div class="xw-num">3</div><div class="xw-txt">AI 一本正经地做错,人类难以察觉</div></div>
<div class="xw-step"><div class="xw-num">4</div><div class="xw-txt">所以最贵的能力会变成 <span class="xw-focus">发现问题</span></div></div>
</div>
<div class="xw-hero"><div class="xw-quote" style="font-size:30px">AI 让「<span class="xw-focus-blue">做出来</span>」变便宜,<br>但让「<span class="xw-focus">做对、做稳、别出事</span>」变更贵。</div></div>
<div class="xw-footer"><span>Content · Steps</span><span>04 / 08</span></div>
</section>
<!-- 5. CODE EXAMPLE -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>一段你今晚就能跑的验收 Skill</div>
<div class="xw-page">05 / 08</div>
</div>
<h2 class="xw-title-md">不是写 prompt,<br>是写 <span class="xw-grad">验收清单</span></h2>
<pre class="xw-codebox"><span class="cm"># skills/ai-acceptance/SKILL.md</span>
<span class="kw">name</span>: <span class="st">ai-acceptance</span>
<span class="kw">description</span>: <span class="st">"Runs AI output through a 4-gate review checklist."</span>
<span class="kw">gates</span>:
- <span class="hl">functional</span>: <span class="st">"Does it actually do what the user asked?"</span>
- <span class="hl">edge_cases</span>: <span class="st">"Empty / long / non-ASCII / concurrent?"</span>
- <span class="hl">safety</span>: <span class="st">"PII, secrets, destructive ops — all red-flagged?"</span>
- <span class="hl">rollback</span>: <span class="st">"If this ships and breaks, can we undo in 60s?"</span></pre>
<div class="xw-footer"><span>Content · Code Block</span><span>05 / 08</span></div>
</section>
<!-- 6. CHART — SVG bar -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>岗位相对价值变化</div>
<div class="xw-page">06 / 08</div>
</div>
<h2 class="xw-title-md">越来越 <span class="xw-focus-pink">便宜</span>,越来越 <span class="xw-focus-green">贵</span></h2>
<svg viewBox="0 0 960 380" style="width:100%;max-width:1000px;margin-top:30px" xmlns="http://www.w3.org/2000/svg">
<g font-family="Inter, sans-serif" font-size="16" fill="#475467">
<!-- baseline -->
<line x1="180" y1="330" x2="940" y2="330" stroke="#eaecf3" stroke-width="2"/>
<!-- rows -->
<g transform="translate(0,40)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">纯执行</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#fff0f6"/>
<rect x="180" y="10" width="120" height="28" rx="14" fill="#ff5fa2"/>
<text x="710" y="30" fill="#c11574" font-weight="700">-65% 价值</text>
</g>
<g transform="translate(0,100)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">内容生产</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#eef4ff"/>
<rect x="180" y="10" width="200" height="28" rx="14" fill="#4e8cff"/>
<text x="710" y="30" fill="#174ea6" font-weight="700">-40% 价值</text>
</g>
<g transform="translate(0,160)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">数据分析</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#fff5ea"/>
<rect x="180" y="10" width="320" height="28" rx="14" fill="#ff9d42"/>
<text x="710" y="30" fill="#b54708" font-weight="700">持平</text>
</g>
<g transform="translate(0,220)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">测试 / 验收</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#edfdf3"/>
<rect x="180" y="10" width="440" height="28" rx="14" fill="#17b26a"/>
<text x="710" y="30" fill="#067647" font-weight="700">+85% 价值</text>
</g>
<g transform="translate(0,280)">
<text x="170" y="30" text-anchor="end" font-weight="700" fill="#111">安全 / 风控</text>
<rect x="180" y="10" width="520" height="28" rx="14" fill="#f4efff"/>
<rect x="180" y="10" width="500" height="28" rx="14" fill="#7b61ff"/>
<text x="710" y="30" fill="#5b21b6" font-weight="700">+110% 价值</text>
</g>
</g>
</svg>
<div class="xw-footer"><span>Chart · Horizontal Bars</span><span>06 / 08</span></div>
</section>
<!-- 7. CTA -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>今晚就可以做的三件事</div>
<div class="xw-page">07 / 08</div>
</div>
<h2 class="xw-title-md">别再追工具,<br>开始练 <span class="xw-grad">判断力</span></h2>
<div class="xw-grid-3">
<div class="xw-card soft-purple"><div class="xw-label">Tonight</div><div class="main">写一份<br>验收清单</div><div class="desc">哪怕只有 5 条,开始比完美更重要</div></div>
<div class="xw-card soft-blue"><div class="xw-label">This week</div><div class="main">跑一遍<br>红队演练</div><div class="desc">对自己的 agent 说:试着让它出事</div></div>
<div class="xw-card soft-green"><div class="xw-label">This month</div><div class="main">加一条<br>回滚流程</div><div class="desc">60 秒内能撤销,你就敢把手放开</div></div>
</div>
<div class="xw-hero"><div class="xw-quote" style="font-size:32px">真正的稀缺,不是「会用 AI」,<br>而是 <span class="xw-focus">「敢为 AI 的结果签字」</span>。</div></div>
<div class="xw-footer"><span>CTA</span><span>07 / 08</span></div>
</section>
<!-- 8. THANKS -->
<section class="slide">
<div class="xw-topline"></div>
<div class="xw-topbar">
<div class="xw-tag"><span class="dot"></span>Thanks for reading</div>
<div class="xw-page">08 / 08</div>
</div>
<div style="margin-top:100px">
<div class="xw-big-stat xw-grad">谢谢<small> · thanks</small></div>
<p class="xw-sub" style="font-size:28px;margin-top:36px">如果你也在想这些问题,欢迎在评论里告诉我——<br>你最想让 AI 帮你做什么?你最不放心它做什么?</p>
<div style="margin-top:40px">
<span class="xw-pill">@lewis</span>
<span class="xw-pill">小红书 · 白底杂志风</span>
<span class="xw-pill">html-ppt · full-deck</span>
</div>
</div>
<div class="xw-footer"><span>End</span><span>08 / 08</span></div>
</section>
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
templates/full-decks/xhs-white-editorial/style.css
/* xhs-white-editorial — 白底杂志风 */
.tpl-xhs-white-editorial{
--xw-bg:#ffffff;
--xw-ink:#111318;
--xw-ink2:#475467;
--xw-muted:#98a2b3;
--xw-line:#eaecf3;
--xw-purple:#7b61ff;
--xw-pink:#ff5fa2;
--xw-blue:#4e8cff;
--xw-green:#17b26a;
--xw-orange:#ff9d42;
--xw-soft-purple:#f4efff;
--xw-soft-pink:#fff0f6;
--xw-soft-blue:#eef4ff;
--xw-soft-green:#edfdf3;
--xw-soft-orange:#fff5ea;
background:var(--xw-bg);
color:var(--xw-ink);
font-family:'Inter','Noto Sans SC','PingFang SC',-apple-system,sans-serif;
}
.tpl-xhs-white-editorial .slide{background:#fff;padding:72px 88px}
.tpl-xhs-white-editorial .xw-topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
.tpl-xhs-white-editorial .xw-tag{display:inline-flex;align-items:center;gap:10px;padding:10px 18px;border:1px solid var(--xw-line);border-radius:999px;font-size:15px;color:var(--xw-ink2);background:#fff}
.tpl-xhs-white-editorial .xw-tag .dot{width:10px;height:10px;border-radius:50%;background:linear-gradient(90deg,#7b61ff,#4e8cff,#17b26a,#ff9d42,#ff5fa2)}
.tpl-xhs-white-editorial .xw-page{font-size:14px;color:var(--xw-muted);letter-spacing:.1em}
.tpl-xhs-white-editorial .xw-kicker{font-size:18px;color:var(--xw-ink2);margin-top:6px;font-weight:500}
.tpl-xhs-white-editorial .xw-title{font-size:84px;line-height:1.02;letter-spacing:-2px;font-weight:850;margin:18px 0 0;color:var(--xw-ink)}
.tpl-xhs-white-editorial .xw-title-md{font-size:60px;line-height:1.05;letter-spacing:-1.5px;font-weight:800;margin:14px 0 0}
.tpl-xhs-white-editorial .xw-grad{background:linear-gradient(90deg,#7b61ff 0%,#4e8cff 25%,#17b26a 48%,#ff9d42 72%,#ff5fa2 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
.tpl-xhs-white-editorial .xw-sub{font-size:24px;line-height:1.45;color:#1f2937;margin-top:22px;max-width:900px}
.tpl-xhs-white-editorial .xw-focus{display:inline-block;padding:6px 14px;border-radius:14px;background:#111318;color:#fff;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-blue{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-blue);color:#174ea6;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-pink{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-pink);color:#c11574;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-orange{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-orange);color:#b54708;font-weight:700}
.tpl-xhs-white-editorial .xw-focus-green{display:inline-block;padding:6px 14px;border-radius:14px;background:var(--xw-soft-green);color:#067647;font-weight:700}
.tpl-xhs-white-editorial .xw-hero{margin-top:28px;border:1px solid var(--xw-line);border-radius:28px;padding:30px 34px;background:linear-gradient(180deg,#fff 0%,#fcfcff 100%);box-shadow:0 18px 48px rgba(17,19,24,.08)}
.tpl-xhs-white-editorial .xw-quote{font-size:38px;line-height:1.3;font-weight:800;letter-spacing:-.5px}
.tpl-xhs-white-editorial .xw-grid-2{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-top:22px}
.tpl-xhs-white-editorial .xw-grid-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;margin-top:22px}
.tpl-xhs-white-editorial .xw-card{border:1px solid var(--xw-line);border-radius:24px;padding:24px 26px;box-shadow:0 10px 24px rgba(17,19,24,.04);background:#fff}
.tpl-xhs-white-editorial .xw-card.soft-purple{background:var(--xw-soft-purple)}
.tpl-xhs-white-editorial .xw-card.soft-pink{background:var(--xw-soft-pink)}
.tpl-xhs-white-editorial .xw-card.soft-blue{background:var(--xw-soft-blue)}
.tpl-xhs-white-editorial .xw-card.soft-green{background:var(--xw-soft-green)}
.tpl-xhs-white-editorial .xw-card.soft-orange{background:var(--xw-soft-orange)}
.tpl-xhs-white-editorial .xw-label{font-size:14px;font-weight:800;opacity:.7;margin-bottom:10px;letter-spacing:.08em;text-transform:uppercase}
.tpl-xhs-white-editorial .xw-card .main{font-size:28px;line-height:1.22;font-weight:850;letter-spacing:-.5px}
.tpl-xhs-white-editorial .xw-card .desc{font-size:16px;line-height:1.5;color:#475467;margin-top:12px}
.tpl-xhs-white-editorial .xw-steps{margin-top:18px}
.tpl-xhs-white-editorial .xw-step{display:flex;gap:18px;align-items:flex-start;margin:16px 0}
.tpl-xhs-white-editorial .xw-num{flex:0 0 48px;height:48px;border-radius:50%;background:#111318;color:#fff;display:grid;place-items:center;font-size:20px;font-weight:900}
.tpl-xhs-white-editorial .xw-txt{font-size:22px;line-height:1.45;font-weight:700}
.tpl-xhs-white-editorial .xw-codebox{background:#0f1117;color:#e4e2d8;border-radius:18px;padding:22px 26px;font-family:'JetBrains Mono',monospace;font-size:15px;line-height:1.75;margin-top:20px;border:1px solid #1f222c}
.tpl-xhs-white-editorial .xw-codebox .cm{color:#6b6a62}
.tpl-xhs-white-editorial .xw-codebox .kw{color:#c88f64}
.tpl-xhs-white-editorial .xw-codebox .st{color:#a8c292}
.tpl-xhs-white-editorial .xw-codebox .hl{color:#e9c58a;font-weight:600}
.tpl-xhs-white-editorial .xw-footer{position:absolute;left:88px;right:88px;bottom:44px;display:flex;justify-content:space-between;align-items:flex-end;font-size:13px;color:var(--xw-muted)}
.tpl-xhs-white-editorial .xw-topline{position:absolute;top:0;left:0;right:0;height:5px;background:linear-gradient(90deg,#6366f1,#8b5cf6,#a855f7,#ec4899,#f43f5e,#f97316,#eab308,#22c55e,#06b6d4,#6366f1)}
.tpl-xhs-white-editorial .xw-pill{display:inline-block;padding:8px 16px;border-radius:999px;font-size:14px;font-weight:700;margin:0 8px 8px 0;background:#fff;border:1px solid var(--xw-line);color:#394150}
.tpl-xhs-white-editorial .xw-big-stat{font-size:96px;font-weight:900;letter-spacing:-4px;line-height:1}
.tpl-xhs-white-editorial .xw-big-stat small{font-size:22px;color:var(--xw-muted);font-weight:700;letter-spacing:0;margin-left:6px}
templates/single-page/bullets.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Bullets</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Why">
<p class="kicker">Why · 为什么</p>
<h2 class="h2">好的演讲系统,帮你做三件事</h2>
<p class="lede mb-l">不是做不出漂亮的 slide,是每次都要重新做。</p>
<ul class="grid g1 anim-stagger-list" style="list-style:none;padding:0;margin:0;gap:14px" data-anim-target>
<li class="card card-accent"><h4>① 统一设计语言</h4><p class="dim">所有页共用同一套 tokens,字号、颜色、阴影不会跑偏。</p></li>
<li class="card card-accent"><h4>② 降低复用成本</h4><p class="dim">下一次讲同类话题,复制 deck、换数据、换主题即可。</p></li>
<li class="card card-accent"><h4>③ 可切换,可演示</h4><p class="dim">按 T 即可循环切主题、按 A 可切动效,汇报现场加分。</p></li>
</ul>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/arch-diagram.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Architecture</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.arch{margin-top:20px;display:grid;grid-template-rows:auto auto auto;gap:22px}
.arch .tier{display:grid;grid-template-columns:120px 1fr;align-items:stretch;gap:22px}
.arch .tname{display:flex;align-items:center;justify-content:center;background:var(--surface-2);border-radius:var(--radius);padding:18px;font-weight:600;font-size:13px;color:var(--text-2);text-transform:uppercase;letter-spacing:.1em;text-align:center}
.arch .cells{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.arch .cells.three{grid-template-columns:repeat(3,1fr)}
.arch .cell{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:16px;text-align:center;box-shadow:var(--shadow)}
.arch .cell .ic{font-size:24px}
.arch .cell h4{font-size:14px;margin:6px 0 2px}
.arch .cell p{font-size:11px;color:var(--text-3);margin:0}
.arch .tier.hl .cell{border-top:3px solid var(--accent)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Architecture">
<p class="kicker">Architecture · 系统总览</p>
<h2 class="h2">一套三层栈</h2>
<div class="arch anim-stagger-list" data-anim-target>
<div class="tier"><div class="tname">UI Layer</div>
<div class="cells"><div class="cell"><div class="ic">🧱</div><h4>Layouts</h4><p>30 个单页</p></div>
<div class="cell"><div class="ic">🎨</div><h4>Themes</h4><p>24 tokens 主题</p></div>
<div class="cell"><div class="ic">✨</div><h4>Animations</h4><p>25 个命名动效</p></div>
<div class="cell"><div class="ic">⌨️</div><h4>Runtime</h4><p>键盘导航</p></div>
</div>
</div>
<div class="tier hl"><div class="tname">Core · tokens</div>
<div class="cells three"><div class="cell"><div class="ic">🎯</div><h4>base.css</h4><p>排版 + 网格</p></div>
<div class="cell"><div class="ic">🔤</div><h4>fonts.css</h4><p>中英字体</p></div>
<div class="cell"><div class="ic">🪞</div><h4>:root vars</h4><p>语义颜色</p></div>
</div>
</div>
<div class="tier"><div class="tname">Tooling</div>
<div class="cells three"><div class="cell"><div class="ic">🧪</div><h4>render.sh</h4><p>headless Chrome</p></div>
<div class="cell"><div class="ic">🆕</div><h4>new-deck.sh</h4><p>脚手架</p></div>
<div class="cell"><div class="ic">📦</div><h4>AgentSkill</h4><p>Claude 接入点</p></div>
</div>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/chart-line.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Line</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Line chart">
<p class="kicker">Chart · 折线图</p>
<h2 class="h2">日活与留存并排看</h2>
<div class="card mt-l" style="height:520px;padding:28px"><canvas id="c"></canvas></div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const accent=css.getPropertyValue('--accent').trim();
const acc2=css.getPropertyValue('--accent-2').trim();
const text2=css.getPropertyValue('--text-2').trim();
const border=css.getPropertyValue('--border').trim();
new Chart(document.getElementById('c'),{type:'line',
data:{labels:['W1','W2','W3','W4','W5','W6','W7','W8','W9','W10','W11','W12'],
datasets:[
{label:'DAU (K)',data:[12,14,15,19,24,28,33,38,45,51,58,66],
borderColor:accent,backgroundColor:accent+'22',fill:true,tension:.4,borderWidth:3,pointRadius:4},
{label:'Retention %',data:[38,40,42,45,48,50,53,55,58,60,62,64],
borderColor:acc2,backgroundColor:acc2+'22',fill:true,tension:.4,borderWidth:3,pointRadius:4}
]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{x:{ticks:{color:text2},grid:{color:border}},
y:{ticks:{color:text2},grid:{color:border}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/cover.html
<!DOCTYPE html>
<html lang="zh-CN" data-theme="minimal-white">
<head>
<meta charset="utf-8"><title>Cover — html-ppt</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head>
<body class="single">
<div class="deck" data-themes="minimal-white,aurora,catppuccin-mocha,tokyo-night,xiaohongshu-white,neo-brutalism" data-theme-base="../../assets/themes/">
<section class="slide is-active" data-title="Cover">
<div class="deck-header"><span class="eyebrow">Keynote · 2026</span><span class="eyebrow">html-ppt</span></div>
<div class="anim-stagger-list">
<p class="kicker">Tech Sharing · 纯干货</p>
<h1 class="h1 anim-fade-up" data-anim="fade-up">
设计一套<span class="gradient-text">属于你</span>的<br>HTML 演讲系统
</h1>
<p class="lede">从主题、版式到动效,全部由模板驱动。一行命令即可开场。</p>
<div class="row wrap mt-l">
<span class="pill pill-accent">24 themes</span>
<span class="pill">30 layouts</span>
<span class="pill">25 animations</span>
<span class="pill">零构建</span>
</div>
</div>
<div class="deck-footer"><span class="dim2">lewis · 2026-04-15</span><span class="slide-number" data-current="1" data-total="1"></span></div>
<div class="notes">封面页口播:大家好,今天给大家带来一套开箱即用的 HTML 演讲系统。</div>
</section>
</div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/code.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Code</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/tokyo-night.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11.10.0/styles/tokyo-night-dark.min.css">
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.10.0/lib/core.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/highlight.js@11.10.0/lib/languages/javascript.min.js"></script>
<script>addEventListener('DOMContentLoaded',()=>{hljs.registerLanguage('javascript',window.hljsLangJavascript||window.hljs.getLanguage('javascript'));document.querySelectorAll('pre code').forEach(el=>hljs.highlightElement(el))})</script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Code">
<p class="kicker">Snippet · 运行时核心</p>
<h2 class="h2">按一下键盘,幻灯片就跑起来了</h2>
<pre class="card mt-m" style="padding:24px"><code class="language-javascript">// runtime.js — keyboard-driven deck
function go(n) {
n = Math.max(0, Math.min(total - 1, n));
slides.forEach((s, i) => {
s.classList.toggle('is-active', i === n);
});
idx = n;
barFill.style.width = ((n + 1) / total * 100) + '%';
history.replaceState(null, '', '#/' + (n + 1));
}
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === ' ') go(idx + 1);
if (e.key === 'ArrowLeft') go(idx - 1);
if (e.key === 't') cycleTheme();
});</code></pre>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/full-decks/xhs-post/style.css
/* xhs-post — 小红书 3:4 九宫格 */
.tpl-xhs-post{
--bg:#fef7f3;--bg-soft:#fff1ea;--surface:#ffffff;--surface-2:#fff5ef;
--border:rgba(90,40,30,.12);--border-strong:rgba(90,40,30,.24);
--text-1:#3a1f18;--text-2:#6f4a3e;--text-3:#a68676;
--accent:#ff6b8b;--accent-2:#ffa94d;--accent-3:#ffd166;
--grad:linear-gradient(135deg,#ffd3e0,#ffe5c7 50%,#d6f0ff);
--good:#7bc67b;--warn:#ffb547;--bad:#ff6b6b;
--radius:24px;--radius-lg:32px;
--shadow:0 14px 36px rgba(90,40,30,.08);
font-family:'Inter','Noto Sans SC','PingFang SC',sans-serif;
}
.tpl-xhs-post{background:#f0eae2;display:flex;align-items:center;justify-content:center;min-height:100vh}
.tpl-xhs-post .deck{width:810px;height:1080px;position:relative;background:transparent}
.tpl-xhs-post .slide{
position:absolute;inset:0;width:810px;height:1080px;aspect-ratio:3/4;
padding:70px 64px;border-radius:28px;overflow:hidden;
background:var(--bg);
}
.tpl-xhs-post .slide::before{content:"";position:absolute;inset:0;background:
radial-gradient(45% 30% at 80% 10%,rgba(255,209,102,.35),transparent 70%),
radial-gradient(50% 35% at 10% 95%,rgba(255,107,139,.22),transparent 70%),
radial-gradient(40% 30% at 90% 85%,rgba(122,200,255,.18),transparent 70%);
pointer-events:none;z-index:0}
.tpl-xhs-post .slide > *{position:relative;z-index:1}
.tpl-xhs-post .h1{font-size:72px;line-height:1.1;font-weight:900;letter-spacing:-.02em;color:var(--text-1)}
.tpl-xhs-post .h2{font-size:54px;line-height:1.15;font-weight:800;letter-spacing:-.015em;color:var(--text-1)}
.tpl-xhs-post .h3{font-size:36px;font-weight:800;color:var(--text-1)}
.tpl-xhs-post .page-dot{position:absolute;top:40px;right:48px;background:var(--text-1);color:#fff;border-radius:999px;padding:6px 14px;font-family:'JetBrains Mono',monospace;font-size:14px;font-weight:700;z-index:2}
.tpl-xhs-post .sticker{position:absolute;padding:10px 18px;background:#fff;border:2.5px dashed var(--text-1);border-radius:18px;font-weight:800;font-size:18px;color:var(--text-1);transform:rotate(-3deg);box-shadow:4px 4px 0 var(--text-1)}
.tpl-xhs-post .sticker.pink{background:#ffd3e0}
.tpl-xhs-post .sticker.yellow{background:#ffe788}
.tpl-xhs-post .sticker.blue{background:#cfeaff}
.tpl-xhs-post .sticker.green{background:#d4f2c8}
.tpl-xhs-post .hand-box{background:#fff;border:2.5px solid var(--text-1);border-radius:22px;padding:24px 28px;box-shadow:5px 5px 0 var(--text-1)}
.tpl-xhs-post .lede{color:var(--text-2);font-size:26px;line-height:1.55}
.tpl-xhs-post .big-emoji{font-size:180px;line-height:1;text-align:center}
.tpl-xhs-post .num-circle{display:inline-flex;align-items:center;justify-content:center;width:72px;height:72px;border-radius:50%;background:var(--accent);color:#fff;font-weight:900;font-size:36px;border:3px solid var(--text-1);box-shadow:4px 4px 0 var(--text-1)}
.tpl-xhs-post .step-card{background:#fff;border:2.5px solid var(--text-1);border-radius:22px;padding:26px 28px;box-shadow:5px 5px 0 var(--text-1);margin-bottom:24px}
.tpl-xhs-post .step-card h4{font-size:28px;font-weight:800;margin:0 0 6px}
.tpl-xhs-post .step-card p{font-size:18px;color:var(--text-2);margin:0}
.tpl-xhs-post .tag-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:24px}
.tpl-xhs-post .ht{background:#fff;color:var(--accent);border:2px solid var(--text-1);padding:6px 14px;border-radius:999px;font-weight:700;font-size:16px}
.tpl-xhs-post .cover-title{background:linear-gradient(180deg,transparent 60%,var(--accent-3) 60%,var(--accent-3) 92%,transparent 92%);padding:0 10px}
.tpl-xhs-post .heart{color:var(--accent);font-size:28px}
.tpl-xhs-post .bottom-bar{position:absolute;bottom:40px;left:64px;right:64px;display:flex;justify-content:space-between;align-items:center;font-size:15px;color:var(--text-3);font-family:'JetBrains Mono',monospace;z-index:2}
.tpl-xhs-post .avatar{width:54px;height:54px;border-radius:50%;background:var(--grad);border:2.5px solid var(--text-1);box-shadow:3px 3px 0 var(--text-1);display:inline-flex;align-items:center;justify-content:center;font-weight:900;font-size:20px;color:var(--text-1)}
templates/single-page/chart-radar.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Radar</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Radar chart">
<p class="kicker">Chart · 雷达图</p>
<h2 class="h2">把 html-ppt 和竞品放一张图</h2>
<div class="card mt-l" style="height:520px;padding:28px"><canvas id="c"></canvas></div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const a1=css.getPropertyValue('--accent').trim();
const a2=css.getPropertyValue('--accent-2').trim();
const text2=css.getPropertyValue('--text-2').trim();
new Chart(document.getElementById('c'),{type:'radar',
data:{labels:['上手','美观','自定义','性能','导出','生态'],
datasets:[
{label:'html-ppt',data:[9,9,10,10,9,6],borderColor:a1,backgroundColor:a1+'33',borderWidth:3,pointRadius:5},
{label:'reveal.js',data:[8,7,9,8,8,10],borderColor:a2,backgroundColor:a2+'22',borderWidth:3,pointRadius:5}]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{r:{suggestedMin:0,suggestedMax:10,ticks:{color:text2,backdropColor:'transparent'},
pointLabels:{color:text2,font:{size:14}},grid:{color:'rgba(0,0,0,.08)'},angleLines:{color:'rgba(0,0,0,.08)'}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/cta.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>CTA</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/aurora.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.cta .btn{display:inline-flex;align-items:center;gap:10px;padding:20px 32px;border-radius:999px;background:var(--accent);color:#0b1024;font-weight:700;font-size:20px;box-shadow:var(--shadow-lg);text-decoration:none;border:none;cursor:pointer}
.cta .btn.outline{background:transparent;color:var(--text-1);border:1.5px solid var(--border-strong)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active center tc cta" data-title="CTA">
<div style="max-width:900px">
<p class="kicker">Call to action</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in" style="font-size:96px">
<span class="gradient-text">动手做你的</span><br>第一份 html-ppt
</h1>
<p class="lede" style="margin:16px auto 30px">复制模板、换上你的内容、按 <b>T</b> 挑一个最对味的主题,讲完还能一键导出 PNG。</p>
<div class="row" style="justify-content:center">
<a class="btn" href="#">🚀 ./new-deck.sh my-talk</a>
<a class="btn outline" href="#">查看 SKILL.md</a>
</div>
<p class="dim mt-l" style="font-size:14px">键盘: ← → 翻页 · T 主题 · A 动效 · F 全屏 · O 概览 · S 备注</p>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/layout-showcase.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><title>Layout Showcase — html-ppt</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
<style>
.layout-nav{position:fixed;top:16px;left:50%;transform:translateX(-50%);z-index:50;background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:8px 20px;font-family:var(--font-mono);font-size:12px;color:var(--text-2);box-shadow:var(--shadow);display:flex;gap:14px;align-items:center}
.layout-nav a{color:var(--text-2);text-decoration:none;padding:4px 10px;border-radius:999px}
.layout-nav a:hover{background:var(--surface-2)}
iframe{width:100%;height:100vh;border:0;display:block;background:var(--bg)}
body{margin:0;overflow:hidden}
</style>
</head>
<body>
<div class="layout-nav">
<b>layouts</b>
<a href="#" data-go="-1">←</a>
<span id="cur">cover</span>
<a href="#" data-go="+1">→</a>
</div>
<iframe id="frame" src="single-page/cover.html"></iframe>
<script>
const list=['cover','toc','section-divider','bullets','two-column','three-column','big-quote',
'stat-highlight','kpi-grid','table','code','diff','terminal','flow-diagram','timeline',
'roadmap','mindmap','comparison','pros-cons','todo-checklist','gantt','image-hero','image-grid',
'chart-bar','chart-line','chart-pie','chart-radar','arch-diagram','process-steps','cta','thanks'];
let i=0;
const frame=document.getElementById('frame');
const cur=document.getElementById('cur');
function go(n){
i=(n+list.length)%list.length;
frame.src='single-page/'+list[i]+'.html';
cur.textContent=list[i]+' · '+(i+1)+'/'+list.length;
history.replaceState(null,'','#/'+(i+1));
}
document.querySelectorAll('[data-go]').forEach(a=>a.addEventListener('click',e=>{e.preventDefault();go(i+parseInt(a.dataset.go,10))}));
document.addEventListener('keydown',e=>{
if(e.key==='ArrowRight'||e.key===' '){go(i+1);e.preventDefault()}
if(e.key==='ArrowLeft'){go(i-1);e.preventDefault()}
});
const m=/^#\/(\d+)/.exec(location.hash||'');
if(m)go(parseInt(m[1],10)-1);else go(0);
</script>
</body></html>
templates/single-page/big-quote.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Big Quote</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/editorial-serif.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active center tc" data-title="Quote">
<div style="max-width:1040px">
<div class="serif" style="font-size:140px;line-height:.9;color:var(--accent);opacity:.6">"</div>
<blockquote class="serif anim-fade-up" data-anim="fade-up" style="font-size:56px;line-height:1.25;margin:-40px 0 24px;font-style:italic;font-weight:600">
好的设计不是把东西加得更多,<br>而是把一切不需要的东西拿掉。
</blockquote>
<p class="dim" style="font-size:20px;letter-spacing:.08em">— Dieter Rams</p>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/chart-bar.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Bar</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Bar chart">
<p class="kicker">Chart · 柱状图</p>
<h2 class="h2">季度 MRR 趋势</h2>
<div class="card mt-l" style="height:520px;padding:28px"><canvas id="c"></canvas></div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const accent=css.getPropertyValue('--accent').trim();
const text2=css.getPropertyValue('--text-2').trim();
const border=css.getPropertyValue('--border').trim();
new Chart(document.getElementById('c'),{type:'bar',
data:{labels:['Q1','Q2','Q3','Q4','Q1 +1','Q2 +1','Q3 +1','Q4 +1'],
datasets:[{label:'MRR (K)',data:[42,58,73,96,124,158,204,261],
backgroundColor:accent,borderRadius:8,barThickness:36}]},
options:{plugins:{legend:{labels:{color:text2}}},
scales:{x:{ticks:{color:text2},grid:{color:border}},
y:{ticks:{color:text2},grid:{color:border}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/image-grid.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Image Grid</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.gg{display:grid;grid-template-columns:repeat(4,1fr);grid-auto-rows:180px;gap:14px;margin-top:24px}
.gg .cell{border-radius:var(--radius);overflow:hidden;position:relative;box-shadow:var(--shadow)}
.gg .cell span{position:absolute;inset:auto 0 0 0;padding:12px 14px;color:#fff;font-size:13px;font-weight:500;background:linear-gradient(transparent,rgba(0,0,0,.55))}
.gg .c1{background:linear-gradient(135deg,#3b6cff,#7a5cff);grid-column:span 2;grid-row:span 2}
.gg .c2{background:linear-gradient(135deg,#ff7a90,#ff2742)}
.gg .c3{background:linear-gradient(135deg,#5ef2c6,#7aa2ff)}
.gg .c4{background:linear-gradient(135deg,#ffd27a,#f2a341)}
.gg .c5{background:linear-gradient(135deg,#c984ff,#ff5c8a)}
.gg .c6{background:linear-gradient(135deg,#0e1530,#24283b)}
.gg .c7{background:linear-gradient(135deg,#88c0d0,#5ef2c6);grid-column:span 2}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Image Grid">
<p class="kicker">Gallery · 作品集</p>
<h2 class="h2">一次 bento grid,六张要点图</h2>
<div class="gg anim-stagger-list" data-anim-target>
<div class="cell c1"><span>主视觉 · Keynote 封面</span></div>
<div class="cell c2"><span>暖色 · Stat</span></div>
<div class="cell c3"><span>冷色 · Flow</span></div>
<div class="cell c4"><span>日落 · Code</span></div>
<div class="cell c5"><span>渐变 · Quote</span></div>
<div class="cell c6"><span>暗色 · Terminal</span></div>
<div class="cell c7"><span>极光 · CTA</span></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/image-hero.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Image Hero</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.hero{position:relative;height:calc(100vh - 144px);border-radius:var(--radius-lg);overflow:hidden;box-shadow:var(--shadow-lg)}
.hero .bg{position:absolute;inset:0;background:
radial-gradient(80% 60% at 30% 40%,#ffb38a,transparent 70%),
radial-gradient(70% 50% at 75% 60%,#c084fc,transparent 70%),
linear-gradient(135deg,#0b1024,#1a2238)}
.hero .bg.kb{animation:kf-kenburns 12s ease-in-out infinite alternate}
.hero .overlay{position:absolute;inset:0;background:linear-gradient(180deg,transparent 40%,rgba(10,12,20,.65));}
.hero .caption{position:absolute;bottom:48px;left:56px;right:56px;color:#fff;z-index:2}
.hero h1{font-size:72px;line-height:1;font-weight:800;letter-spacing:-.03em;margin:0}
.hero p{font-size:20px;opacity:.85;margin:14px 0 0;max-width:60ch}
.hero .pill{background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.2)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Image Hero" style="padding:56px">
<div class="hero">
<div class="bg kb"></div>
<div class="overlay"></div>
<div class="caption">
<span class="pill">Cover · 04</span>
<h1 class="mt-s anim-rise-in" data-anim="rise-in">像杂志一样的<br>封面画面感</h1>
<p>Ken Burns 缓慢推拉 + 径向渐变 + 中灰叠加。无须真实图片,也能做出高级感。</p>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/chart-pie.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Chart · Pie</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Pie chart">
<p class="kicker">Chart · 环形图</p>
<h2 class="h2">时间都花在了哪里</h2>
<div class="grid g2 mt-l" style="align-items:center">
<div class="card" style="height:460px;padding:28px"><canvas id="c"></canvas></div>
<div class="card">
<h4>Takeaways</h4>
<p class="dim">超过一半的时间都在写内容,动效只占 5%。说明值得把动效做成可复用模板。</p>
<ul class="mt-m dim">
<li>✍️ 写内容 55%</li><li>🧩 挑版式 18%</li><li>🎨 调样式 14%</li>
<li>📸 出图 8%</li><li>✨ 动效 5%</li>
</ul>
</div>
</div>
<script>
addEventListener('DOMContentLoaded',()=>{
const css=getComputedStyle(document.documentElement);
const colors=['--accent','--accent-2','--accent-3','--good','--warn'].map(k=>css.getPropertyValue(k).trim());
const text2=css.getPropertyValue('--text-2').trim();
new Chart(document.getElementById('c'),{type:'doughnut',
data:{labels:['写内容','挑版式','调样式','出图','动效'],
datasets:[{data:[55,18,14,8,5],backgroundColor:colors,borderWidth:0}]},
options:{cutout:'62%',plugins:{legend:{position:'right',labels:{color:text2}}}}});
});
</script>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/kpi-grid.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>KPI Grid</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="KPIs">
<p class="kicker">Metrics · 关键数字</p>
<h2 class="h2">这一季度,我们做到了什么</h2>
<div class="grid g4 mt-l anim-stagger-list" data-anim-target>
<div class="card"><p class="eyebrow">Revenue</p><div style="font-size:56px;font-weight:800"><span class="counter" data-to="1248">0</span>K</div><p class="dim" style="color:var(--good)">↑ 38% YoY</p></div>
<div class="card"><p class="eyebrow">Active users</p><div style="font-size:56px;font-weight:800"><span class="counter" data-to="82">0</span>K</div><p class="dim" style="color:var(--good)">↑ 12% QoQ</p></div>
<div class="card"><p class="eyebrow">Retention</p><div style="font-size:56px;font-weight:800"><span class="counter" data-to="74">0</span>%</div><p class="dim" style="color:var(--good)">↑ 3 pts</p></div>
<div class="card"><p class="eyebrow">NPS</p><div style="font-size:56px;font-weight:800"><span class="counter" data-to="61">0</span></div><p class="dim" style="color:var(--warn)">→ 持平</p></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/mindmap.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Mindmap</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.mm{position:relative;margin:30px auto 0;max-width:1200px;height:520px}
.mm .n{position:absolute;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 18px;box-shadow:var(--shadow);font-weight:600}
.mm .root{top:calc(50% - 34px);left:calc(50% - 100px);width:200px;padding:22px;background:var(--accent);color:#fff;border:none;text-align:center;font-size:20px;border-radius:24px}
.mm .n.sm{font-size:13px}
.mm svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.mm svg path{stroke:var(--border-strong);stroke-width:1.5;fill:none}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Mindmap">
<p class="kicker">Concept map · 心智图</p>
<h2 class="h2">html-ppt 的知识地图</h2>
<div class="mm">
<svg class="anim-path-draw" viewBox="0 0 1200 520" preserveAspectRatio="none">
<path d="M600,260 C400,140 280,100 160,90"/>
<path d="M600,260 C400,380 280,420 160,430"/>
<path d="M600,260 C800,140 920,100 1040,90"/>
<path d="M600,260 C800,380 920,420 1040,430"/>
<path d="M600,260 C600,140 600,100 600,60"/>
<path d="M600,260 C600,380 600,420 600,460"/>
</svg>
<div class="n root">html-ppt</div>
<div class="n" style="top:60px;left:40px">Themes</div>
<div class="n sm" style="top:410px;left:40px">Layouts</div>
<div class="n sm" style="top:60px;right:40px">Animations</div>
<div class="n sm" style="top:410px;right:40px">Runtime</div>
<div class="n sm" style="top:30px;left:calc(50% - 50px)">Tokens</div>
<div class="n sm" style="top:450px;left:calc(50% - 60px)">Render.sh</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/comparison.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Comparison</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.vs{display:grid;grid-template-columns:1fr 90px 1fr;gap:28px;align-items:stretch;margin-top:30px}
.vs .side{padding:30px}
.vs .mid{font-size:56px;font-weight:800;color:var(--text-3);display:flex;align-items:center;justify-content:center}
.vs .bad-side{border-top:3px solid var(--bad)}
.vs .good-side{border-top:3px solid var(--good)}
.vs h3{font-size:24px}
.vs ul{padding-left:20px;font-size:15px;line-height:1.8;color:var(--text-2)}
.vs li::marker{color:var(--bad)}
.vs .good-side li::marker{color:var(--good)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Comparison">
<p class="kicker">Before vs After · 对比</p>
<h2 class="h2">从「每次重做」到「一键起稿」</h2>
<div class="vs">
<div class="card bad-side side anim-fade-left" data-anim="fade-left">
<h3>📉 过去</h3>
<ul>
<li>每次都要重新做封面、目录、结语</li>
<li>颜色、字号跨 slide 不一致</li>
<li>想换主题?手动改每一页</li>
<li>动效全靠 transition 硬写</li>
<li>截图导出还要一张一张截</li>
</ul>
</div>
<div class="mid">→</div>
<div class="card good-side side anim-fade-right" data-anim="fade-right">
<h3>📈 现在</h3>
<ul>
<li>复制模板、换数据、完事</li>
<li>所有页共享 tokens</li>
<li>按 <b>T</b> 循环切主题</li>
<li>25 个命名动效直接挑</li>
<li>一条 render.sh 全部出图</li>
</ul>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/pros-cons.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Pros / Cons</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.pc{display:grid;grid-template-columns:1fr 1fr;gap:28px;margin-top:30px}
.pc .card h3{display:flex;align-items:center;gap:10px}
.pc .card h3 .b{display:inline-flex;width:36px;height:36px;border-radius:10px;align-items:center;justify-content:center;font-size:20px}
.pc .pro h3 .b{background:color-mix(in srgb,var(--good) 18%,transparent);color:var(--good)}
.pc .con h3 .b{background:color-mix(in srgb,var(--bad) 18%,transparent);color:var(--bad)}
.pc ul{padding-left:22px;line-height:1.8;color:var(--text-2)}
.pc .pro{border-top:3px solid var(--good)}
.pc .con{border-top:3px solid var(--bad)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Pros Cons">
<p class="kicker">Trade-offs · 诚实的取舍</p>
<h2 class="h2">纯 HTML 演讲:好在哪里,痛在哪里</h2>
<div class="pc">
<div class="card pro anim-fade-up" data-anim="fade-up"><h3><span class="b">✓</span> 好处</h3>
<ul><li>零构建:一个文件就能跑</li><li>可 diff:Git 友好,好 review</li><li>可编程:动效自由定制</li><li>可分发:URL / PDF / PNG 都行</li></ul>
</div>
<div class="card con anim-fade-up" data-anim="fade-up"><h3><span class="b">✗</span> 痛点</h3>
<ul><li>协作不像 Keynote 那样实时</li><li>非程序员上手稍陡</li><li>复杂动效仍需写 JS</li><li>部分字体在离线环境缺失</li></ul>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/roadmap.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Roadmap</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.rm{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-top:28px;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
.rm .col{padding:20px 22px;border-right:1px solid var(--border);background:var(--surface);position:relative}
.rm .col:last-child{border-right:none}
.rm .col .tag{display:inline-block;padding:3px 12px;border-radius:999px;font-size:11px;background:var(--surface-2);color:var(--text-2);margin-bottom:10px}
.rm .col.now{background:color-mix(in srgb,var(--accent) 6%,var(--surface))}
.rm .col.now .tag{background:var(--accent);color:#fff}
.rm h4{font-size:15px;margin:8px 0 4px}
.rm ul{padding-left:18px;margin:8px 0 0;font-size:13px;color:var(--text-2)}
.rm li{margin-bottom:6px;line-height:1.5}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Roadmap">
<p class="kicker">Roadmap · 2026</p>
<h2 class="h2">接下来四个季度,我们会做什么</h2>
<div class="rm">
<div class="col now">
<span class="tag">NOW · Q2</span>
<h4>主题系统</h4>
<ul><li>24 个官方主题</li><li>tokens 语义文档</li><li>T 键循环切换</li></ul>
</div>
<div class="col">
<span class="tag">NEXT · Q3</span>
<h4>导出</h4>
<ul><li>PNG 批量</li><li>PDF 打包</li><li>小红书 3:4 切片</li></ul>
</div>
<div class="col">
<span class="tag">LATER · Q4</span>
<h4>交互</h4>
<ul><li>Presenter 模式</li><li>Notes 同屏</li><li>遥控器配对</li></ul>
</div>
<div class="col">
<span class="tag">VISION</span>
<h4>AI 写稿</h4>
<ul><li>从 md 自动分页</li><li>智能选版式</li><li>智能选主题</li></ul>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/section-divider.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Section Divider</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active tc center" data-title="Section 02">
<div style="max-width:780px;margin:0 auto">
<p class="kicker anim-fade-down" data-anim="fade-down">Section · 02</p>
<h1 class="h1 anim-rise-in" data-anim="rise-in" style="font-size:112px">主题与 <span class="gradient-text">Tokens</span></h1>
<div class="divider-accent" style="margin:24px auto"></div>
<p class="lede" style="margin:0 auto">换主题 = 换一份 CSS 变量。其他一切保持不变。</p>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/stat-highlight.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Stat Highlight</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active center tc" data-title="Stat">
<p class="kicker">Impact · 一个数字</p>
<div style="font-size:260px;line-height:1;font-weight:900;letter-spacing:-.05em">
<span class="counter gradient-text" data-to="92">0</span><span class="gradient-text">%</span>
</div>
<h3 class="mt-s">的准备时间被你省下</h3>
<p class="lede" style="margin:16px auto 0">在 10 个真实项目中,使用 html-ppt 的平均 deck 制作时间从 4 小时降到了 20 分钟。</p>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/gantt.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Gantt</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.gantt{margin-top:24px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:24px}
.gantt .hd{display:grid;grid-template-columns:200px repeat(12,1fr);gap:4px;font-size:12px;color:var(--text-3);text-transform:uppercase;letter-spacing:.08em;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid var(--border)}
.gantt .row{display:grid;grid-template-columns:200px repeat(12,1fr);gap:4px;align-items:center;height:40px}
.gantt .lbl{font-size:14px;font-weight:500}
.gantt .cells{grid-column:2/-1;position:relative;height:28px;background:linear-gradient(90deg,var(--surface-2) 1px,transparent 1px) 0 0/calc(100%/12) 100%}
.gantt .bar{position:absolute;top:4px;height:20px;border-radius:6px;background:var(--grad);display:flex;align-items:center;padding:0 10px;font-size:11px;color:#fff;font-weight:600;box-shadow:var(--shadow)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Gantt">
<p class="kicker">Plan · Q2 甘特图</p>
<h2 class="h2">四个月,五条线并行</h2>
<div class="gantt">
<div class="hd"><div>任务</div><div>W1</div><div>W2</div><div>W3</div><div>W4</div><div>W5</div><div>W6</div><div>W7</div><div>W8</div><div>W9</div><div>W10</div><div>W11</div><div>W12</div></div>
<div class="row"><div class="lbl">主题系统 (tokens)</div><div class="cells"><div class="bar" style="left:0;width:25%">tokens + 24 themes</div></div></div>
<div class="row"><div class="lbl">layouts 目录</div><div class="cells"><div class="bar" style="left:17%;width:33%">30 个单页模板</div></div></div>
<div class="row"><div class="lbl">动画系统</div><div class="cells"><div class="bar" style="left:42%;width:25%">25 个命名动效</div></div></div>
<div class="row"><div class="lbl">渲染 / CI</div><div class="cells"><div class="bar" style="left:58%;width:25%">render.sh</div></div></div>
<div class="row"><div class="lbl">发布 & 文档</div><div class="cells"><div class="bar" style="left:75%;width:25%">v1.0</div></div></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/table.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Table</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.t{width:100%;border-collapse:collapse;font-size:16px}
.t th,.t td{padding:14px 16px;text-align:left;border-bottom:1px solid var(--border)}
.t th{font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:var(--text-3);font-weight:600}
.t tr:hover td{background:var(--surface-2)}
.t td.num{font-variant-numeric:tabular-nums;text-align:right}
.up{color:var(--good)}.dn{color:var(--bad)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Table">
<p class="kicker">Comparison · 数据表</p>
<h2 class="h2">主流 HTML 演讲框架对比</h2>
<div class="card mt-l" style="padding:4px 12px">
<table class="t">
<thead><tr><th>框架</th><th>学习曲线</th><th>自定义</th><th class="num">体积</th><th class="num">GitHub ⭐</th><th>适合</th></tr></thead>
<tbody>
<tr><td><b>html-ppt</b></td><td>极低</td><td>★★★★★</td><td class="num">~80 KB</td><td class="num">—</td><td>快速出稿、换主题</td></tr>
<tr><td>reveal.js</td><td>低</td><td>★★★★</td><td class="num">~450 KB</td><td class="num">67k</td><td>长讲座、互动</td></tr>
<tr><td>Slidev</td><td>中</td><td>★★★★★</td><td class="num">需构建</td><td class="num">31k</td><td>开发者、技术分享</td></tr>
<tr><td>Marp</td><td>极低</td><td>★★</td><td class="num">~10 MB</td><td class="num">13k</td><td>Markdown 出稿</td></tr>
<tr><td>Impress.js</td><td>中</td><td>★★★</td><td class="num">~60 KB</td><td class="num">38k</td><td>3D 展示</td></tr>
</tbody>
</table>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/diff.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Diff</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.diff{font-family:var(--font-mono);font-size:14px;line-height:1.6;border-radius:var(--radius);overflow:hidden;border:1px solid var(--border)}
.diff .ln{display:block;padding:2px 16px;white-space:pre}
.diff .add{background:rgba(26,175,108,.12);color:var(--good)}
.diff .del{background:rgba(224,68,90,.12);color:var(--bad)}
.diff .ctx{color:var(--text-2)}
.diff .hd{background:var(--surface-2);color:var(--text-3);padding:8px 16px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;border-bottom:1px solid var(--border)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Diff">
<p class="kicker">Before / After</p>
<h2 class="h2">一次迁移到 tokens 后</h2>
<div class="card diff mt-l" style="padding:0">
<div class="hd">assets/components/card.css</div>
<span class="ln ctx">.card {</span>
<span class="ln del">- background: #ffffff;</span>
<span class="ln del">- color: #0c0d10;</span>
<span class="ln del">- border-radius: 18px;</span>
<span class="ln del">- box-shadow: 0 10px 30px rgba(18,24,40,.08);</span>
<span class="ln add">+ background: var(--surface);</span>
<span class="ln add">+ color: var(--text-1);</span>
<span class="ln add">+ border-radius: var(--radius);</span>
<span class="ln add">+ box-shadow: var(--shadow);</span>
<span class="ln ctx">}</span>
</div>
<p class="dim mt-m">24 个主题从此只需改 variables——其他 0 改动。</p>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/flow-diagram.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Flow Diagram</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.flow{display:flex;align-items:center;gap:16px;margin-top:40px;max-width:1200px}
.flow .node{flex:1;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px;text-align:center;box-shadow:var(--shadow);position:relative}
.flow .node .ic{font-size:32px;margin-bottom:6px}
.flow .node h4{font-size:16px}
.flow .node p{font-size:12px;color:var(--text-3);margin:0}
.flow .arr{color:var(--text-3);font-size:28px;flex-shrink:0}
.flow .node.hl{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 18%,transparent),var(--shadow)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Flow">
<p class="kicker">Pipeline · 渲染管线</p>
<h2 class="h2">从 Markdown 到 PNG,共 5 步</h2>
<div class="flow anim-stagger-list" data-anim-target>
<div class="node"><div class="ic">📝</div><h4>Markdown</h4><p>你的内容源</p></div>
<div class="arr">→</div>
<div class="node"><div class="ic">🧩</div><h4>Layouts</h4><p>选择页型</p></div>
<div class="arr">→</div>
<div class="node hl"><div class="ic">🎨</div><h4>Theme</h4><p>换装</p></div>
<div class="arr">→</div>
<div class="node"><div class="ic">🌐</div><h4>HTML</h4><p>运行时接管</p></div>
<div class="arr">→</div>
<div class="node"><div class="ic">📸</div><h4>PNG</h4><p>headless Chrome</p></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/terminal.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Terminal</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/terminal-green.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.term{font-family:var(--font-mono);font-size:16px;background:#020803;border:1px solid rgba(0,255,120,.3);border-radius:10px;padding:24px 28px;box-shadow:0 0 60px rgba(0,255,136,.08) inset}
.term .bar{display:flex;gap:6px;margin-bottom:16px}
.term .bar span{width:12px;height:12px;border-radius:50%;background:#222;border:1px solid rgba(0,255,136,.3)}
.term .p{color:var(--accent)}
.term .c{color:var(--text-1)}
.term .o{color:var(--text-2)}
.term .caret{display:inline-block;width:9px;height:18px;background:var(--accent);vertical-align:middle;animation:blink 1s step-end infinite}
@keyframes blink{50%{opacity:0}}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Terminal">
<p class="kicker">CLI · 一行命令起一个 deck</p>
<h2 class="h2" style="color:var(--text-1)">你需要的就是一个终端</h2>
<div class="term mt-l">
<div class="bar"><span></span><span></span><span></span></div>
<div><span class="p">$ </span><span class="c">./scripts/new-deck.sh graphify-talk</span></div>
<div class="o"> ✔ created decks/graphify-talk/index.html</div>
<div class="o"> ✔ linked theme → aurora</div>
<div class="o"> ✔ 12 slides scaffolded from templates/single-page/*</div>
<div style="margin-top:12px"><span class="p">$ </span><span class="c">open decks/graphify-talk/index.html</span></div>
<div class="o"> ↗ launched in Chrome</div>
<div style="margin-top:12px"><span class="p">$ </span><span class="c">./scripts/render.sh decks/graphify-talk/index.html</span></div>
<div class="o"> ✔ wrote graphify-talk.png (1920×1080)</div>
<div style="margin-top:12px"><span class="p">$ </span><span class="caret"></span></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/three-column.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Three Column</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Three column">
<p class="kicker">Pillars · 三根支柱</p>
<h2 class="h2">一个 html-ppt,三件装备</h2>
<div class="grid g3 mt-l anim-stagger-list" data-anim-target>
<div class="card"><div style="font-size:40px">🎨</div><h4 class="mt-s">Themes</h4><p class="dim">克制 · 编辑 · 霓虹 · 终端 …… 随场景换装。</p></div>
<div class="card"><div style="font-size:40px">🧱</div><h4 class="mt-s">Layouts</h4><p class="dim">30 种页型,从封面到结语一次给齐。</p></div>
<div class="card"><div style="font-size:40px">✨</div><h4 class="mt-s">Animations</h4><p class="dim">25 个命名动效,每一个都克制、每一个都上台。</p></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/thanks.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Thanks</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active center tc" data-title="Thanks">
<div>
<div class="anim-confetti-burst" style="display:inline-block;padding:40px"></div>
<h1 class="h1 anim-fade-up" data-anim="fade-up" style="font-size:180px;line-height:1"><span class="gradient-text">Thanks</span></h1>
<p class="lede" style="margin:18px auto 0">愿你每一次上台,都少一点紧张,多一点从容。</p>
<div class="row mt-l" style="justify-content:center;gap:32px">
<div class="dim"><b>lewis</b> · sudolewis@gmail.com</div>
<div class="dim">github.com/lewis/html-ppt</div>
<div class="dim">2026 · MIT</div>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/process-steps.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Process Steps</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:22px;margin-top:24px}
.step{position:relative;padding:24px 26px;border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);box-shadow:var(--shadow)}
.step .num{position:absolute;top:-24px;left:22px;width:48px;height:48px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:20px;box-shadow:var(--shadow)}
.step h4{margin:18px 0 8px;font-size:17px}
.step p{font-size:13px;color:var(--text-2);line-height:1.6}
.step .tag{display:inline-block;margin-top:10px;font-size:11px;padding:3px 10px;border-radius:999px;background:var(--surface-2);color:var(--text-3)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Process">
<p class="kicker">How-to · 四步做一个 deck</p>
<h2 class="h2">从零到一,只需要十分钟</h2>
<div class="steps anim-stagger-list" data-anim-target>
<div class="step"><div class="num">1</div><h4>起稿</h4><p>运行 <code>new-deck.sh</code> 创建脚手架,默认 6 页。</p><span class="tag">~30s</span></div>
<div class="step"><div class="num">2</div><h4>选版式</h4><p>从 <code>templates/single-page/</code> 里复制你需要的页型。</p><span class="tag">~2min</span></div>
<div class="step"><div class="num">3</div><h4>写内容</h4><p>替换 demo 数据,保留结构。</p><span class="tag">~6min</span></div>
<div class="step"><div class="num">4</div><h4>换主题</h4><p>按 <b>T</b> 循环看 24 个主题,挑一个定稿。</p><span class="tag">~1min</span></div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/theme-showcase.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"><title>Theme Showcase — html-ppt v2</title>
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
<style>
/* NOTE: theme isolation via <iframe srcdoc> per slide. Each slide loads base.css
+ its OWN theme.css, so pressing → actually shows a different look. */
html,body{background:#0b0c10;color:#e8ebf4}
.deck{background:#0b0c10}
.slide{padding:0;background:transparent}
.theme-frame{
position:absolute;inset:0;width:100%;height:100%;
border:0;background:#fff;
}
.theme-chrome{
position:absolute;top:22px;left:40px;right:40px;display:flex;justify-content:space-between;
font-family:'JetBrains Mono',monospace;font-size:12px;color:#aab0c0;letter-spacing:.12em;
text-transform:uppercase;z-index:20;pointer-events:none;mix-blend-mode:difference;
}
.theme-chrome .name{color:#fff;font-weight:700}
</style>
</head>
<body>
<div class="deck"></div>
<script>
/* 36 themes total (24 v1 + 12 v2). Each rendered in its OWN iframe so CSS is isolated. */
const THEMES = [
['minimal-white', '极简白 · clean restraint'],
['editorial-serif', '杂志衬线 · high editorial'],
['soft-pastel', '马卡龙 · soft pastel'],
['sharp-mono', '黑白高对比 · sharp mono'],
['arctic-cool', '冷色调 · arctic'],
['sunset-warm', '暖色调 · sunset'],
['catppuccin-latte', 'Catppuccin Latte'],
['catppuccin-mocha', 'Catppuccin Mocha'],
['dracula', 'Dracula'],
['tokyo-night', 'Tokyo Night'],
['nord', 'Nord · nordic cool'],
['solarized-light', 'Solarized Light'],
['gruvbox-dark', 'Gruvbox Dark'],
['rose-pine', 'Rose Pine'],
['neo-brutalism', 'Neo-Brutalism'],
['glassmorphism', 'Glassmorphism'],
['bauhaus', 'Bauhaus 几何原色'],
['swiss-grid', 'Swiss Grid'],
['terminal-green', 'Terminal Green'],
['xiaohongshu-white', '小红书白底'],
['rainbow-gradient', 'Rainbow Gradient'],
['aurora', 'Aurora 极光'],
['blueprint', 'Blueprint 蓝图'],
['memphis-pop', 'Memphis Pop'],
/* v2 additions */
['cyberpunk-neon', 'Cyberpunk Neon 霓虹'],
['y2k-chrome', 'Y2K Chrome 镜面'],
['retro-tv', 'Retro TV CRT 扫描线'],
['japanese-minimal', '和风极简 · 朱红'],
['vaporwave', 'Vaporwave 蒸汽波'],
['midcentury', 'Mid-Century Modern'],
['corporate-clean', 'Corporate Clean 商务'],
['academic-paper', '学术白皮书'],
['news-broadcast', 'News Broadcast 新闻'],
['pitch-deck-vc', 'Pitch Deck VC / YC'],
['magazine-bold', 'Magazine Bold 大字杂志'],
['engineering-whiteprint','Engineering Whiteprint']
];
/* The demo content that each iframe will render. Uses tokens only, so it picks up
whatever theme the iframe loads. Self-contained — no external references. */
function demoHTML(themeName, label){
return `<!doctype html><html><head><meta charset="utf-8">
<link rel="stylesheet" href="../assets/fonts.css">
<link rel="stylesheet" href="../assets/base.css">
<link rel="stylesheet" href="../assets/themes/${themeName}.css">
<link rel="stylesheet" href="../assets/animations/animations.css">
<style>
html,body{height:100%}
.wrap{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;
padding:72px 96px;box-sizing:border-box;background:var(--bg);color:var(--text-1);
font-family:var(--font-sans)}
.swatch{display:flex;gap:10px;margin-top:18px}
.swatch div{width:44px;height:44px;border-radius:10px;border:1px solid var(--border)}
.kpi{font-size:54px;font-weight:800;letter-spacing:-.02em}
.mini-code{font-family:var(--font-mono);font-size:11px;background:var(--surface-2);
padding:10px 12px;border-radius:8px;margin:6px 0 0;white-space:pre;color:var(--text-1);
border:1px solid var(--border)}
</style>
</head><body><div class="wrap">
<p class="kicker">Theme · ${themeName}</p>
<h1 class="h1">${label}</h1>
<p class="lede">Same demo slide, different tokens. Background, type, accent, radius, shadow all come from <code>assets/themes/${themeName}.css</code>.</p>
<div class="grid g3 mt-l">
<div class="card">
<h4>Palette · 调色盘</h4>
<p class="dim">accent / accent-2 / accent-3 / text / surface-2</p>
<div class="swatch">
<div style="background:var(--accent)"></div>
<div style="background:var(--accent-2)"></div>
<div style="background:var(--accent-3)"></div>
<div style="background:var(--text-1)"></div>
<div style="background:var(--surface-2)"></div>
</div>
</div>
<div class="card">
<h4>KPI</h4>
<div class="kpi gradient-text">2.4K</div>
<p class="dim">上周月活 · weekly active</p>
</div>
<div class="card">
<h4>Code · 令牌</h4>
<pre class="mini-code">:root {
--accent: /* theme */;
--radius: /* theme */;
}</pre>
</div>
</div>
<div class="row mt-m" style="gap:10px;flex-wrap:wrap">
<span class="pill">#tokens</span>
<span class="pill pill-accent">primary</span>
<span class="pill">#radius</span>
<span class="pill">#shadow</span>
<span class="pill">#type</span>
</div>
</div></body></html>`;
}
(function(){
const deck = document.querySelector('.deck');
THEMES.forEach((t,i)=>{
const s = document.createElement('section');
s.className = 'slide';
s.setAttribute('data-title', t[0]);
const chrome = `<div class="theme-chrome"><span class="name">${String(i+1).padStart(2,'0')} · ${t[0]}</span><span>${i+1}/${THEMES.length}</span></div>`;
const iframe = document.createElement('iframe');
iframe.className = 'theme-frame';
iframe.setAttribute('loading','eager');
// Use src via data URL for the inline demo HTML so relative paths still resolve from the templates/ dir.
// Easier: use srcdoc but set <base href> to the templates dir so relative ../assets paths work.
const base = `<base href="${location.href}">`;
iframe.srcdoc = demoHTML(t[0], t[1]).replace('<head>','<head>'+base);
s.innerHTML = chrome;
s.appendChild(iframe);
deck.appendChild(s);
});
})();
</script>
<script src="../assets/runtime.js"></script>
</body></html>
templates/single-page/timeline.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Timeline</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.tl{position:relative;margin-top:40px}
.tl::before{content:"";position:absolute;left:0;right:0;top:48px;height:2px;background:var(--border)}
.tl .row{display:grid;grid-template-columns:repeat(5,1fr);gap:22px;align-items:start}
.tl .item{position:relative;padding-top:80px;text-align:center}
.tl .dot{position:absolute;top:36px;left:50%;transform:translateX(-50%);width:24px;height:24px;border-radius:50%;background:var(--accent);border:4px solid var(--bg);box-shadow:0 0 0 2px var(--accent)}
.tl .year{font-size:14px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;position:absolute;top:0;left:0;right:0;font-weight:600}
.tl h4{font-size:18px}
.tl p{font-size:13px;color:var(--text-2)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Timeline">
<p class="kicker">Roadmap · 时间线</p>
<h2 class="h2">html-ppt 是怎么长大的</h2>
<div class="tl">
<div class="row anim-stagger-list" data-anim-target>
<div class="item"><div class="year">2025 Q3</div><div class="dot"></div><h4>起源</h4><p>一套个人 reveal.js 模板</p></div>
<div class="item"><div class="year">2025 Q4</div><div class="dot"></div><h4>tokens 化</h4><p>把颜色全部收进 :root</p></div>
<div class="item"><div class="year">2026 Q1</div><div class="dot"></div><h4>Agent 接入</h4><p>开放为 AgentSkill</p></div>
<div class="item"><div class="year">2026 Q2</div><div class="dot"></div><h4>24 themes</h4><p>从克制到霓虹一应俱全</p></div>
<div class="item"><div class="year">2026 Q3</div><div class="dot"></div><h4>渲染管线</h4><p>headless Chrome PNG 出稿</p></div>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/todo-checklist.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Checklist</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
<style>
.todo{max-width:820px;margin-top:26px}
.todo li{list-style:none;display:flex;align-items:flex-start;gap:14px;padding:14px 18px;border-bottom:1px solid var(--border)}
.todo li .b{flex-shrink:0;width:22px;height:22px;border:2px solid var(--border-strong);border-radius:6px;display:flex;align-items:center;justify-content:center;margin-top:2px}
.todo li.done .b{background:var(--good);border-color:var(--good);color:#fff}
.todo li.done .b::after{content:"✓";font-weight:900}
.todo li.done .t{text-decoration:line-through;color:var(--text-3)}
.todo li .t{font-size:18px}
.todo li .tag{margin-left:auto;font-size:12px;color:var(--text-3)}
</style>
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Checklist">
<p class="kicker">Launch · 上线前检查清单</p>
<h2 class="h2">发版前必过的 8 件事</h2>
<ul class="todo anim-stagger-list" data-anim-target>
<li class="done"><span class="b"></span><span class="t">所有 slide 在 Chrome 打开无控制台报错</span><span class="tag">#sanity</span></li>
<li class="done"><span class="b"></span><span class="t">字体回退链路正确(Noto Sans SC)</span><span class="tag">#fonts</span></li>
<li class="done"><span class="b"></span><span class="t">24 个主题各选一页截图比对</span><span class="tag">#themes</span></li>
<li class="done"><span class="b"></span><span class="t">键盘导航全键通</span><span class="tag">#runtime</span></li>
<li><span class="b"></span><span class="t">PDF 打印不跨页裁切</span><span class="tag">#print</span></li>
<li><span class="b"></span><span class="t">render.sh 对每个 showcase 跑通</span><span class="tag">#ci</span></li>
<li><span class="b"></span><span class="t">references/*.md 通读一遍</span><span class="tag">#docs</span></li>
<li><span class="b"></span><span class="t">LICENSE / README 作者信息核对</span><span class="tag">#legal</span></li>
</ul>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/two-column.html
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="utf-8"><title>Two Column</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head><body class="single">
<div class="deck"><section class="slide is-active" data-title="Two column">
<p class="kicker">Pattern · 双栏</p>
<h2 class="h2">概念 <span class="dim2">⟷</span> 示例</h2>
<div class="grid g2 mt-l" style="align-items:start">
<div class="card anim-fade-left" data-anim="fade-left">
<h3>左栏 · 概念</h3>
<p class="dim">一个主题 = 一组 CSS 变量。把颜色、字体、圆角、阴影全部收到 <code>:root</code> 里。</p>
<ul class="mt-m">
<li>— tokens 定义语义,不写具体色值</li>
<li>— base.css 用 tokens 排版</li>
<li>— 每个主题只改变量</li>
</ul>
</div>
<div class="card anim-fade-right" data-anim="fade-right">
<h3>右栏 · 示例</h3>
<pre class="mono" style="font-size:13px;background:var(--surface-2);padding:14px;border-radius:var(--radius-sm);overflow:auto">
:root {
--bg: #fff;
--text-1: #0c0d10;
--accent: #3b6cff;
--radius: 18px;
}
.card {
background: var(--bg);
color: var(--text-1);
border-radius: var(--radius);
}</pre>
</div>
</div>
</section></div>
<script src="../../assets/runtime.js"></script>
</body></html>
templates/single-page/toc.html
<!DOCTYPE html>
<html lang="zh-CN" data-theme="minimal-white">
<head>
<meta charset="utf-8"><title>Table of Contents</title>
<link rel="stylesheet" href="../../assets/fonts.css">
<link rel="stylesheet" href="../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../assets/themes/minimal-white.css">
<link rel="stylesheet" href="../../assets/animations/animations.css">
</head>
<body class="single">
<div class="deck">
<section class="slide is-active" data-title="Contents">
<p class="eyebrow">Contents · 目录</p>
<h2 class="h2">今天的五件事</h2>
<div class="grid g2 mt-l anim-stagger-list" data-anim-target>
<div class="card"><div class="row"><div class="h3 dim2" style="width:56px">01</div><div><h4>为什么需要设计系统</h4><p class="dim">从模板到 token,减少重复。</p></div></div></div>
<div class="card"><div class="row"><div class="h3 dim2" style="width:56px">02</div><div><h4>主题与 tokens</h4><p class="dim">24 个可切换的主题,一键换装。</p></div></div></div>
<div class="card"><div class="row"><div class="h3 dim2" style="width:56px">03</div><div><h4>版式目录</h4><p class="dim">30 种单页类型,覆盖 95% 场景。</p></div></div></div>
<div class="card"><div class="row"><div class="h3 dim2" style="width:56px">04</div><div><h4>入场动效</h4><p class="dim">25 种命名动画,可挑可组合。</p></div></div></div>
<div class="card" style="grid-column:span 2"><div class="row"><div class="h3 dim2" style="width:56px">05</div><div><h4>现场演示 & 问答</h4><p class="dim">打开模板就能讲。</p></div></div></div>
</div>
<div class="deck-footer"><span class="dim2">html-ppt</span><span>目录</span></div>
</section>
</div>
<script src="../../assets/runtime.js"></script>
</body></html>