Skill: lewislulu/html-ppt (version 1.0.0)

--- name: html-ppt description: HTML PPT Studio — author professional static HTML presentations in many styles, layouts, and animations, all driven by templates. Use when the user asks for a presentation, PPT, slides, keynote, deck, slideshow, "幻灯片", "演讲稿", "做一份 PPT", "做一份 slides", a reveal-style HTML deck, a 小红书 图文, or any kind of multi-slide pitch/report/sharing document that should look tasteful and be usable with keyboard navigation. Triggers include keywords like "presentation", "ppt", "slides", "deck", "keynote", "reveal", "slideshow", "幻灯片", "演讲稿", "分享稿", "小红书图文", "talk slides", "pitch deck", "tech sharing", "technical presentation". --- # html-ppt — HTML PPT Studio Author professional HTML presentations as static files. One theme file = one look. One layout file = one page type. One animation class = one entry effect. All pages share a token-based design system in `assets/base.css`. ## Install ```bash npx skills add https://github.com/lewislulu/html-ppt-skill ``` One command, no build. Pure static HTML/CSS/JS with only CDN webfonts. ## What the skill gives you - **36 themes** (`assets/themes/*.css`) — minimal-white, editorial-serif, soft-pastel, sharp-mono, arctic-cool, sunset-warm, catppuccin-latte/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 - **14 full-deck templates** (`templates/full-decks/<name>/`) — complete multi-slide decks with scoped `.tpl-<name>` CSS. 8 extracted from real-world decks (xhs-white-editorial, graphify-dark-graph, knowledge-arch-blueprint, hermes-cyber-terminal, obsidian-claude-gradient, testing-safety-alert, xhs-pastel-card, dir-key-nav-minimal), 6 scenario scaffolds (pitch-deck, product-launch, tech-sharing, weekly-report, xhs-post 3:4, course-module) - **31 layouts** (`templates/single-page/*.html`) with realistic demo data - **27 CSS animations** (`assets/animations/animations.css`) via `data-anim` - **20 canvas FX animations** (`assets/animations/fx/*.js`) via `data-fx` — particle-burst, confetti-cannon, firework, starfield, matrix-rain, knowledge-graph (force-directed), neural-net (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 - **Keyboard runtime** (`assets/runtime.js`) — arrows, T (theme), A (anim), F/S/O - **FX runtime** (`assets/animations/fx-runtime.js`) — auto-inits `[data-fx]` on slide enter, cleans up on leave - **Showcase decks** for themes / layouts / animations / full-decks gallery - **Headless Chrome render script** for PNG export ## When to use Use when the user asks for any kind of slide-based output or wants to turn text/notes into a presentable deck. Prefer this over building from scratch. ## Before you author anything — ALWAYS ask or recommend **Do not start writing slides until you understand three things.** Either ask the user directly, or — if they already handed you rich content — propose a tasteful default and confirm. 1. **Content & audience.** What's the deck about, how many slides, who's watching (engineers / execs / 小红书读者 / 学生 / VC)? 2. **Style / theme.** Which of the 36 themes fits? If unsure, recommend 2-3 candidates based on tone: - Business / investor pitch → `pitch-deck-vc`, `corporate-clean`, `swiss-grid` - Tech sharing / engineering → `tokyo-night`, `dracula`, `catppuccin-mocha`, `terminal-green`, `blueprint` - 小红书图文 → `xiaohongshu-white`, `soft-pastel`, `rainbow-gradient`, `magazine-bold` - Academic / report → `academic-paper`, `editorial-serif`, `minimal-white` - Edgy / cyber / launch → `cyberpunk-neon`, `vaporwave`, `y2k-chrome`, `neo-brutalism` 3. **Starting point.** One of the 14 full-deck templates, or scratch? Point to the closest `templates/full-decks/<name>/` and ask if it fits. If the user's content suggests something obvious (e.g. "我要做产品发布会" → `product-launch`), propose it confidently instead of asking blindly. A good opening message looks like: > 我可以给你做这份 PPT！先确认三件事： > 1. 大致内容 / 页数 / 观众是谁？ > 2. 风格偏好？我建议从这 3 个主题里选一个：`tokyo-night`（技术分享默认好看）、`xiaohongshu-white`（小红书风）、`corporate-clean`（正式汇报）。 > 3. 要不要用我现成的 `tech-sharing` 全 deck 模板打底？ Only after those are clear, scaffold the deck and start writing. ## Quick start 1. **Scaffold a new deck.** From the repo root: ```bash ./scripts/new-deck.sh my-talk open examples/my-talk/index.html ``` 2. **Pick a theme.** Open the deck and press `T` to cycle. Or hard-code it: ```html <link rel="stylesheet" id="theme-link" href="../assets/themes/aurora.css"> ``` Catalog in [references/themes.md](references/themes.md). 3. **Pick layouts.** Copy `<section class="slide">...</section>` blocks out of files in `templates/single-page/` into your deck. Replace the demo data. Catalog in [references/layouts.md](references/layouts.md). 4. **Add animations.** Put `data-anim="fade-up"` (or `class="anim-fade-up"`) on any element. On `<ul>`/grids, use `anim-stagger-list` for sequenced reveals. For canvas FX, use `<div data-fx="knowledge-graph">...</div>` and include `<script src="../assets/animations/fx-runtime.js"></script>`. Catalog in [references/animations.md](references/animations.md). 5. **Use a full-deck template.** Copy `templates/full-decks/<name>/` into `examples/my-talk/` as a starting point. Each folder is self-contained with scoped CSS. Catalog in [references/full-decks.md](references/full-decks.md) and gallery at `templates/full-decks-index.html`. 6. **Render to PNG.** ```bash ./scripts/render.sh templates/theme-showcase.html # one shot ./scripts/render.sh examples/my-talk/index.html 12 # 12 slides ``` ## Authoring rules (important) - **Always start from a template.** Don't author slides from scratch — copy the closest layout from `templates/single-page/` first, then replace content. - **Use tokens, not literal colors.** Every color, radius, shadow should come from CSS variables defined in `assets/base.css` and overridden by a theme. Good: `color: var(--text-1)`. Bad: `color: #111`. - **Don't invent new layout files.** Prefer composing existing ones. Only add a new `templates/single-page/*.html` if none of the 30 fit. - **Respect chrome slots.** `.deck-header`, `.deck-footer`, `.slide-number` and the progress bar are provided by `assets/base.css` + `runtime.js`. - **Keyboard-first.** Always include `<script src="../assets/runtime.js"></script>` so the deck supports ← → / T / A / F / S / O / hash deep-links. - **One `.slide` per logical page.** `runtime.js` makes `.slide.is-active` visible; all others are hidden. - **Supply notes.** Wrap speaker notes in `<div class="notes">…</div>` inside each slide. Press S to open the overlay. ## Writing guide See [references/authoring-guide.md](references/authoring-guide.md) for a step-by-step walkthrough: file structure, naming, how to transform an outline into a deck, how to choose layouts and themes per audience, how to do a Chinese + English deck, and how to export. ## Catalogs (load when needed) - [references/themes.md](references/themes.md) — all 36 themes with when-to-use. - [references/layouts.md](references/layouts.md) — all 31 layout types. - [references/animations.md](references/animations.md) — 27 CSS + 20 canvas FX animations. - [references/full-decks.md](references/full-decks.md) — all 14 full-deck templates. - [references/authoring-guide.md](references/authoring-guide.md) — full workflow. ## File structure ``` html-ppt/ ├── SKILL.md (this file) ├── references/ (detailed catalogs, load as needed) ├── assets/ │ ├── base.css (tokens + primitives — do not edit per deck) │ ├── fonts.css (webfont imports) │ ├── runtime.js (keyboard + presenter + overview + theme cycle) │ ├── themes/*.css (36 token overrides, one per theme) │ └── animations/ │ ├── animations.css (27 named CSS entry animations) │ ├── fx-runtime.js (auto-init [data-fx] on slide enter) │ └── fx/*.js (20 canvas FX modules: particles/graph/fireworks…) ├── templates/ │ ├── deck.html (minimal 6-slide starter) │ ├── theme-showcase.html (36 slides, iframe-isolated per theme) │ ├── layout-showcase.html (iframe tour of all 31 layouts) │ ├── animation-showcase.html (20 FX + 27 CSS animation slides) │ ├── full-decks-index.html (gallery of all 14 full-deck templates) │ ├── full-decks/<name>/ (14 scoped multi-slide deck templates) │ └── single-page/*.html (31 layout files with demo data) ├── scripts/ │ ├── new-deck.sh (scaffold a deck from deck.html) │ └── render.sh (headless Chrome → PNG) └── examples/demo-deck/ (complete working deck) ``` ## Rendering to PNG `scripts/render.sh` wraps headless Chrome at `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`. For multi-slide capture, runtime.js exposes `#/N` deep-links, and render.sh iterates 1..N. ```bash ./scripts/render.sh templates/single-page/kpi-grid.html # single page ./scripts/render.sh examples/demo-deck/index.html 8 out-dir # 8 slides, custom dir ``` ## Keyboard cheat sheet ``` ← → Space PgUp PgDn Home End navigate F fullscreen S speaker notes overlay O slide overview grid T cycle themes (reads data-themes attr) A cycle demo animation on current slide #/N in URL deep-link to slide N ``` ## License & author MIT. Copyright (c) 2026 lewis &lt;[REDACTED_SECRET]&gt;.

path: README.md
content: # html-ppt — HTML PPT Studio

> A world-class AgentSkill for producing professional HTML presentations in
> **36 themes**, **14 full-deck templates**, **31 page layouts**, and
> **47 animations** (27 CSS + 20 canvas FX) — all pure static HTML/CSS/JS, no
> build step.

**Author:** lewis &lt;[REDACTED_SECRET]&gt;
**License:** MIT

![html-ppt — cover with live previews](docs/readme/hero.gif)

> One command installs **36 themes × 20 canvas FX × 31 layouts × 14 full decks = 101 PPT skills**. Every preview above is a live iframe of a real template file rendering inside the deck — no screenshots, no mock-ups.

## 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 |
|---|---|---|
| 🎨 **Themes** | **36** | `assets/themes/*.css` |
| 📑 **Full-deck templates** | **14** | `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`.

![36 themes · 8 of them](docs/readme/themes.png)

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).

![14 full-deck templates](docs/readme/templates.png)

### 14 Full-deck templates

Eight extracted from real-world decks, six 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`

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](docs/readme/layouts.png)

### 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.

![31 layouts auto-cycling through real template files](docs/readme/layouts-live.gif)

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

![47 animations — 27 CSS + 20 canvas FX](docs/readme/animations.png)

### 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                               speaker notes overlay
O                               slide overview grid
T                               cycle themes
A                               cycle a demo animation on current slide
#/N (URL)                       deep-link to slide N
```

## 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 &lt;[REDACTED_SECRET]&gt;.


path: assets/animations/animations.css
content: /* 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}
}


path: assets/animations/fx/_util.js
content: /* 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);
})();


path: assets/animations/fx/chain-react.js
content: (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(); } };
  };
})();


path: assets/animations/fx/confetti-cannon.js
content: (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(); } };
  };
})();


path: assets/animations/fx/constellation.js
content: (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(); } };
  };
})();


path: assets/animations/fx/counter-explosion.js
content: (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); } };
  };
})();


path: assets/animations/fx/data-stream.js
content: (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(); } };
  };
})();


path: assets/animations/fx/firework.js
content: (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(); } };
  };
})();


path: assets/animations/fx/galaxy-swirl.js
content: (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(); } };
  };
})();


path: assets/animations/fx/gradient-blob.js
content: (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(); } };
  };
})();


path: assets/animations/fx/knowledge-graph.js
content: (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(); } };
  };
})();


path: assets/animations/fx/letter-explode.js
content: (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); } };
  };
})();


path: assets/animations/fx/magnetic-field.js
content: (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(); } };
  };
})();


path: assets/animations/fx/matrix-rain.js
content: (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(); } };
  };
})();


path: assets/animations/fx/neural-net.js
content: (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(); } };
  };
})();


path: assets/animations/fx/orbit-ring.js
content: (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(); } };
  };
})();


path: assets/animations/fx/particle-burst.js
content: (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(); } };
  };
})();


path: assets/animations/fx/shockwave.js
content: (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(); } };
  };
})();


path: assets/animations/fx/sparkle-trail.js
content: (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();
    }};
  };
})();


path: assets/animations/fx/starfield.js
content: (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(); } };
  };
})();


path: assets/animations/fx/typewriter-multi.js
content: (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); } };
  };
})();


path: assets/animations/fx/word-cascade.js
content: (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(); } };
  };
})();


path: assets/animations/fx-runtime.js
content: /* 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();
  }
})();


path: assets/base.css
content: /* 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-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)}

/* ================= 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}
}


path: assets/fonts.css
content: /* 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');


path: assets/runtime.js
content: /* html-ppt :: runtime.js
 * Keyboard-driven deck runtime. Zero dependencies.
 *
 * Features:
 *   ← → / space / PgUp PgDn / Home End  navigation
 *   F  fullscreen
 *   S  speaker notes overlay
 *   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);}

  ready(function () {
    const deck = document.querySelector('.deck');
    if (!deck) return;
    const slides = Array.from(deck.querySelectorAll('.slide'));
    if (!slides.length) return;

    let idx = 0;
    const total = slides.length;

    // 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
    let notes = document.querySelector('.notes-overlay');
    if (!notes) {
      notes = document.createElement('div');
      notes.className = 'notes-overlay';
      document.body.appendChild(notes);
    }

    // overview
    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';
        const title = s.getAttribute('data-title') ||
          (s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1));
        t.innerHTML = '<div class="n">'+(i+1)+'</div><div class="t">'+title.trim().slice(0,80)+'</div>';
        t.addEventListener('click', () => { go(i); toggleOverview(false); });
        overview.appendChild(t);
      });
      document.body.appendChild(overview);
    }

    function go(n){
      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
      const note = slides[n].querySelector('.notes');
      notes.innerHTML = note ? note.innerHTML : '';
      // hash
      if (location.hash !== '#/'+(n+1)) history.replaceState(null,'','#/'+(n+1));
      // 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);
      });
    }

    function toggleNotes(force){ notes.classList.toggle('open', force!==undefined?force:!notes.classList.contains('open')); }
    function toggleOverview(force){ overview.classList.toggle('open', force!==undefined?force:!overview.classList.contains('open')); }
    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;
    function cycleTheme(){
      if (!themes.length) return;
      themeIdx = (themeIdx+1) % themes.length;
      const name = themes[themeIdx];
      let link = document.getElementById('theme-link');
      if (!link) {
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.id = 'theme-link';
        document.head.appendChild(link);
      }
      // resolve relative to runtime's location
      const themePath = (root.getAttribute('data-theme-base') || 'assets/themes/') + name + '.css';
      link.href = themePath;
      root.setAttribute('data-theme', name);
      const ind = document.querySelector('.theme-indicator');
      if (ind) ind.textContent = 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': 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);
  });
})();


path: assets/themes/academic-paper.css
content: /* 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}


path: assets/themes/arctic-cool.css
content: /* 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;
}


path: assets/themes/aurora.css
content: /* 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%)}


path: assets/themes/bauhaus.css
content: /* 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}


path: assets/themes/blueprint.css
content: /* 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)}


path: assets/themes/catppuccin-latte.css
content: /* 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;
}


path: assets/themes/catppuccin-mocha.css
content: /* 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;
}


path: assets/themes/corporate-clean.css
content: /* 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)}


path: assets/themes/cyberpunk-neon.css
content: /* 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)}


path: assets/themes/dracula.css
content: /* 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;
}


path: assets/themes/editorial-serif.css
content: /* 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}


path: assets/themes/engineering-whiteprint.css
content: /* 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}


path: assets/themes/glassmorphism.css
content: /* 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)}


path: assets/themes/gruvbox-dark.css
content: /* 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;
}


path: assets/themes/japanese-minimal.css
content: /* 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}


path: assets/themes/magazine-bold.css
content: /* 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}


path: assets/themes/memphis-pop.css
content: /* 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}


path: assets/themes/midcentury.css
content: /* 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)}


path: assets/themes/minimal-white.css
content: /* 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;
}


path: assets/themes/neo-brutalism.css
content: /* 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}


path: assets/themes/news-broadcast.css
content: /* 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}


path: assets/themes/nord.css
content: /* 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;
}


path: assets/themes/pitch-deck-vc.css
content: /* 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}


path: assets/themes/rainbow-gradient.css
content: /* 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}}


path: assets/themes/retro-tv.css
content: /* 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)}


path: assets/themes/rose-pine.css
content: /* 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;
}


path: assets/themes/sharp-mono.css
content: /* 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}


path: assets/themes/soft-pastel.css
content: /* 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;
}


path: assets/themes/solarized-light.css
content: /* 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;
}


path: assets/themes/sunset-warm.css
content: /* 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;
}


path: assets/themes/swiss-grid.css
content: /* 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%}


path: assets/themes/terminal-green.css
content: /* 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)}


path: assets/themes/tokyo-night.css
content: /* 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;
}


path: assets/themes/vaporwave.css
content: /* 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)}


path: assets/themes/xiaohongshu-white.css
content: /* 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;
}


path: assets/themes/y2k-chrome.css
content: /* 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)}


path: docs/readme/_theme-cell.html
content: <!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>


path: docs/readme/montage-animations.html
content: <!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>


path: docs/readme/montage-layouts.html
content: <!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>


path: docs/readme/montage-templates.html
content: <!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>


path: docs/readme/montage-themes.html
content: <!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>


path: examples/demo-deck/index.html
content: <!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 · [REDACTED_SECRET]</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 · [REDACTED_SECRET] · 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&amp;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>


path: references/animations.md
content: # 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.


path: references/authoring-guide.md
content: # 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.

## 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`.


path: references/full-decks.md
content: # 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 |

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.

---

## 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.


path: references/layouts.md
content: # 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.


path: references/themes.md
content: # 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.


path: scripts/new-deck.sh
content: #!/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"


path: scripts/render.sh
content: #!/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"


path: templates/animation-showcase.html
content: <!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>


path: templates/deck.html
content: <!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 · [REDACTED_SECRET]</p>
  </section>
</div>
<script src="../assets/runtime.js"></script>
</body></html>


path: templates/full-decks/course-module/README.md
content: # 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.


path: templates/full-decks/course-module/index.html
content: <!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 &lt; 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 &lt; 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 &amp; 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>


path: templates/full-decks/course-module/style.css
content: /* 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)}


path: templates/full-decks/dir-key-nav-minimal/README.md
content: # 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`


path: templates/full-decks/dir-key-nav-minimal/index.html
content: <!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>


path: templates/full-decks/dir-key-nav-minimal/style.css
content: /* 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}


path: templates/full-decks/graphify-dark-graph/README.md
content: # 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`


path: templates/full-decks/graphify-dark-graph/index.html
content: <!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>


path: templates/full-decks/graphify-dark-graph/style.css
content: /* 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)}


path: templates/full-decks/hermes-cyber-terminal/README.md
content: # 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`


path: templates/full-decks/hermes-cyber-terminal/index.html
content: <!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>


path: templates/full-decks/hermes-cyber-terminal/style.css
content: /* 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}


path: templates/full-decks/knowledge-arch-blueprint/README.md
content: # 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`


path: templates/full-decks/knowledge-arch-blueprint/index.html
content: <!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">—— 数据流 &nbsp;&nbsp; ┈┈ 反馈回路</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>


path: templates/full-decks/knowledge-arch-blueprint/style.css
content: /* 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}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)