Skill: kaisersong/kai-slide-creator (version 2.27.0)

--- name: kai-slide-creator description: 生成HTML演示文稿/幻灯片 — 22 种风格模版，播放/演讲者模式。适用于路演、产品发布、技术分享、方案宣讲等场景。 version: 2.27.0 metadata: {"openclaw":{"emoji":"🎞","os":["darwin","linux","windows"],"homepage":"https://github.com/kaisersong/slide-creator","requires":{"bins":["python3"]},"install":[]}} --- # kai-slide-creator 生成零依赖 HTML 演示文稿，纯浏览器运行。主路径是 IR-first：`user prompt → BRIEF.json → HTML → validate`。 ## 安装 **Claude Code:** 告诉 Claude「安装 https://github.com/kaisersong/slide-creator」 **OpenClaw:** `clawhub install kai-slide-creator` ## 使用方式 `/slide-creator ...` 是 Claude/OpenClaw 的 slash 技能调用，不是 bash 命令。 **规划深度：** - `自动` (Auto) — 快速出稿，约 3-6 分钟 - `精修` (Polish) — 深度规划，约 8-15 分钟，自动执行 Review **内容类型 → 风格推荐：** | 内容类型 | 推荐风格 | |---|---| | 数据报告 / KPI 看板 | Data Story、Enterprise Dark、Swiss Modern | | 商业路演 / VC Deck | Bold Signal、Aurora Mesh、Enterprise Dark | | 产品发布 / SaaS | Blud Sky、Aurora Mesh、Glassmorphism、Electric Studio | | 开发工具 / API 文档 | Terminal Green、Neon Cyber、Neo-Retro Dev Deck | | 研究 / 思想领导力 | Modern Newspaper、Paper & Ink、Swiss Modern | | 创意 / 个人品牌 | Vintage Editorial、Split Pastel、Neo-Brutalism | | 哲学 / 思想 | Chinese chan| | 咨询报告 / 战略方案 | Strategy Consulting、Enterprise Dark、Swiss Modern | ## 命令路由 `自动` (Auto) — 快速出稿 | `精修` (Polish) — 深度规划，自动执行 Review | 命令 | 加载内容 | 行为 | |------|----------|------| | `--plan [prompt]` | `references/brief-template.json`；如用户指定了 custom theme，同步加载 `themes/<name>/reference.md` | 创建 `BRIEF.json`；仅在用户明确要求时额外派生 `PLANNING.md` | | `--generate` | SKILL.md + 已选风格文件（内置 `references/*.md` / `blue-sky-starter.html`；自定义 `themes/<name>/reference.md`）+ composition 源 + `references/title-quality.md` + `references/html-template.md` + `references/js-engine.md` + `references/base-css.md` + `references/impeccable-anti-patterns.md` | 从 `BRIEF.json` 生成 HTML，并执行写入前门禁；如用户显式要求 `eval`，同步产出单 deck 评测 JSON | | `--review [file.html]` | `references/review-checklist.md` + 目标 HTML | 执行 17 项检查点 → 确认窗口 → 修复/报告 | | 无 flag (交互式) | `references/workflow.md` + 其他按需 | 遵循 Phase 0-5 | | 直接给内容 + 风格 | 同 `--generate` | 先落一个有效 `BRIEF.json`，再走同一个 deterministic renderer 与 strict 写入前门禁；禁止手拼最终 HTML | **渐进式披露：** 每个命令只加载所需文件。`--plan` 只提炼 IR，不接触 CSS。 ## 核心规则（按优先级） 1. **风格强制** 所有颜色、字体、组件、背景、动画、图表色、signature elements 都**必须且只能**来自选中的风格文件。模板里的 `[from style file]` 和示例值只是占位，禁止直接使用。 默认推荐面第一阶段聚焦 4 个 production presets：`Swiss Modern / Enterprise Dark / Data Story / Blue Sky`；**但用户显式指定任意当前 preset 时，必须保留该选择**。support tier 只影响默认推荐优先级，不影响显式指定。 Custom theme（`themes/<name>/reference.md`）与内置 preset 同等对待。`BRIEF.json` 的 `style.preset` 支持三种值：内置 preset 名、custom theme 文件夹名、或 `themes/<name>/reference.md` 路径。代码层会自动归一化解析。 **Swiss Modern 额外要求 canonical export path**：面板保持 `.slide` direct child，token 保持 `--bg/--text/--red`，使用 canonical 类（`.left-panel/.right-panel/.stat-row/.cta-block`），并写入 `data-export-role`；不得生成 `.left-col/.right-col` 或 `--bg-primary/--accent` 这类兼容别名。 2. **叙事弧线** 先锁内容结构，再锁 composition route。每页都要有明确 page role、不同布局意图和足够的信息推进；不要先拼播放/编辑壳子，再回头补故事。内部 route 名称属于实现细节，不要挤占用户入口层。 3. **标题质量** 标题必须是断言式，禁止 Overview / Introduction / Summary / 结论 / 概览 这类通用标签。多行标题必须平衡，不能出现孤儿行、塌陷中间行或靠过窄 measure 硬挤换行。规则和示例只看 `references/title-quality.md`。 4. **运行时与壳子基线** 非 Blue Sky 风格必须走共享壳子：`body[data-preset]`、`scroll-snap`、`SlidePresentation`、`PresentMode`、首屏 `.visible` 修复、`data-notes`、shell markers。Blue Sky 是唯一允许保留自身 `#stage/#track` 架构的 preset。 5. **可降级功能** 播放模式、编辑模式、水印都默认开启，但优先级低于风格、叙事和标题。 - 播放模式：默认开启 - 编辑模式：默认开启，**可省略编辑模式**，尤其在用户明确说不要或 existing HTML/导入场景受约束时 - 水印：默认保留，但必须是 JS 注入到最后一页，`position: absolute`，禁止 `position: fixed` 详见 `references/html-template.md`。生成任何 HTML 前必读此文件。 **Direct-route guard:** 无论是 `--generate` 还是“直接给内容 + 风格”，都必须先 materialize 一个有效 `BRIEF.json`，再通过 `render_from_brief()` 同一路径渲染，并在写出最终文件前通过 `python3 scripts/validate_html.py "$TMP_HTML" --strict`。禁止在交互式路径里绕过 BRIEF 直接手写最终 HTML。 **Optional eval artifact:** 当用户显式要求 `eval` / `评测` 时，生成链路应在 strict gate 通过后额外写出单 deck 评测 JSON。原始 CLI 对应 `--eval`（默认写同名 `.eval.json`）或 `--eval-out <path>`；评测至少包含 `style_score (= style_signature_coverage)`、quality gates、hard failures 与关键 diagnostics。 ## Pre-Write Validation Pipeline（写入前门禁） 组装完整 HTML 后，先写入临时文件，再运行： 任一失败都算生成失败，必须修复或重生直到通过。 **核心门禁顺序：** 1. **风格强制 / canonical export** 选中风格的 Signature Elements、Typography、Components、Background 必须完整注入；不得遗漏 checklist 项。Blue Sky 例外：使用 `blue-sky-starter.html` 基底。 2. **叙事弧线 / composition route** 页数、page roles、composition route 必须完整；连续两页不得使用相同布局；每页至少使用 2-3 种组件类型，不得只用 `.g` + `.bl` 堆砌。单页列表项 ≤ 5 条；标题 + 正文段落 + 列表三者不得同时出现在同一页（内容过多时拆页）。 3. **标题质量** 标题规则见 `references/title-quality.md`。 4. **架构隔离 / runtime** 非 Blue Sky 风格必须使用 `scroll-snap-type: y mandatory` + `SlidePresentation`。`#stage/#track`/`translateX` 只允许 Blue Sky。 5. **Preset metadata / shell markers** `body[data-preset]` 必须存在且非占位；写出 `id="slide-N"`、`#brand-mark`、`.nav-dots`、`.progress-bar`、`.slide-num-label`、`data-export-role`。 6. **对比度 / token 使用** 文字对比必须正确；浅底不用浅字，深底不用深字；优先用风格 token，不要发明局部颜色。 7. **字体 / CJK** Google Fonts 合并为单一链接；`<style>` 开头要有 `body { background-color: ... }` 回退；CJK 页面追加对应 `Noto Sans SC/JP/KR`。 8. **CSS 工程** 布局、背景、间距通过 CSS 选择器定义；inline `style=""` ≤ 5 处，目标 0。 9. **低级错误与模板完整性** 禁止 U+FE0F。Blue Sky 模板路径还要检查 REQUIRED BLOCK、`@keyframes`、以及 `go()` 返回 boolean。 > 更多视觉和内容规则在 `--review` / Phase 3.5 Review 中执行。完整反模式映射表见 `references/impeccable-anti-patterns.md`。 ## 风格参考 只读取已选风格的文件： - **内置预设**：完整列表、风格说明和选择建议都在 `references/style-index.md` - **支持层级**：默认推荐面与 support tier 定义在 `references/preset-support-tiers.json` - **内置风格文件位置**：统一在 `references/` 下；Blue Sky 例外，使用 `references/blue-sky-starter.html` - **自定义主题**：`themes/<name>/reference.md` - 风格选择器：`references/style-index.md` - 视口与共享 CSS：`references/base-css.md` - 设计质量基线：`references/design-quality.md` - 标题规则：`references/title-quality.md` - 反模式：`references/impeccable-anti-patterns.md` - `BRIEF.json` 已存在 → 直接读取并作为真相源 - 只有在用户明确要求人工审阅时才派生 `PLANNING.md` - 现有 HTML 增强遵循 `references/workflow.md` 的 Enhancement Mode ## 相关技能 - **kai-html-export** — 导出 PPT（图片模式或 Native 模式）

path: README.md
content: # slide-creator

> You have great content — but great content deserves a great presentation. AI can generate slides, but the results are inconsistent and re-rolling gets old fast. Slide-Creator gives you stable, polished output every time: pick a style that fits your audience, and let the model handle the rest. Go grab a coffee.
>
> **[See the guide as a report →](https://kaisersong.github.io/slide-creator/demos/blue-sky-en.html)** — this document was generated by slide-creator itself.

A skill for [Claude Code](https://claude.ai/claude-code) and [OpenClaw](https://openclaw.ai) that generates stunning, zero-dependency HTML presentations.

English | [简体中文](README.zh-CN.md)

---

## Live Demo

See what slide-creator produces — open directly in your browser:

- [slide-creator intro (English)](https://kaisersong.github.io/slide-creator/demos/blue-sky-en.html)
- [slide-creator 介绍（中文）](https://kaisersong.github.io/slide-creator/demos/blue-sky-zh.html)

Click any screenshot below to open the live demo (same content, different styles):

<table>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/blue-sky-en.html"><img src="demos/screenshots/blue-sky.png" width="240" alt="Blue Sky"/></a><br/><b>Blue Sky</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/bold-signal-en.html"><img src="demos/screenshots/bold-signal.png" width="240" alt="Bold Signal"/></a><br/><b>Bold Signal</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/electric-studio-en.html"><img src="demos/screenshots/electric-studio.png" width="240" alt="Electric Studio"/></a><br/><b>Electric Studio</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/creative-voltage-en.html"><img src="demos/screenshots/creative-voltage.png" width="240" alt="Creative Voltage"/></a><br/><b>Creative Voltage</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/dark-botanical-en.html"><img src="demos/screenshots/dark-botanical.png" width="240" alt="Dark Botanical"/></a><br/><b>Dark Botanical</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/notebook-tabs-en.html"><img src="demos/screenshots/notebook-tabs.png" width="240" alt="Notebook Tabs"/></a><br/><b>Notebook Tabs</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/pastel-geometry-en.html"><img src="demos/screenshots/pastel-geometry.png" width="240" alt="Pastel Geometry"/></a><br/><b>Pastel Geometry</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/split-pastel-en.html"><img src="demos/screenshots/split-pastel.png" width="240" alt="Split Pastel"/></a><br/><b>Split Pastel</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/vintage-editorial-en.html"><img src="demos/screenshots/vintage-editorial.png" width="240" alt="Vintage Editorial"/></a><br/><b>Vintage Editorial</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/neon-cyber-en.html"><img src="demos/screenshots/neon-cyber.png" width="240" alt="Neon Cyber"/></a><br/><b>Neon Cyber</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/terminal-green-en.html"><img src="demos/screenshots/terminal-green.png" width="240" alt="Terminal Green"/></a><br/><b>Terminal Green</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/swiss-modern-en.html"><img src="demos/screenshots/swiss-modern.png" width="240" alt="Swiss Modern"/></a><br/><b>Swiss Modern</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/paper-ink-en.html"><img src="demos/screenshots/paper-ink.png" width="240" alt="Paper & Ink"/></a><br/><b>Paper & Ink</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/aurora-mesh-en.html"><img src="demos/screenshots/aurora-mesh.png" width="240" alt="Aurora Mesh"/></a><br/><b>Aurora Mesh</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/enterprise-dark-en.html"><img src="demos/screenshots/enterprise-dark.png" width="240" alt="Enterprise Dark"/></a><br/><b>Enterprise Dark</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/glassmorphism-en.html"><img src="demos/screenshots/glassmorphism.png" width="240" alt="Glassmorphism"/></a><br/><b>Glassmorphism</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/neo-brutalism-en.html"><img src="demos/screenshots/neo-brutalism.png" width="240" alt="Neo-Brutalism"/></a><br/><b>Neo-Brutalism</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/chinese-chan-en.html"><img src="demos/screenshots/chinese-chan.png" width="240" alt="Chinese Chan"/></a><br/><b>Chinese Chan</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/data-story-en.html"><img src="demos/screenshots/data-story.png" width="240" alt="Data Story"/></a><br/><b>Data Story</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/modern-newspaper-en.html"><img src="demos/screenshots/modern-newspaper.png" width="240" alt="Modern Newspaper"/></a><br/><b>Modern Newspaper</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/neo-retro-dev-en.html"><img src="demos/screenshots/neo-retro-dev.png" width="240" alt="Neo-Retro Dev Deck"/></a><br/><b>Neo-Retro Dev Deck</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/strategy-consulting-en.html"><img src="demos/screenshots/strategy-consulting.png" width="240" alt="Strategy Consulting"/></a><br/><b>Strategy Consulting</b></td>
</tr>
</table>

---

## Design Philosophy: Build for the Real Last Mile

slide-creator is built around one observation: people usually spend a long time generating or discussing content first, then ask for slides at the very end. That is the worst possible moment to depend on raw conversation context. The model is already overloaded, style signals are diluted, and hard constraints get dropped.

The design philosophy of slide-creator is to protect that last mile.

### 1. IR-first workflow, planning is optional

The main workflow is now explicitly **IR-first workflow**:

```
user prompt → BRIEF.json → HTML → validate → eval
```

`--plan` exists to distill a durable `BRIEF.json`, not to force a human review step every time. `PLANNING.md` is optional, a human-readable view when someone explicitly wants to inspect structure before rendering.

This matters because the generator should not carry the full chat history into the render step. `--generate` should execute against a small, hard truth source, not against a messy, late-stage conversation.

The same rule applies to direct prompt generation. "Give content + preset and generate now" is still an IR-first path: materialize `BRIEF.json`, render through the deterministic renderer, then pass the strict pre-write validator before writing the final HTML.

### 2. Public modes stay simple, internal pipeline stays strict

Users should not need to think in six internal phases. The public mental model is deliberately small:

- **Auto** for fast first draft
- **Polish** for the quality-locked path

Internally, the pipeline is stricter than the UI suggests. The real path is style discovery, BRIEF distillation, rendering, validation, and review. Simpler UX outside, stronger contract inside.

This is why the README and the skill keep talking about Auto / Polish, while the repo still maintains explicit routing, review logic, and eval infrastructure.

### 3. Progressive disclosure for model context

A skill file loads into model context on every invocation. That means context is a product surface, not an implementation detail.

slide-creator keeps `SKILL.md` as a thin router and pushes detail into references so each path loads only what it needs:

```
--plan        → references/brief-template.json only
--generate    → references/html-template.md + references/js-engine.md + one style file + base-css.md
interactive   → references/workflow.md
style picker  → references/style-index.md
```

The goal is not elegance for its own sake. It is to reduce context pressure so the model does not forget the important parts right before it renders.

### 4. Show, don't tell, for visual choices

Most users cannot reliably describe a visual direction in abstract language. They can react to concrete options immediately.

That is why slide-creator treats style selection as a preview problem, not a questionnaire problem. Show three strong directions. Let the user point. Then write the decision into `BRIEF.json`.

This is also why style choice belongs before rendering. If style remains a vague instruction until the HTML step, it is too late.

### 5. Zero-dependency runtime is part of the product

The output is not a screenshot, and not a build artifact that still needs another toolchain. The output is a browser-native deck with:

- viewport-fitted slides
- presenter mode
- **Default-on** inline editing
- keyboard navigation
- self-contained runtime

The zero-dependency requirement forces discipline. If a deck only works after a bundler, remote font fetch, or extra runtime glue, the product has already missed its point.

### 6. Validate before trust

The system should catch failure before the user opens a broken deck.

That is why slide-creator is moving quality checks earlier:

- `--plan` creates a structured `BRIEF.json`
- `--generate` renders from the IR instead of from the whole conversation
- `validate-brief.py` checks the brief contract
- `scripts/validate_html.py --strict` checks the runtime contract
- evals score route / compression / render / efficiency

The important design idea here is not "more tests". It is **better failure localization**. If a result is bad, we want to know whether the mistake happened in routing, compression, rendering, or polish. That feedback then improves the skill itself.

**Validation positioning: pre-write gate, not optional review**

validate.py should run inside `--generate`, but after rendering and before the final file is accepted. The right sequence is: assemble HTML → write temp file → run `python3 scripts/validate_html.py "$TMP_HTML" --strict` → fix/regenerate until pass → write final output. If you also want a single-deck eval artifact, add `--eval` or `--eval-out report.json` after the strict gate passes.

This keeps validation out of planning and composition, but inside delivery:
- No extra planning step → no extra LLM cognitive load during ideation
- No extra style-file reads beyond the existing generation inputs
- Hard failures stop bad output before handoff
- Warnings can still feed polish / retry policy without pretending the deck is already valid

**Captured-run skill evals**

`scripts/run_evals.py` checks committed BRIEF/HTML artifacts and deterministic renderer behavior. It is a regression gate, not a full skill eval.

For OpenAI-style captured-run skill evals, use:

```bash
python3 scripts/run-skill-evals.py --runner codex --run-live --format json --json-out .tmp-run/skill-evals/results.json
```

This runs the prompt set in `evals/slide-skill-prompts.csv` through the selected runner, stores raw and normalized traces under `evals/artifacts/current/skill-runs/`, and scores each case across four categories:

- Outcome: deck task completion and valid HTML artifacts.
- Process: skill routing, BRIEF/materialization evidence, reference loading, and strict validation evidence from normalized runner metrics.
- Style: preset fit, slide rhythm, content fidelity, and structured rubric grading for positive captured-run cases.
- Efficiency: command count, repeated failures, token budgets, and wall-clock budget.

The live eval architecture is deliberately split into three roles:

- Supervisor: selects cases, launches isolated workers, captures traces, writes baselines, and compares regressions.
- Generate Worker: one fresh isolated worker per case; reads `SKILL.md`, a small reference set, writes `BRIEF.json`, renders HTML, and runs strict validation.
- Style Judge: a separate grader that produces `style-rubric.json`; the generator does not self-grade style.

All generation-worker shell commands and tokens count toward Efficiency. Subagents/fresh workers are for context isolation, not for hiding cost. The live worker prompt forbids broad repo searches, previous eval traces, existing decks, `tests/`, `demos/`, `evals/baselines/`, CLI discovery, Python introspection, symlinks, and writes outside the per-case artifact directory.

Use fixture mode for deterministic local tests:

```bash
python3 scripts/run-skill-evals.py --runner fixture --case-id explicit-generate --normalized-trace tests/fixtures/skill-evals/explicit-generate-normalized.json --format json
```

`scripts/preset_release_gate.py` keeps captured-run evals optional. Add `--include-skill-evals` for deterministic release checks, or let `--runner codex --run-live` run only during a manual evaluation pass. Positive fixture cases use checked-in `tests/fixtures/skill-evals/*-style-rubric.json`; if a positive case has no rubric, the harness marks it `eval_complete: false` and fails the eval instead of hiding the gap behind a green score.

When `--baseline-dir` contains `skill-evals.json`, the release gate automatically compares the new captured-run result against that baseline and fails on pass, completeness, total score, or category regressions. You can also run the comparator directly:

```bash
python3 scripts/compare-skill-eval-baseline.py \
  --old evals/baselines/2026-05-17/skill-evals/skill-evals.json \
  --new /path/to/candidate/skill-evals.json \
  --format json
```

Before adding any new check, verify: does this check belong in the deterministic runtime gate? If it requires subjective taste judgment rather than contract validation, it should stay in review/eval instead of strict validate.

**Contract alignment: validators must match generation contracts**

validate.py checks must align with actual contracts in SKILL.md / html-template.md / js-engine.md. For example:
- Check hotzone → must use `.edit-hotzone` (class) not `id="hotzone"`
- Check preset metadata → generated decks must emit a real `body[data-preset]`, not omit it or leave the template placeholder
- Check external links → must allow Google Fonts (explicitly required by html-template.md)
- Check watermark → must verify JS injection logic (not hardcoded position)

Contract alignment isn't about doc syncing; it's about script validation: every validate.py change should run against demos to ensure checks match actual generation output.

### 7. Against slide slop, visual and semantic

Most AI slide failures are not dramatic. They are mediocre. Sparse pages, repeated layouts, weak titles, and generic structure. That is what makes decks feel machine-made.

slide-creator treats this as a design problem and a content problem:

- visual density must be intentional
- layout rhythm must change
- titles should carry judgments when the content type calls for it
- numbers should surface early when the material has them
- jargon should be translated for the audience

The point is not to make every deck look busy. It is to avoid accidental emptiness and accidental vagueness.

### 8. Extensible themes, but with a contract

Custom themes are supported, but they are not prompt soup. The contract is explicit:

- create `themes/your-theme/`
- add `reference.md` for the design language
- optionally add `starter.html` for complex systems

This makes themes composable and reviewable. A theme is not just "use our brand colors". It is a reusable rendering contract.

### 9. Content-type routing is a quality feature

22 presets are useful only if the system helps users start in the right neighborhood.

That is why slide-creator routes by content type:

```
Data report / KPI dashboard → Data Story, Enterprise Dark, Swiss Modern
Business pitch / VC deck    → Bold Signal, Aurora Mesh, Enterprise Dark
Developer tool / API docs   → Terminal Green, Neon Cyber, Neo-Retro Dev Deck
Consulting / strategy       → Strategy Consulting, Enterprise Dark, Swiss Modern
```

Good defaults reduce rework. In practice, that means fewer bad first drafts, fewer style resets, and less wasted context.

Phase 1 recommendation surface is intentionally narrower than the full preset library:

- `Swiss Modern`
- `Enterprise Dark`
- `Data Story`
- `Blue Sky`

This does **not** remove the other presets. Explicit preset selection still wins; the narrower surface only affects defaults and recommendation priority.

---

## Install

### Claude Code

Tell Claude: "Install https://github.com/kaisersong/slide-creator"

Or manually:
```bash
git clone https://github.com/kaisersong/slide-creator ~/.claude/skills/slide-creator
```

Restart Claude Code. Use as `/slide-creator`.

### OpenClaw

```bash
# Via ClawHub (recommended)
clawhub install kai-slide-creator

# Or manually
git clone https://github.com/kaisersong/slide-creator ~/.openclaw/skills/slide-creator
```

> ClawHub page: https://clawhub.ai/skills/kai-slide-creator

---

## Usage

### Commands

```
/slide-creator --plan       # Analyze content + resources/, create BRIEF.json
/slide-creator --generate   # Generate HTML from BRIEF.json
/slide-creator --review     # Diagnose and fix content quality issues
/slide-creator              # Start from scratch (interactive style discovery)
/kai-html-export            # Export to PPTX or PNG (separate skill)
```

### Bare Sandbox Fallback

`/slide-creator ...` is a Claude/OpenClaw slash-skill call, not a raw bash or python command.

If you are in a bare sandbox or external agent runner:

```bash
python3 main.py --validate-brief --brief BRIEF.json
python3 main.py --generate --brief BRIEF.json --output presentation.html
python3 main.py --generate --brief BRIEF.json --output presentation.html --eval
```

Built-in presets still load from `references/` / `references/style-index.md`; `themes/<name>/reference.md` is only for custom themes.

### Planning Depths

- **Auto** — fast draft; skips Phase 3.5 Review
- **Polish** — deeper path; auto-runs Phase 3.5 Review

Same content switching between Auto/Polish should keep the same preset unless user explicitly requests a style change.

### Typical Workflows

**Interactive creation:**
1. Run `/slide-creator`, answer four questions (purpose, length, content, images)
2. See 3 style previews, pick one
3. Generate full deck, open in browser

**IR-first workflow (complex content):**
1. Put assets in `resources/` folder
2. Run `/slide-creator --plan "My AI startup pitch deck"`
3. Inspect `BRIEF.json`; only ask for `PLANNING.md` if a human review view is needed
4. Run `/slide-creator --generate`

**PPT conversion:**
1. Put `.pptx` file in current directory
2. Run `/slide-creator` — skill auto-detects and extracts content

### Review Mode

```
/slide-creator --review presentation.html
```

**Behavior:**
1. Load `references/review-checklist.md`
2. Execute 16 checkpoints (6 auto-detect + 10 AI-advised)
3. Show results: ✅ passed / 🔧 auto-fixable / ⚠️ needs confirmation / ❌ needs judgment
4. User chooses: [Auto-fix all] / [Confirm each] / [Skip]
5. Output fixed HTML + diagnostic report

**Polish mode:** Phase 3.5 Review runs automatically after generation.
**Auto mode:** Skips Phase 3.5.

### Timing

Expected end-to-end:

- **Auto:** ~3–6 minutes
- **Polish:** ~8–15 minutes

Tracked segments: `plan`, `generate`, `validate`, `polish`, `total`

---

## Features

### Core

- **IR-first workflow** — `--plan` distills `BRIEF.json`, `--generate` renders from the IR
- **Two planning depths** — Auto for speed, Polish for narrative and visual locking
- **Content Review System** — 16 checkpoints: `--review` for on-demand diagnosis; Polish auto-runs review; three rule types (hard/context-aware/advisory)
- **22 design presets** — each with named layout variations
- **Content-type routing** — auto-suggests best style for pitch decks, dev tools, data reports
- **Style discovery** — generate 3 visual previews before committing
- **Inline SVG diagrams** — flowcharts, timelines, bar charts, comparison grids, org charts — no external libs
- **Blue Sky starter template** — complete boilerplate so models never mis-implement the visual system

### Interaction

- **Play Mode** — Press `F5` or click ▶ (bottom-right) for fullscreen; slides scale to any screen; controls auto-hide; `Esc` to exit
- **Presenter Mode** — Press `P` for synced speaker window: notes, timer, slide counter, prev/next nav; height auto-adapts
- **Notes editing panel** — In edit mode (`E`), notes bar at bottom; click title bar to collapse/expand; edits sync live
- **Inline editing** — Default-on browser editing; edit text in-browser, `Ctrl+S` to save
- **Viewport fitting** — Every slide fits 100vh exactly, no scrolling

### Output

- **Custom theme system** — Drop `reference.md` in `themes/your-theme/` to add preset; `starter.html` optional for complex systems
- **Template export chrome switch** — Set `data-export-progress="false"` on `<body>` to hide progress bar and nav dots
- **Image pipeline** — Auto-evaluate and process assets (Pillow)
- **PPT import** — Convert `.pptx` to web presentations
- **PPTX / PNG export** — via [kai-html-export](https://github.com/kaisersong/kai-html-export)
- **Bilingual** — Chinese / English support

---

## Design Presets

| Preset | Vibe | Best For |
|--------|------|----------|
| **Bold Signal** | Confident, high-impact | Pitch decks, keynotes |
| **Electric Studio** | Clean, professional | Agency presentations |
| **Creative Voltage** | Energetic, retro-modern | Creative pitches |
| **Dark Botanical** | Elegant, sophisticated | Premium brands |
| **Blue Sky** | Airy, enterprise SaaS | Product launches, tech decks |
| **Notebook Tabs** | Editorial, organized | Reports, reviews |
| **Pastel Geometry** | Friendly, approachable | Product overviews |
| **Split Pastel** | Playful, modern | Creative agencies |
| **Vintage Editorial** | Witty, personality-driven | Personal brands |
| **Neon Cyber** | Futuristic, techy | Tech startups |
| **Terminal Green** | Developer-focused | Dev tools, APIs |
| **Swiss Modern** | Minimal, precise | Corporate, data |
| **Paper & Ink** | Literary, thoughtful | Storytelling |
| **Aurora Mesh** | Vibrant, premium SaaS | Product launches, VC pitch |
| **Enterprise Dark** | Authoritative, data-driven | B2B, investor decks, strategy |
| **Glassmorphism** | Light, translucent, modern | Consumer tech, brand launches |
| **Neo-Brutalism** | Bold, uncompromising | Indie dev, creative manifesto |
| **Chinese Chan** | Still, contemplative | Design philosophy, brand, culture |
| **Data Story** | Clear, precise, persuasive | Business review, KPI, analytics |
| **Modern Newspaper** | Punchy, authoritative, editorial | Business reports, thought leadership |
| **Neo-Retro Dev Deck** | Opinionated, technical, handmade | Dev tool launches, API docs, hackathons |
| **Strategy Consulting** | Structured, authoritative, clean | Consulting reports, strategy decks, due diligence |

### Blue Sky

Light sky-blue gradient (`#f0f9ff → #e0f2fe`) with floating glassmorphism cards and animated ambient orbs. Inspired by a real enterprise AI pitch deck (CloudHub V12 MVP). Feels like a high-altitude clear day: open, confident, premium.

Signature elements: grainy noise texture overlay · 3 animated blur orbs repositioning per slide · glassmorphism cards with `backdrop-filter: blur(24px)` · 40px tech grid with radial mask · spring-physics horizontal transitions · cloud hero effect on title slides.

**Why Blue Sky is the starter template:** It demonstrates all 10 signature visual elements pre-built. Models only fill in content — no risk of mis-implementing the design system. This pattern (`reference.md` + `starter.html`) is reusable for any complex theme.

---

## Creating Custom Themes

1. Create `themes/your-theme/` directory
2. Write `reference.md` describing:
   - Colors (primary, accent, neutrals)
   - Typography (fonts, weights, sizes)
   - Layout patterns (cards, grids, full-bleed)
   - Component classes (if custom CSS needed)
3. Optionally add `starter.html` for complex visual systems (animated backgrounds, custom JS)

Your theme appears as "Custom: your-theme" in the style picker.

**Example brand themes bundled:** `themes/cloudhub/` and `themes/kingdee/`

---

## Brand Style Migration

Migrate existing `.pptx` to custom brand design — get pixel-perfect archive + editable version.

```bash
# Step 1 — re-style
/slide-creator --plan "migrate company-deck.pptx to our brand style"
/slide-creator --generate  # → branded-deck.html

# Step 2 — export both modes
/kai-html-export branded-deck.html              # pixel-perfect
/kai-html-export --pptx --mode native branded-deck.html  # editable
```

---

## Requirements

slide-creator has **no external dependencies**. Python 3 is optional for image evaluation — no packages required.

For PPTX/PNG export: `clawhub install kai-html-export` or `pip install playwright python-pptx`

---

## Output

- `presentation.html` — single-file, zero dependencies, runs in browser
- `PRESENTATION_SCRIPT.md` — speaker notes (auto-generated if ≥8 slides)

---

## Compatibility

| Platform | Version | Install path |
|---------|---------|-------------|
| Claude Code | any | `~/.claude/skills/slide-creator/` |
| OpenClaw | ≥ 0.9 | `~/.openclaw/skills/slide-creator/` |

---

## Version History

**v2.27.0** — Captured-run eval architecture release: added OpenAI-style skill eval prompts, normalized trace scoring, fixture style rubrics, live Codex baselines, regression comparison, and optional release-gate integration. README and design docs now document the Supervisor / Generate Worker / Style Judge split, context isolation rules, and token accounting policy so live eval cost remains visible instead of hidden by subagents.

**v2.26.0** — Blue Sky deterministic renderer and custom theme release: Blue Sky now renders through the canonical BRIEF pipeline with strict validation coverage, custom themes resolve correctly in both source and plugin layouts, and private Kingdee / Cloudhub theme assets were cleaned up, compressed, and locked with regression tests.

**v2.25.0** — Strategy Consulting preset: new consulting-style design with 12 canonical layouts (exec summary, before/after, three things, funnel, framework matrix, quote + evidence, driver breakdown, etc.), white-background navy-accent visual language inspired by MBB slide decks. Preset count now 22.

**v2.24.3** — Single-deck eval and production verification release: `--generate` / `render-from-brief` can now emit per-deck eval JSON via `--eval` or `--eval-out`, Data Story chart routing/label logic now fails closed on weak numeric signals instead of drawing fake charts, and production demo fixtures were revalidated and resynced to the current watermark version so core preset verification remains green.

**v2.24.2** — Enterprise Dark / Chinese Chan chrome hardening: validator now rejects hidden-brand regressions for presets that must suppress `#brand-mark`, and watermark checks now fail stale version/preset strings so non-canonical shell output cannot pass `--strict` with leaked skill names or outdated credits.

**v2.24.1** — Direct-route contract and pre-write gate fix: forced direct prompt generation back through `BRIEF.json -> render_from_brief()`, made both CLI render entrypoints run `scripts/validate_html.py --strict` before writing output, and tightened the SKILL/workflow/doc contracts plus regression tests so non-canonical Chinese Chan HTML can no longer slip through a current-version generation path.

**v2.24.0** — Core preset hardening and quality-gate release: added preset support tiers plus manifest-driven eval/release-gate tooling, upgraded low-context BRIEF semantics and preset usage rules, repaired Chinese Chan formal contracts and production/shared runtime drift, modernized family demo runtimes, moved the canonical HTML validator to `scripts/validate_html.py` with a compatibility wrapper, and tightened Swiss Modern/shared shell chrome so nav dots and page-number treatments render correctly across presets.

**v2.23.2** — Sandbox entrypoint and skill-surface patch: added root `main.py` plus `slide-creator` wrapper for bare-sandbox BRIEF validation/rendering, made `--plan` fail with a clear slash-skill boundary instead of a misleading runtime error, restored `SKILL.md`'s user-facing style recommendation surface, and clarified that built-in presets live under `references/` while `themes/<name>/reference.md` is only for custom themes.

**v2.23.1** — Enterprise Dark runtime stability patch: fixed shared js-engine active-slide reveal toggling, hid editing chrome by default, replaced unresolved watermark tokens with real version/preset metadata, stabilized one-gesture-per-page wheel pagination for scroll-snap decks, and corrected Enterprise Dark narrative cover routing, split-title clipping, governance/table rhythm, and subtle grid overlay balance.

**v2.23.0** — Title composition and low-context quality release: added a preset-aware title profile registry plus browser-level title QA, expanded low-context diagnostics/eval buckets for quality uplift checks, hardened strict gates around shared runtime and `body[data-preset]`, and reordered `SKILL.md` so style enforcement, narrative arc, and title quality take priority over degradable playback/edit/watermark features.

**v2.22.0** — Style reference rollout and strict quality gate hardening: all presets now pass style-reference audit, Swiss Modern plus Enterprise Dark / Data Story / Glassmorphism / Chinese Chan gain explicit canonical export contracts and user-content routing, `tests/validate.py --strict` is documented and tested as the `--generate` pre-write gate, and new regression coverage locks CSS variable resolution, layout variety, and priority preset contract checks.

**v2.19.0** — IR-first release: `BRIEF.json` becomes the primary truth source, `PLANNING.md` is optional human view, late-context eval fixtures added under `evals/generated-decks/`, README design philosophy rewritten around prompt → BRIEF → HTML → validate → eval, and stronger regression coverage added for the new contract.

**v2.18.1** — Paper & Ink style fix: restored correct editorial style reference (Cormorant Garamond headlines, Source Serif 4 body, crimson ornamental rules, drop caps); added `.slide-content` wrapper to slide HTML structure for proper vertical centering; Chinese text font fallback now uses system serif (宋体) instead of Noto Sans SC.

**v2.18.0** — JS engine extraction (html-template.md 557→222 lines); style signature injection expanded to require all CSS classes from Typography/Components sections; neon-cyber glow effects clarified; style consistency audit tool (`tests/audit_style_consistency.py`).

**v2.17.0** — Style reference system overhaul; contrast fix for light backgrounds; glassmorphism text theme mapping.

**v2.12.0**

**v2.11.0** — Hard rules embedded in SKILL.md; `--generate` flow restructured with explicit validation.

**v2.9.0** — Content Review System: 16 checkpoints (6 auto-detectable, 10 AI-advised); Phase 3.5 Review in Polish mode; `--review` command for on-demand diagnosis; three rule types (hard/context-aware/advisory).

**v2.8.0** — Simplified planning depths (Auto / Polish); bilingual naming; timing guidance; preset-lock across depths; checked-in demo paths; regression coverage.

**v2.7.1** — Zero-dependency `check-doc-sync.py` contract checker for SKILL.md/README/workflow.md sync; regression test integration.

**v2.7.0** — Enhancement Mode guardrails for editing existing HTML decks; inline editing default-on but optional; bundled brand theme examples (`themes/cloudhub/`, `themes/kingdee/`).

**v2.6.1** — Brand Style Migration workflow documentation.

**v2.6.0** — Design Quality Baseline (`references/design-quality.md`): minimum 65% fill, multi-column balance, 90/8/2 color law, no 3 consecutive bullet slides, content-tone color calibration, pre-output self-check. Fixed aurora-mesh Inter font contradiction (Space Grotesk + DM Sans).

**v2.5.0** — 21 presets with Blue Sky starter template; Show Don't Tell style discovery.

**v2.0.0** — Two-stage workflow (`--plan` / `--generate`); inline editing; presenter mode.

**v1.0.0** — Initial release with 10 presets.


path: README.zh-CN.md
content: # slide-creator

> 很多人有很好的内容，却无法有效地展现。虽然大模型现在能帮你写 PPT，但输出效果不稳定，多次抽卡又很头疼。Slide-Creator 帮助你简单、稳定地输出演示文稿——根据场景选择喜欢的风格即可，其他的就让大模型去干，喝杯咖啡吧。
>
> **[看这份指南本身生成的报告 →](https://kaisersong.github.io/slide-creator/demos/blue-sky-zh.html)** — 本文档由 slide-creator 自己生成。

适用于 [Claude Code](https://claude.ai/claude-code) 和 [OpenClaw](https://openclaw.ai) 的演示文稿生成技能，零依赖、纯浏览器运行的 HTML 幻灯片。

[English](README.md) | 简体中文

---

## 效果展示

用浏览器直接打开，零安装查看效果：

- 🇨🇳 [slide-creator 介绍（中文）](https://kaisersong.github.io/slide-creator/demos/blue-sky-zh.html)
- 🇺🇸 [slide-creator intro (English)](https://kaisersong.github.io/slide-creator/demos/blue-sky-en.html)

点击下方任意截图可打开对应的在线演示（内容相同，风格不同）：

<table>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/blue-sky-zh.html"><img src="demos/screenshots/blue-sky.png" width="240" alt="Blue Sky"/></a><br/><b>Blue Sky</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/bold-signal-zh.html"><img src="demos/screenshots/bold-signal.png" width="240" alt="Bold Signal"/></a><br/><b>Bold Signal</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/electric-studio-zh.html"><img src="demos/screenshots/electric-studio.png" width="240" alt="Electric Studio"/></a><br/><b>Electric Studio</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/creative-voltage-zh.html"><img src="demos/screenshots/creative-voltage.png" width="240" alt="Creative Voltage"/></a><br/><b>Creative Voltage</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/dark-botanical-zh.html"><img src="demos/screenshots/dark-botanical.png" width="240" alt="Dark Botanical"/></a><br/><b>Dark Botanical</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/notebook-tabs-zh.html"><img src="demos/screenshots/notebook-tabs.png" width="240" alt="Notebook Tabs"/></a><br/><b>Notebook Tabs</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/pastel-geometry-zh.html"><img src="demos/screenshots/pastel-geometry.png" width="240" alt="Pastel Geometry"/></a><br/><b>Pastel Geometry</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/split-pastel-zh.html"><img src="demos/screenshots/split-pastel.png" width="240" alt="Split Pastel"/></a><br/><b>Split Pastel</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/vintage-editorial-zh.html"><img src="demos/screenshots/vintage-editorial.png" width="240" alt="Vintage Editorial"/></a><br/><b>Vintage Editorial</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/neon-cyber-zh.html"><img src="demos/screenshots/neon-cyber.png" width="240" alt="Neon Cyber"/></a><br/><b>Neon Cyber</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/terminal-green-zh.html"><img src="demos/screenshots/terminal-green.png" width="240" alt="Terminal Green"/></a><br/><b>Terminal Green</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/swiss-modern-zh.html"><img src="demos/screenshots/swiss-modern.png" width="240" alt="Swiss Modern"/></a><br/><b>Swiss Modern</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/paper-ink-zh.html"><img src="demos/screenshots/paper-ink.png" width="240" alt="Paper & Ink"/></a><br/><b>Paper & Ink</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/aurora-mesh-zh.html"><img src="demos/screenshots/aurora-mesh.png" width="240" alt="Aurora Mesh"/></a><br/><b>Aurora Mesh</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/enterprise-dark-zh.html"><img src="demos/screenshots/enterprise-dark.png" width="240" alt="Enterprise Dark"/></a><br/><b>Enterprise Dark</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/glassmorphism-zh.html"><img src="demos/screenshots/glassmorphism.png" width="240" alt="Glassmorphism"/></a><br/><b>Glassmorphism</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/neo-brutalism-zh.html"><img src="demos/screenshots/neo-brutalism.png" width="240" alt="Neo-Brutalism"/></a><br/><b>Neo-Brutalism</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/chinese-chan-zh.html"><img src="demos/screenshots/chinese-chan.png" width="240" alt="Chinese Chan"/></a><br/><b>Chinese Chan</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/data-story-zh.html"><img src="demos/screenshots/data-story.png" width="240" alt="Data Story"/></a><br/><b>Data Story</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/modern-newspaper-zh.html"><img src="demos/screenshots/modern-newspaper.png" width="240" alt="Modern Newspaper"/></a><br/><b>Modern Newspaper</b></td>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/neo-retro-dev-zh.html"><img src="demos/screenshots/neo-retro-dev.png" width="240" alt="Neo-Retro Dev Deck"/></a><br/><b>Neo-Retro Dev Deck</b></td>
</tr>
<tr>
<td align="center"><a href="https://kaisersong.github.io/slide-creator/demos/strategy-consulting-zh.html"><img src="demos/screenshots/strategy-consulting.png" width="240" alt="Strategy Consulting"/></a><br/><b>Strategy Consulting</b></td>
</tr>
</table>

---

## 设计理念：为真实的最后一步而设计

slide-creator 的出发点很具体：用户通常会先花很长时间生成内容、讨论结构，最后才说一句“帮我做成 slide”。这恰好是最容易翻车的时刻。上下文已经很长，风格信号被稀释，硬约束也最容易丢。

所以 slide-creator 的设计目标不是“会做 slide”，而是**保护最后这一步**。

### 一、IR-first，planning 退居可选

现在的主流程明确是 **IR-first 工作流**：

```
user prompt → BRIEF.json → HTML → validate → eval
```

`--plan` 的默认职责，是提炼可执行的 `BRIEF.json`，而不是强制先走一遍人工审阅。`PLANNING.md` 仍然保留，但定位已经变成“需要人看时才派生的人类可读视图”。

原因很简单：真正生成 HTML 时，不应该继续背整段聊天记录，而应该只背一个短、硬、结构化的真相源。

这条规则同样适用于“直接给内容 + 风格，立刻生成”。这类请求也必须先 materialize `BRIEF.json`，再走 deterministic renderer，最后通过 strict 写入前门禁，不能在交互路径里直接手拼最终 HTML。

### 二、公开模式尽量简单，内部链路必须严格

对用户来说，公开心智模型应该尽量小：

- **Auto**，先出第一版
- **Polish**，把质量锁住

但内部流程不能因此变松。真正的路径仍然是风格发现、BRIEF 提炼、渲染、校验、review。外部更简单，内部更严格。

这也是为什么 README 和技能对外强调 Auto / Polish，而仓库内部仍然维护清晰的路由、review 逻辑和 eval 资产。

### 三、渐进式披露，不浪费模型上下文

技能文件每次调用都会进入模型上下文，所以“上下文预算”本身就是产品面。

slide-creator 把 `SKILL.md` 保持成一个薄路由层，把细节下沉到 references，让每条路径只加载当前需要的内容：

```
--plan        → 只读 references/brief-template.json
--generate    → references/html-template.md + references/js-engine.md + 单个风格文件 + base-css.md
交互模式      → references/workflow.md
风格选择      → references/style-index.md
```

这不是为了形式上的优雅，而是为了减少上下文压力，避免模型在真正渲染前把最重要的约束忘掉。

### 四、视觉选择必须“先看图，再落字”

大多数用户无法稳定地用抽象词描述自己想要的风格，但他们看到方案后会立刻知道喜欢什么。

所以 slide-creator 把风格选择看成“预览问题”，而不是“问卷问题”。先给 3 个强烈不同的方向，让用户选。然后再把选择写进 `BRIEF.json`。

风格如果一直停留在模糊描述，拖到 HTML 生成阶段才真正决定，就已经太晚了。

### 五、零依赖运行时，本身就是产品的一部分

输出结果不是截图，也不是还要交给另一套工具链继续处理的中间产物。输出本身就是浏览器原生 deck，包含：

- 视口适配的幻灯片
- 演讲者模式
- **Default-on** 浏览器内编辑
- 键盘导航
- 自包含运行时

零依赖约束会逼出纪律。如果一个 deck 必须依赖 bundler、远程字体或额外运行时胶水才能正常工作，那产品意义已经打折。

### 六、先验证，再相信结果

系统应该在用户打开坏 deck 之前就发现问题。

这就是为什么 slide-creator 把质量检查不断前移：

- `--plan` 先产出结构化 `BRIEF.json`
- `--generate` 从 IR 生成，而不是从整段对话硬生成
- `validate-brief.py` 校验 brief 契约
- `scripts/validate_html.py --strict` 校验运行时契约
- eval 按 route / compression / render / efficiency 四层打分

这里最重要的设计思想不是”多写一点测试”，而是**更准确地定位失败发生在哪一层**。这样坏结果才能反过来推动 skill 本身变好。

**验证的定位：写入前门禁，不是可选复查**

validate.py 应当运行在 `--generate` 内部，但位置是在渲染完成之后、最终文件被接受之前。正确顺序是：组装 HTML → 写入临时文件 → 运行 `python3 scripts/validate_html.py "$TMP_HTML" --strict` → 修复或重生直到通过 → 再写入最终输出。如果还要单 deck 评测产物，就在 strict gate 通过后追加 `--eval` 或 `--eval-out report.json`。

这样做的含义是：
- 不增加规划阶段步骤 → 不增加构思时的 LLM 认知负担
- 不增加额外 style file 读取 → 仍然复用现有生成输入
- 硬失败直接拦住坏产物 → 不再把失败 deck 当成功交付
- warning 仍可进入 polish / retry 策略，但不能冒充“已经有效”

**捕获运行轨迹的 Skill Evals**

`scripts/run_evals.py` 检查仓库内已有的 BRIEF/HTML 工件和确定性 renderer 行为。它是回归门禁，不是完整的 skill eval。

按 OpenAI eval-skills 的四类目标评测时，使用：

```bash
python3 scripts/run-skill-evals.py --runner codex --run-live --format json --json-out .tmp-run/skill-evals/results.json
```

该命令会读取 `evals/slide-skill-prompts.csv`，通过选定 runner 运行提示词，保存原始 trace 和归一化 metrics，并按四类目标评分：

- Outcome：deck 任务是否完成，HTML 工件是否有效。
- Process：是否按 skill 路由、BRIEF 物化、reference 加载和 strict validation 流程执行。
- Style：是否符合 preset、版式节奏、内容保真，以及正向 captured-run case 的结构化 rubric 评分。
- Efficiency：命令数量、重复失败、token 预算和总耗时。

live eval 架构刻意拆成三类角色：

- Supervisor：选择 case、启动隔离 worker、捕获 trace、写入 baseline，并做回归比较。
- Generate Worker：每个 case 一个 fresh isolated worker；只读 `SKILL.md` 和少量必要 reference，写 `BRIEF.json`，渲染 HTML，并运行 strict validation。
- Style Judge：独立生成 `style-rubric.json`；generator 不自评 style。

Generate Worker 的 shell command 和 token 全部计入 Efficiency。subagent / fresh worker 只用于隔离上下文，不能用来隐藏成本。live worker prompt 明确禁止 broad repo search、previous eval trace、existing deck、`tests/`、`demos/`、`evals/baselines/`、CLI discovery、Python introspection、symlink，以及写入 per-case artifact 目录以外的位置。

本地单元测试使用 normalized fixture trace，不会真实调用任何 agent：

```bash
python3 scripts/run-skill-evals.py --runner fixture --case-id explicit-generate --normalized-trace tests/fixtures/skill-evals/explicit-generate-normalized.json --format json
```

`scripts/preset_release_gate.py` 中 captured-run eval 默认不启用。需要确定性发版检查时加 `--include-skill-evals`；只有手动评测才使用 `--runner codex --run-live`。正向 fixture case 使用仓库内的 `tests/fixtures/skill-evals/*-style-rubric.json`，如果缺 rubric，harness 会把 case 标成 `eval_complete: false` 并失败，不再用绿色分数掩盖覆盖缺口。

当 `--baseline-dir` 下存在 `skill-evals.json` 时，release gate 会自动比较新的 captured-run 结果和 baseline；如果 pass 状态、完整性、总分或分类分数退化，会直接失败。也可以单独运行 comparator：

```bash
python3 scripts/compare-skill-eval-baseline.py \
  --old evals/baselines/2026-05-17/skill-evals/skill-evals.json \
  --new /path/to/candidate/skill-evals.json \
  --format json
```

每次新增检查前，都要先判断：它是不是属于确定性的运行时门禁？如果本质上是主观审美判断，而不是契约校验，就应留在 review/eval，不要塞进 strict validate。

**契约对齐：验证脚本必须与生成契约一致**

validate.py 的检查项必须与 SKILL.md / html-template.md / js-engine.md 的实际契约保持一致。例如：
- 检查 hotzone → 必须用 `.edit-hotzone`（class）而不是 `id=”hotzone”`
- 检查 preset metadata → 生成结果必须写出真实的 `body[data-preset]`，不能省略，也不能保留模板占位值
- 检查外部链接 → 必须允许 Google Fonts（html-template.md 明确要求）
- 检查水印 → 必须验证 JS 注入逻辑（而不是硬编码位置）

契约对齐不是靠文档同步，而是靠脚本实测：每次改动 validate.py，都要跑一遍 demo，确保检查项真的匹配生成输出。

### 七、反对 slide slop，既反对视觉烂稿，也反对内容烂稿

AI 幻灯片最常见的问题不是明显报错，而是平庸。页面太空、布局重复、标题没判断、结构泛泛。这些最容易让结果看起来像“机器做的”。

slide-creator 把它同时当成设计问题和内容问题来处理：

- 视觉密度必须是刻意的
- 版式节奏必须变化
- 该用判断句标题时就不能偷懒用名词短语
- 有数字时要尽量前置
- 有术语时要翻译给目标受众

目标不是让每一页都很满，而是避免“意外的空”和“意外的虚”。

### 八、自定义主题可以扩展，但必须有契约

支持自定义主题，不代表允许 prompt soup。

theme 的约束是明确的：

- 创建 `themes/your-theme/`
- 用 `reference.md` 描述视觉语言
- 复杂主题再附带 `starter.html`

这样 theme 才是可组合、可审查、可复用的渲染契约，而不只是“帮我套一下品牌色”。

### 九、内容类型路由，本质上是质量功能

22 个预设只有在”系统能帮用户先站到对的位置”时才真正有价值。

所以 slide-creator 会先按内容类型给出合理起点：

```
数据报告 / KPI 看板    → Data Story、Enterprise Dark、Swiss Modern
商业路演 / VC Deck     → Bold Signal、Aurora Mesh、Enterprise Dark
开发工具 / API 文档    → Terminal Green、Neon Cyber、Neo-Retro Dev Deck
咨询报告 / 战略方案    → Strategy Consulting、Enterprise Dark、Swiss Modern
```

好的默认值会直接减少返工。结果就是更少的坏第一稿，更少的风格重置，也更少被上下文浪费掉的 token。

第一阶段的默认推荐面会有意收窄到 4 个核心 preset：

- `Swiss Modern`
- `Enterprise Dark`
- `Data Story`
- `Blue Sky`

这**不代表其他 preset 不可用**。用户只要明确指定任意当前 preset，系统仍应尊重该选择；收窄的只是默认推荐面和推荐优先级。

---

## 安装

### Claude Code

对 Claude 说：「安装 https://github.com/kaisersong/slide-creator」

或手动：
```bash
git clone https://github.com/kaisersong/slide-creator ~/.claude/skills/slide-creator
```

重启 Claude Code，使用 `/slide-creator` 调用。

### OpenClaw

```bash
# 通过 ClawHub 安装（推荐）
clawhub install kai-slide-creator

# 或手动克隆
git clone https://github.com/kaisersong/slide-creator ~/.openclaw/skills/slide-creator
```

> ClawHub 页面：https://clawhub.ai/skills/kai-slide-creator

---

## 使用方式

### 基本命令

```
/slide-creator --plan       # 分析内容和 resources/ 目录，生成 BRIEF.json
/slide-creator --generate   # 根据 BRIEF.json 生成 HTML 演示文稿
/slide-creator --review     # 诊断并修复内容质量问题
/slide-creator              # 从零开始（交互式风格探索）
/kai-html-export            # 导出为 PPTX 或 PNG（独立技能）
```

### 原始沙箱 fallback

`/slide-creator ...` 是 Claude/OpenClaw 的 slash 技能调用，不是原始 bash / python 命令。

如果你在原始沙箱或外部 agent runner 里执行，请改用：

```bash
python3 main.py --validate-brief --brief BRIEF.json
python3 main.py --generate --brief BRIEF.json --output presentation.html
python3 main.py --generate --brief BRIEF.json --output presentation.html --eval
```

内置 preset 仍然从 `references/` / `references/style-index.md` 读取；`themes/<name>/reference.md` 只用于自定义主题。

### 规划深度

- `自动（Auto）` — 快速路径；跳过 Phase 3.5 Review
- `精修（Polish）` — 深度路径；自动执行 Phase 3.5 Review

同一份内容在 `自动` 与 `精修` 之间切换时，除非用户明确要求换风格，否则应保持相同 preset。

### 典型工作流

**方式一：交互式创建**
1. 运行 `/slide-creator`，回答目的、长度、内容和图片四个问题
2. 查看 3 个风格预览，选择喜欢的风格
3. 生成完整演示文稿，在浏览器中打开

**方式二：IR-first 工作流（复杂内容推荐）**
1. 在项目目录放入素材（`resources/` 文件夹）
2. 运行 `/slide-creator --plan 我的AI创业公司融资路演`
3. 先检查 `BRIEF.json`；只有需要给人审阅时再派生 `PLANNING.md`
4. 运行 `/slide-creator --generate`

**方式三：PPT 转换**
1. 将 `.pptx` 文件放到当前目录
2. 运行 `/slide-creator`，技能会自动识别并提取内容

### Review 模式

```
/slide-creator --review presentation.html
```

**Review 行为：**
1. 加载 `references/review-checklist.md`
2. 执行全部 16 个检查点（6 个可自动检测 + 10 个 AI 建议）
3. 展示结果：✅ 通过 / 🔧 可自动修复 / ⚠️ 需确认 / ❌ 需人工判断
4. 用户选择：[全部自动修复] / [逐项确认] / [跳过]
5. 输出修复后 HTML + 诊断报告

**精修模式**：Phase 3.5 Review 在生成后自动执行。
**自动模式**：跳过 Phase 3.5。

### 耗时参考

端到端预计耗时：

- `自动（Auto）`：通常约 3-6 分钟
- `精修（Polish）`：通常约 8-15 分钟

---

## 功能特性

### 核心功能

- **IR-first 工作流** — `--plan` 提炼 `BRIEF.json`，`--generate` 从 IR 输出幻灯片
- **两种规划深度** — `自动` 适合快速出稿，`精修` 适合更强叙事和视觉锁定
- **内容 Review 系统** — 16 个质量检查点：`--review` 按需诊断；精修模式自动执行 Review
- **22 种设计预设** — 每种风格含命名布局变体
- **内容类型智能路由** — 根据路演、开发工具、数据报告等自动推荐风格
- **视觉风格探索** — 先生成 3 个预览，看图选风格而非描述风格
- **内联 SVG 图表** — 流程图、时间轴、条形图、对比矩阵、组织架构图，无需外部库
- **Blue Sky Starter 模板** — 完整 boilerplate，任何模型都能正确实现全套视觉系统

### 交互功能

- **播放模式** — 按 `F5` 或点击右下角 ▶ 按钮进入全屏播放；幻灯片缩放适配任意屏幕；控制栏自动隐藏；按 `Esc` 退出
- **演讲者模式** — 按 `P` 打开同步演讲者窗口：备注、计时器、页数、翻页导航；窗口高度随备注自动调整
- **备注编辑面板** — 编辑模式（`E` 键）下底部出现备注栏，点击标题可收起/展开，输入实时同步
- **浏览器内编辑** — 默认开启；直接在浏览器中编辑文字，Ctrl+S 保存
- **视口自适应** — 每张幻灯片精确填充 100vh，永不出现滚动条

### 输出功能

- **自定义主题系统** — 在 `themes/你的主题/` 放入 `reference.md` 即可添加专属预设；可选提供 `starter.html`
- **模板导出界面开关** — 在 `<body>` 上设置 `data-export-progress="false"`，同时隐藏进度条和导航点
- **图片处理流水线** — 自动评估和处理素材（Pillow）
- **PPT 导入** — 将 `.pptx` 文件转换为网页演示
- **PPTX / PNG 导出** — 通过 [kai-html-export](https://github.com/kaisersong/kai-html-export)
- **中英双语** — 完整支持中文内容

---

## 设计预设

| 预设 | 风格 | 适合场景 |
|------|------|----------|
| **Bold Signal** | 自信、强冲击 | 路演、主题演讲 |
| **Electric Studio** | 简洁、专业 | 商务演示 |
| **Creative Voltage** | 活力、复古现代 | 创意提案 |
| **Dark Botanical** | 优雅、精致 | 高端品牌 |
| **Blue Sky** | 清透、企业 SaaS | 产品发布、科技路演 |
| **Notebook Tabs** | 编辑感、有条理 | 报告、评审 |
| **Pastel Geometry** | 友好、亲切 | 产品介绍 |
| **Split Pastel** | 活泼、现代 | 创意机构 |
| **Vintage Editorial** | 个性鲜明 | 个人品牌 |
| **Neon Cyber** | 科技感、未来感 | 科技创业 |
| **Terminal Green** | 开发者风格 | 开发工具、API |
| **Swiss Modern** | 极简、精确 | 企业、数据 |
| **Paper & Ink** | 文学、沉思 | 叙事演讲 |
| **Aurora Mesh** | 鲜明、高端 SaaS | 产品发布、VC 融资路演 |
| **Enterprise Dark** | 权威、数据驱动 | B2B、投资者 deck、战略 |
| **Glassmorphism** | 轻盈、毛玻璃、现代 | 消费科技、品牌发布 |
| **Neo-Brutalism** | 大胆、不妥协 | 独立开发者、创意宣言 |
| **Chinese Chan** | 静谧、沉思 | 设计哲学、品牌、文化 |
| **Data Story** | 清晰、精确、说服力 | 业务回顾、KPI、数据分析 |
| **Modern Newspaper** | 犀利、权威、编辑感 | 业务报告、思想领导力演讲 |
| **Neo-Retro Dev Deck** | 有主见、技术感、手作风 | 开发工具发布、API 文档、黑客松 |
| **Strategy Consulting** | 结构化、权威、干净 | 咨询报告、战略方案、尽职调查 |

### Blue Sky

天空渐变背景（`#f0f9ff → #e0f2fe`）搭配浮动玻璃拟态卡片与动态环境光球。灵感来自真实的企业 AI 路演文稿（CloudHub V12 MVP），呈现出高空晴日般开阔、自信、精致的视觉气质。

标志性元素：SVG 颗粒噪声纹理叠层 · 3 个按幻灯片类型重新布阵的模糊光球 · `backdrop-filter: blur(24px)` 玻璃拟态卡片 · 40px 科技网格底层 · 弹簧物理横向切换动画 · 封面专属双层流动云朵效果。

**为什么 Blue Sky 是 starter 模板范本：** 它预置了全部 10 个签名视觉元素，模型只需填充幻灯片内容——没有误实现设计系统的风险。这种 `reference.md` + `starter.html` 的模式对任何复杂主题都可复用。

---

## 创建自定义主题

1. 创建 `themes/你的主题/` 目录
2. 编写 `reference.md`，描述：
   - 颜色（主色、强调色、中性色）
   - 字体（字体、字重、字号）
   - 布局模式（卡片、网格、全出血）
   - 组件类（如需自定义 CSS）
3. 可选添加 `starter.html` 用于复杂视觉系统（动画背景、自定义 JS、非常规布局）

你的主题会以"Custom: 你的主题"出现在风格选择列表中。

**附带的品牌主题示例：** `themes/cloudhub/` 和 `themes/kingdee/`

---

## 品牌风格迁移

将现有 `.pptx` 迁移到自定义品牌设计——同时输出像素级归档版和可编辑版。

```bash
# 第一步——风格迁移
/slide-creator --plan "将 company-deck.pptx 迁移到我们的品牌风格"
/slide-creator --generate  # → branded-deck.html

# 第二步——两种模式导出
/kai-html-export branded-deck.html              # 像素级
/kai-html-export --pptx --mode native branded-deck.html  # 可编辑
```

---

## 依赖要求

slide-creator **无外部依赖**。Python 3 仅用于规划阶段可选的图片评估，无需安装任何 Python 包。

如需导出 PPTX 或 PNG：`clawhub install kai-html-export` 或 `pip install playwright python-pptx`

---

## 输出文件

- `presentation.html` — 零依赖单文件，直接用浏览器打开
- `PRESENTATION_SCRIPT.md` — 演讲稿（幻灯片 8 张以上时自动生成）

---

## 兼容性

| 平台 | 版本 | 安装路径 |
|------|------|----------|
| Claude Code | 任意 | `~/.claude/skills/slide-creator/` |
| OpenClaw | ≥ 0.9 | `~/.openclaw/skills/slide-creator/` |

---

## 版本日志

**v2.27.0** — Captured-run eval 架构发版：新增 OpenAI-style skill eval prompts、归一化 trace 评分、fixture style rubric、live Codex baseline、回归比较器和可选 release-gate 集成。README 与 design docs 现在记录 Supervisor / Generate Worker / Style Judge 三段式架构、上下文隔离规则和 token 计费策略，确保 live eval 成本可见，而不是被 subagent 隐藏。

**v2.26.0** — Blue Sky 确定性渲染与自定义主题发版：Blue Sky 现在走正式 BRIEF 渲染链路并补齐 strict 校验覆盖；自定义主题在源仓库和插件包布局下都能正确解析；Kingdee / Cloudhub 私有主题素材完成路径清理、压缩和回归测试锁定。

**v2.25.0** — 新增 Strategy Consulting 咨询风格预设：12 种 canonical 布局（执行摘要、前后对比、三大支柱、漏斗图、框架矩阵、引言+证据、驱动因素分解树等），白底 navy 强调色，灵感源自 MBB 咨询报告。预设总数增至 22 种。

**v2.24.3** — 单 deck eval 与 production 验证发版：`--generate` / `render-from-brief` 现在支持通过 `--eval` 或 `--eval-out` 产出单份 deck 的评测 JSON；Data Story 的图表路由与标签逻辑会在数值信号不足时 fail closed，避免画出假趋势图；production demo fixture 也同步重验并更新到当前水印版本，保证核心 preset 验证持续为绿。

**v2.24.2** — Enterprise Dark / Chinese Chan 顶层 chrome 门禁加固：对于正式要求隐藏 `#brand-mark` 的 preset，校验器现在会直接拒绝泄漏左上角小标题；同时水印检查会校验当前 skill 版本号与 preset，防止旧 shell / 非 canonical 产物带着过期版本和技能名误过 `--strict`。

**v2.24.1** — 直接生成链路与写入前门禁修复：把“直接给内容 + 风格”的生成强制收回 `BRIEF.json -> render_from_brief()` 正式路径；让两个 CLI 渲染入口都在写最终文件前执行 `scripts/validate_html.py --strict`；并同步收紧 SKILL / workflow / 文档契约与回归测试，防止当前版本再次产出不符合 Chinese Chan canonical contract 的 HTML。

**v2.24.0** — 核心 preset 稳定性与质量门禁发版：新增 preset support tier 与 manifest 驱动的 eval / release gate 工具链，升级 low-context BRIEF 语义字段和 preset usage rules，修复 Chinese Chan 正式 contract 与 production/shared runtime 漂移，补齐第二轮家族 demo 运行时债务，把正式 HTML 校验器迁到 `scripts/validate_html.py` 并保留兼容 wrapper，同时收紧 Swiss Modern / shared shell 的页码与导航点 chrome，确保不同 preset 下都能正确可见。

**v2.23.2** — 沙箱入口与技能表面修补：新增根目录 `main.py` 与 `slide-creator` wrapper，用于原始沙箱里的 BRIEF 校验与渲染；`--plan` 现在会明确提示“这是 slash-skill 步骤”，不再抛出误导性的运行时错误；同时修正 `SKILL.md` 的用户入口层，恢复风格推荐表，并明确内置 preset 在 `references/` 下，`themes/<name>/reference.md` 只用于自定义主题。

**v2.23.1** — Enterprise Dark 运行时稳定性补丁：修复 shared js-engine 的 active-slide reveal 切换、默认隐藏编辑 chrome、将 watermark 占位符替换为真实版本/风格元数据、把 scroll-snap deck 的滚轮翻页稳定为“一次手势一页”，并修正 Enterprise Dark 的 narrative cover 路由、split 标题裁切、治理页节奏以及若隐若现的网格强度。

**v2.23.0** — 标题编排与低上下文质量发版：新增 preset-aware 的标题 profile registry 与浏览器级 title QA；扩展 low-context diagnostics / eval buckets 用于验证质量是否真的提升；严格门禁补强共享 runtime 与 `body[data-preset]`；`SKILL.md` 也按优先级重排为风格强制 → 叙事弧线 → 标题质量，再到可降级的播放 / 编辑 / 水印能力。

**v2.22.0** — 风格参考与严格门禁收口：全部 preset 通过 style-reference audit；Swiss Modern 以及 Enterprise Dark / Data Story / Glassmorphism / Chinese Chan 补齐 canonical export contract 与 user-content 路由；`tests/validate.py --strict` 被文档化并测试为 `--generate` 的写入前门禁；新增回归测试锁住 CSS 变量解析、布局多样性与高优先级 preset 契约检查。

**v2.19.0** — IR-first 发版：`BRIEF.json` 升级为主要真相源，`PLANNING.md` 降级为可选的人类视图；新增 `evals/generated-decks/` 下的 late-context 评估产物；README 的设计思想改写为 `prompt → BRIEF → HTML → validate → eval` 主线；并补齐与新契约对应的回归测试。

**v2.18.1** — Paper & Ink 风格参考修复：恢复正确的编辑风格定义（Cormorant Garamond 标题、Source Serif 4 正文、crimson 装饰线、首字母下沉）；slide HTML 添加 `.slide-content` 包裹以实现垂直居中；中文标题字体回退从 Noto Sans SC 改为系统宋体。

**v2.18.0** — JS 引擎抽取（html-template.md 从 557 行缩减到 222 行）；风格签名注入扩展为要求 Typography/Components 章节的所有 CSS 类；neon-cyber 光晕效果明确要求；风格一致性审计工具（`tests/audit_style_consistency.py`）。

**v2.17.0** — 风格参考系统重构；浅色背景对比度修复；glassmorphism 文字主题映射。

**v2.9.0** — 内容 Review 系统：16 个检查点（6 个可自动检测 + 10 个 AI 建议）；精修模式自动执行 Phase 3.5 Review；`--review` 命令支持按需诊断；三种规则类型（硬规则/情境规则/建议规则）。

**v2.8.0** — 将规划深度简化为两个面向用户的模式（自动/精修）；双语命名规则；耗时预期；preset 锁定规则；回归测试覆盖。

**v2.7.1** — 零依赖的 `check-doc-sync.py` 文档契约检查器，用于保持 SKILL.md、README.md 与 workflow.md 三处说明同步。

**v2.7.0** — Enhancement Mode 守则；浏览器内编辑默认开启但可关闭；附带品牌主题示例（`themes/cloudhub/`、`themes/kingdee/`）。

**v2.6.1** — 品牌风格迁移工作流文档。

**v2.6.0** — 设计质量基准（`references/design-quality.md`）：最低 65% 填充率、多栏平衡、90/8/2 配色法则、禁止连续 3 张纯要点页、内容语调配色校准、生成前自检门控。修复 aurora-mesh Inter 字体矛盾。

**v2.5.0** — 21 个预设 + Blue Sky starter 模板；Show Don't Tell 风格探索。

**v2.0.0** — 两阶段工作流（`--plan` / `--generate`）；浏览器内编辑；演讲者模式。

**v1.0.0** — 初始发布，10 个预设。


path: _config.yml
content: exclude:
  - "AI原生管理培训.html"
  - "output.html"
  - "slide-creator-intro.html"
  - "test-blue-sky-gen.html"
  - "test-blue-sky-skill.html"
  - "test-generate.html"
  - "test-generate2.html"
  - ".serena"
  - "docs"
  - "tests"
  - "CLAUDE.md"
  - "README.md"
  - "README.zh-CN.md"
  - "SKILL.md"


path: check-doc-sync.py
content: #!/usr/bin/env python3
"""
check-doc-sync.py — lightweight doc contract checker for slide-creator.

Purpose:
    Catch drift between the three user-facing docs that define the skill:
    - SKILL.md
    - README.md
    - references/workflow.md

This script is intentionally zero-dependency and read-only. `--dry-run` is
accepted for explicit check-only usage and future compatibility, but it does
not modify files in any mode.
"""

from __future__ import annotations

import argparse
import sys
from dataclasses import dataclass
from pathlib import Path


@dataclass(frozen=True)
class RuleResult:
    name: str
    ok: bool
    detail: str


def read_required(path: Path) -> str:
    if not path.exists():
        raise FileNotFoundError(f"Required file not found: {path}")
    return path.read_text(encoding="utf-8")


def contains_all(text: str, snippets: list[str]) -> tuple[bool, str]:
    missing = [snippet for snippet in snippets if snippet not in text]
    if missing:
        return False, "missing: " + ", ".join(repr(item) for item in missing)
    return True, "all expected markers present"


def contains_none(text: str, snippets: list[str]) -> tuple[bool, str]:
    present = [snippet for snippet in snippets if snippet in text]
    if present:
        return False, "forbidden markers present: " + ", ".join(repr(item) for item in present)
    return True, "no forbidden markers present"


def evaluate(root: Path) -> list[RuleResult]:
    skill = read_required(root / "SKILL.md")
    readme = read_required(root / "README.md")
    workflow = read_required(root / "references" / "workflow.md")

    results: list[RuleResult] = []

    ok, detail = contains_all(
        skill,
        [
            "默认开启",
            "--plan",
            "--generate",
            "BRIEF.json",
            "themes/<name>/reference.md",
            "用户显式指定任意当前 preset 时，必须保留该选择",
            "禁止手拼最终 HTML",
        ],
    )
    if ok:
        ok, detail = contains_none(skill, ["Every generated HTML file MUST include both of these", "shareable URL"])
    results.append(RuleResult("skill-contract", ok, detail))

    ok, detail = contains_all(
        readme,
        [
            "IR-first workflow",
            "BRIEF.json",
            "--plan",
            "--generate",
            "Default-on",
            "themes/your-theme/",
            "reference.md",
            "Explicit preset selection still wins",
            "The same rule applies to direct prompt generation",
        ],
    )
    if ok:
        ok, detail = contains_none(readme, ["Vercel", "shareable URL", "Share to URL"])
    results.append(RuleResult("readme-contract", ok, detail))

    ok, detail = contains_all(
        workflow,
        [
            "Enhancement Mode (existing HTML)",
            "If `BRIEF.json` exists, it's the source of truth",
            "single AskUserQuestion call with all 5 questions at once",
            "1280x720",
            "Support tier only affects default recommendation priority",
            "do **not** hand-compose final HTML",
        ],
    )
    results.append(RuleResult("workflow-contract", ok, detail))

    share_violations = []
    for label, text, forbidden in [
        ("SKILL.md", skill, ["shareable URL"]),
        ("README.md", readme, ["Vercel", "shareable URL", "Share to URL"]),
        ("references/workflow.md", workflow, ["Phase 6: Share"]),
    ]:
        present = [item for item in forbidden if item in text]
        if present:
            share_violations.append(f"{label}: {', '.join(repr(item) for item in present)}")
    results.append(
        RuleResult(
            "no-share-language",
            not share_violations,
            "ok" if not share_violations else "; ".join(share_violations),
        )
    )

    edit_mode_ok = (
        "默认开启" in skill
        and "Default-on" in readme
        and "Every generated HTML file MUST include both of these" not in skill
    )
    results.append(
        RuleResult(
            "edit-mode-default",
            edit_mode_ok,
            "skill + readme agree that edit mode is default-on and optional"
            if edit_mode_ok
            else "skill/readme disagree on optional edit mode language",
        )
    )

    return results


def print_results(results: list[RuleResult], dry_run: bool) -> int:
    if dry_run:
        print("DRY RUN: checking document contracts only, no files will be changed.")

    failures = 0
    for result in results:
        status = "PASS" if result.ok else "FAIL"
        print(f"{status} {result.name}: {result.detail}")
        if not result.ok:
            failures += 1

    if failures:
        print(f"\nSummary: {failures} rule(s) failed.")
        return 1

    print(f"\nSummary: {len(results)} rule(s) passed.")
    return 0


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--root",
        default=".",
        help="Repository root to inspect. Defaults to current directory.",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Explicit check-only mode. This script is read-only in all modes.",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    root = Path(args.root).resolve()
    try:
        results = evaluate(root)
    except FileNotFoundError as exc:
        print(f"FAIL missing-file: {exc}")
        return 1
    return print_results(results, dry_run=args.dry_run)


if __name__ == "__main__":
    sys.exit(main())


path: demos/aurora-mesh-en.html
content: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>kai-slide-creator — Zero-Dependency HTML Presentation Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;600;700&family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>

/* ═══════════════════════════════════════════════
   AURORA MESH DESIGN TOKENS
   ═══════════════════════════════════════════════ */
:root {
  --bg:          #0a0a1a;
  --accent:      #00f5c4;
  --accent-rgb:   0,245,196;
  --text:        #ffffff;
  --text-body:   rgba(255,255,255,0.70);
  --text-m:      rgba(255,255,255,0.45);
  --card-bg:     rgba(255,255,255,0.05);
  --card-border: rgba(255,255,255,0.10);
  --divider:     rgba(255,255,255,0.15);

  /* Typography */
  --font-display: 'Space Grotesk', sans-serif;
  --font-body: 'DM Sans', system-ui, sans-serif;
  --title-size: clamp(1.8rem, 5vw, 4rem);
  --subtitle-size: clamp(0.85rem, 1.5vw, 1.2rem);
  --body-size: clamp(0.75rem, 1.3vw, 0.95rem);
  --slide-padding: clamp(1.5rem, 4vw, 4rem);
  --content-gap: clamp(1rem, 2vw, 2rem);

  /* Animation */
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --duration-normal: 0.7s;
  --text-primary: var(--text);
  --text-secondary: var(--text-m);
  --bg-primary: var(--bg);
}

::selection { background: rgba(0,245,196,0.30); color: #00f5c4; }

/* Reset */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html {
  scroll-snap-type: y mandatory;
  height: 100%;
}

body {
  font-family: var(--font-body);
  color: var(--text);
  height: 100%;
  background-color: #0a0a1a;
  background-image:
    radial-gradient(ellipse at 20% 50%, rgba(120,40,200,0.40) 0%, transparent 60%),
    radial-gradient(ellipse at 80% 20%, rgba(0,180,255,0.30) 0%, transparent 50%),
    radial-gradient(ellipse at 60% 80%, rgba(0,255,180,0.20) 0%, transparent 50%);
  animation: auroraDrift 20s ease-in-out infinite alternate;
  background-size: 200% 200%;
}
@keyframes auroraDrift {
  0%   { background-position: 0% 50%; }
  33%  { background-position: 50% 20%; }
  66%  { background-position: 80% 80%; }
  100% { background-position: 100% 50%; }
}

/* ── Slide Container ── */
.slide {
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  padding: var(--slide-padding);
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
  overflow: hidden;
  background: transparent;
}

.slide-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-height: 100%;
  overflow: hidden;
  max-width: min(90vw, 1000px);
  position: relative;
  z-index: 1;
}

/* ── Typography ── */
.a-title {
  font-family: var(--font-display);
  font-size: var(--title-size);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: #fff;
}

.a-h2 {
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 3.5vw, 2.5rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #fff;
  line-height: 1.2;
  margin-bottom: clamp(0.5rem, 1.5vw, 1rem);
}

.a-subtitle {
  font-size: var(--subtitle-size);
  color: var(--text-body);
  line-height: 1.6;
  margin-top: 0.5rem;
}

.a-body {
  font-size: var(--body-size);
  color: var(--text-body);
  line-height: 1.7;
}

.a-label {
  font-size: clamp(0.6rem, 1vw, 0.7rem);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.a-accent { color: var(--accent); font-weight: 600; }

/* ── Components ── */
.g {
  background: rgba(255,255,255,0.05);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.10);
  border-radius: 16px;
  padding: clamp(14px, 2.5vw, 24px);
}

.stat { text-align: center; padding: clamp(12px, 2vw, 20px); }
.stat .num {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  font-weight: 700;
  background: linear-gradient(135deg, #00f5c4, #00b4d8);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text;
}
.stat .label {
  font-size: clamp(0.6rem, 1vw, 0.75rem);
  color: var(--text-m);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-top: 4px;
}

.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 14px; border-radius: 9999px;
  font-size: clamp(0.6rem, 1vw, 0.7rem);
  font-weight: 600; letter-spacing: 0.12em;
  text-transform: uppercase;
  background: rgba(var(--accent-rgb),0.12);
  border: 1px solid rgba(var(--accent-rgb),0.30);
  color: var(--accent);
}

kbd {
  display: inline-block; padding: 2px 8px;
  font-size: 0.8em; font-weight: 600;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.20);
  border-radius: 6px;
  color: var(--accent);
  vertical-align: middle;
}

.co, .info-callout {
  padding: clamp(8px, 1.5vw, 14px) clamp(12px, 2vw, 18px);
  border-radius: 10px;
  font-size: clamp(0.65rem, 1.1vw, 0.8rem);
  line-height: 1.6;
}
.co {
  background: rgba(var(--accent-rgb),0.08);
  border-left: 3px solid var(--accent);
  color: rgba(255,255,255,0.80);
}
.info-callout {
  background: rgba(0,180,255,0.08);
  border-left: 3px solid #00b4d8;
  color: rgba(255,255,255,0.80);
}

.cmd {
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 10px;
  padding: clamp(8px, 1.5vw, 14px) clamp(12px, 2vw, 18px);
  font-family: var(--font-display), monospace;
  font-size: clamp(0.7rem, 1.2vw, 0.9rem);
  color: var(--accent);
  line-height: 1.6;
}
.cmd::before { content: '> '; color: var(--text-m); }

.layer {
  display: flex; gap: 16px;
  padding: clamp(8px, 1.2vw, 12px) 0;
  border-bottom: 1px solid rgba(255,255,255,0.08);
  align-items: flex-start;
}
.layer:last-child { border-bottom: none; }
.layer-num {
  font-family: var(--font-display);
  font-size: 0.75rem; font-weight: 700;
  color: var(--accent);
  min-width: 24px;
}

.cols2 { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px, 2vw, 20px); }
.cols3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(12px, 2vw, 20px); }
.cols4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(8px, 1.5vw, 16px); }

.bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(60px, auto);
  gap: clamp(8px, 1.5vw, 14px);
}
.bento .span2 { grid-column: span 2; }

.a-divider { height: 1px; background: rgba(255,255,255,0.15); margin: clamp(12px, 2vw, 20px) 0; }

.step { position: relative; padding-left: 32px; }
.step::before {
  content: attr(data-step);
  position: absolute; left: 0; top: 0;
  font-size: 0.65rem; font-weight: 700;
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ── Reveal animations ── */
.reveal {
  opacity: 0; transform: translateY(20px);
  transition: opacity var(--duration-normal) var(--ease-out-expo),
              transform var(--duration-normal) var(--ease-out-expo);
}
.slide.visible .reveal { opacity: 1; transform: translateY(0); }
.reveal:nth-child(1) { transition-delay: 0.05s; }
.reveal:nth-child(2) { transition-delay: 0.15s; }
.reveal:nth-child(3) { transition-delay: 0.25s; }
.reveal:nth-child(4) { transition-delay: 0.35s; }

.chapter-num {
  font-family: var(--font-display);
  font-size: clamp(3rem, 10vw, 8rem);
  font-weight: 700;
  background: linear-gradient(135deg, rgba(var(--accent-rgb),0.3), rgba(120,40,200,0.2));
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text;
  line-height: 1;
}

.a-eof {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 4vw, 3rem);
  font-weight: 700;
  background: linear-gradient(135deg, #00f5c4, #00b4d8);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text;
  text-align: center;
}
.a-eof-sub {
  font-size: clamp(0.75rem, 1.5vw, 1rem);
  color: var(--text-m);
  text-align: center;
  margin-top: 8px;
}

.a-table {
  width: 100%; border-collapse: collapse;
  font-size: clamp(0.65rem, 1.1vw, 0.8rem);
}
.a-table th {
  text-align: left; padding: 8px 12px;
  border-bottom: 1px solid rgba(255,255,255,0.15);
  color: var(--accent); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.08em;
  font-size: clamp(0.55rem, 1vw, 0.7rem);
}
.a-table td {
  padding: 8px 12px;
  border-bottom: 1px solid rgba(255,255,255,0.06);
  color: var(--text-body);
}
.a-table tr:last-child td { border-bottom: none; }

/* ── Responsive ── */
@media (max-height: 700px) {
  :root {
    --slide-padding: clamp(0.75rem, 3vw, 2rem);
    --title-size: clamp(1.2rem, 4vw, 2.5rem);
  }
}
@media (max-height: 600px) {
  :root {
    --slide-padding: clamp(0.5rem, 2.5vw, 1.5rem);
    --title-size: clamp(1rem, 3.5vw, 2rem);
    --body-size: clamp(0.7rem, 1.2vw, 0.95rem);
  }
  .nav-dots, .keyboard-hint, .decorative { display: none; }
}
@media (max-height: 500px) {
  :root {
    --slide-padding: clamp(0.4rem, 2vw, 1rem);
    --title-size: clamp(1rem, 3.5vw, 1.5rem);
  }
}
@media (max-width: 600px) {
  .cols2, .cols3, .cols4, .bento { grid-template-columns: 1fr; }
  .bento .span2 { grid-column: span 1; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.2s !important;
  }
  html { scroll-behavior: auto; }
}

/* ═══════════════════════════════════════════════
   PRESENT MODE CSS
   ═══════════════════════════════════════════════ */
#present-btn {
  position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9997;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(var(--accent-rgb),0.2); color: var(--accent);
  border: 1.5px solid rgba(var(--accent-rgb),0.4);
  font-size: 1rem; cursor: pointer;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.2s; pointer-events: none;
}
body:hover #present-btn { opacity: 1; pointer-events: auto; }
#present-btn:hover { background: rgba(var(--accent-rgb),0.35); }

#present-counter {
  display: none; position: fixed; bottom: 1.4rem; left: 50%;
  transform: translateX(-50%); z-index: 9997;
  font-size: 0.65rem; letter-spacing: 0.14em; font-family: var(--font-body);
  color: var(--text-m); opacity: 0.5;
}

body.presenting { background: #000 !important; overflow: hidden !important; }
body.presenting .slide { position: fixed !important; inset: 0; width: 100vw !important; height: 100vh !important; transform-origin: center center; scroll-snap-align: none !important; display: none !important; }
body.presenting .slide.p-on { display: flex !important; }
body.presenting .nav-dots,
body.presenting .keyboard-hint,
body.presenting .slide-credit { display: none !important; }
body.presenting #present-btn { display: none !important; }
body.presenting #present-counter { display: block; }

/* ═══════════════════════════════════════════════
   PROGRESS BAR & NAV DOTS
   ═══════════════════════════════════════════════ */
.progress-bar {
  position: fixed; top: 0; left: 0; height: 3px;
  background: var(--accent); width: 0%; z-index: 100;
  transition: width 0.3s ease;
}

/* ═══════════════════════════════════════════════
   EDIT MODE
   ═══════════════════════════════════════════════ */
.edit-hotzone {
  position: fixed; top: 0; left: 0; width: 48px; height: 48px;
  z-index: 9990; cursor: pointer;
}
.edit-toggle {
  position: fixed; top: 8px; left: 8px; z-index: 9991;
  background: rgba(10,10,26,0.8); backdrop-filter: blur(12px);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 6px; padding: 3px 8px;
  font-family: var(--font-body); font-size: 0.65rem;
  color: var(--text-m); cursor: pointer;
  opacity: 0; transition: opacity 0.2s;
}
.edit-hotzone:hover ~ .edit-toggle,
.edit-toggle.show { opacity: 0.6; }
.edit-toggle:hover, .edit-toggle.active { opacity: 1 !important; color: var(--accent); border-color: var(--accent); }

#notes-panel {
  position: fixed; top: 0; right: 0; width: 320px; height: 100vh;
  background: rgba(10,10,26,0.95); backdrop-filter: blur(12px);
  border-left: 1px solid rgba(255,255,255,0.1);
  z-index: 9992; display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform 0.3s ease;
}
.edit-toggle.active ~ #notes-panel,
#notes-panel.active { transform: translateX(0) !important; }
#notes-panel.collapsed #notes-body { display: none; }
#notes-panel-header {
  padding: 8px 12px; border-bottom: 1px solid rgba(255,255,255,0.15);
  cursor: pointer; font-size: 0.65rem; color: var(--text-m);
  text-transform: uppercase; letter-spacing: 0.1em;
  display: flex; align-items: center; justify-content: space-between;
}
#notes-panel-header:hover { background: rgba(255,255,255,0.03); }
#notes-panel-label { flex: 1; }
#notes-collapse-btn {
  font-size: 1rem; color: var(--text-m); background: none; border: none;
  cursor: pointer; transition: transform 0.2s;
}
#notes-panel.collapsed #notes-collapse-btn { transform: rotate(180deg); }
#notes-body { padding: 0 12px 12px; }
#notes-textarea {
  width: 100%; height: 72px; min-height: 48px; max-height: 200px;
  resize: vertical; background: transparent; border: none; outline: none;
  color: var(--text); font-size: 0.75rem; line-height: 1.6;
  font-family: var(--font-body);
}

</style>
</head>
<body data-export-progress="true" data-preset="Aurora Mesh">

<div class="progress-bar"></div>

<nav class="nav-dots" aria-label="Slide navigation"></nav>

<!-- ═══════════════════════ SLIDES ═══════════════════════ -->

<!-- Slide 1: Hero — Full-screen declaration -->
<section class="slide" data-notes="kai-slide-creator product intro, zero dependencies and 21 presets" aria-label="Hero">
  <div class="slide-content" style="text-align: center; align-items: center;">
    <div class="reveal a-label" style="margin-bottom: 8px;">Zero-Dependency HTML Presentations</div>
    <div class="reveal a-title" style="background: linear-gradient(135deg, #fff 30%, rgba(var(--accent-rgb),0.8)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 16px;">kai-slide-creator</div>
    <div class="reveal a-subtitle">Three commands. 21 design presets. Pure HTML.<br>No templates, no exports, no platforms.</div>
    <div class="reveal" style="margin-top: clamp(20px, 3vw, 36px); display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;">
      <span class="pill">0 Dependencies</span>
      <span class="pill">21 Presets</span>
      <span class="pill" style="border-color: rgba(120,40,200,0.30); color: #b388ff; background: rgba(120,40,200,0.12);">Pure HTML</span>
    </div>
  </div>
</section>

<!-- Slide 2: Problem — Split evidence -->
<section class="slide" data-notes="Three major pain points with existing tools" aria-label="Problem">
  <div class="slide-content">
    <div class="reveal a-label" style="margin-bottom: 12px; color: var(--accent);">Core Pain Points</div>
    <div class="reveal a-h2">Why Do Existing Presentation Tools Always Fall Short?</div>
    <div class="reveal" style="margin-top: clamp(16px, 2vw, 24px); display: flex; flex-direction: column; gap: 12px;">
      <div class="layer"><div class="layer-num">01</div><div class="a-body"><span class="a-accent">Complex toolchains</span> — Install dependencies, configure environments, manage versions — too much friction for a single deck</div></div>
      <div class="layer"><div class="layer-num">02</div><div class="a-body"><span class="a-accent">Generic AI aesthetics</span> — Every slide looks the same: gradients, rounded corners, purple tones, zero identity</div></div>
      <div class="layer"><div class="layer-num">03</div><div class="a-body"><span class="a-accent">Platform lock-in</span> — Use a platform, get trapped. Files don't export, formats are proprietary</div></div>
    </div>
  </div>
</section>

<!-- Slide 3: Discovery — Flow steps -->
<section class="slide" data-notes="Show the visual selection philosophy" aria-label="Discovery">
  <div class="slide-content" style="text-align: center; align-items: center;">
    <div class="reveal a-label" style="margin-bottom: 12px;">Core Philosophy</div>
    <div class="reveal a-h2" style="text-align: center;">See Styles, Don't Describe Them</div>
    <div class="reveal" style="margin-top: clamp(20px, 3vw, 36px); display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(12px, 2vw, 20px);">
      <div class="g" style="text-align: center;"><div class="stat"><div class="num">3</div><div class="label">Previews</div></div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">Generate 3 style previews</div></div>
      <div class="g" style="text-align: center; border-color: rgba(var(--accent-rgb),0.30);"><div class="stat"><div class="num">→</div><div class="label">Visual Choice</div></div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">Pick with your eyes</div></div>
      <div class="g" style="text-align: center;"><div class="stat"><div class="num">21</div><div class="label">Presets</div></div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">Signature visual languages</div></div>
    </div>
  </div>
</section>

<!-- Slide 4: Solution — Preset grid -->
<section class="slide" data-notes="Showcase 21 presets" aria-label="Solution">
  <div class="slide-content">
    <div class="reveal a-label" style="margin-bottom: 12px;">Design Presets</div>
    <div class="reveal a-h2">21 Styles. No Generic AI Look.</div>
    <div class="reveal" style="margin-top: clamp(12px, 2vw, 20px);">
      <div class="bento">
        <div class="g span2" style="border-left: 3px solid var(--accent);"><div class="a-label">Bold Signal</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Pitch decks — high contrast, bold type</div></div>
        <div class="g" style="border-left: 3px solid var(--accent); border-color: rgba(var(--accent-rgb),0.40); background: rgba(0,245,196,0.08);"><div class="a-label" style="color: var(--accent);">Aurora Mesh</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Product launch — animated gradient, frosted glass</div></div>
        <div class="g" style="border-left: 3px solid #00b4d8;"><div class="a-label" style="color: #00b4d8;">Terminal Green</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Dev tools — monospace, scan lines</div></div>
        <div class="g" style="border-left: 3px solid #b388ff;"><div class="a-label" style="color: #b388ff;">Modern Newspaper</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Research — editorial typography</div></div>
        <div class="g" style="border-left: 3px solid var(--text-m);"><div class="a-label" style="color: var(--text-m);">Enterprise Dark</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Data dashboard — dark panels</div></div>
      </div>
    </div>
  </div>
</section>

<!-- Slide 5: Workflow — Flow steps -->
<section class="slide" data-notes="Three steps to create a presentation" aria-label="Workflow">
  <div class="slide-content">
    <div class="reveal a-label" style="margin-bottom: 12px;">Workflow</div>
    <div class="reveal a-h2">Three Commands. Idea to Deck.</div>
    <div class="reveal" style="margin-top: clamp(16px, 2vw, 24px);">
      <div class="cols3">
        <div class="g"><div class="a-label" style="margin-bottom: 6px; color: var(--accent);">01 Plan</div><div class="a-body" style="line-height: 1.8;"><span class="a-accent">/slide-creator --plan</span><br>Tell the AI what you need</div></div>
        <div class="g"><div class="a-label" style="margin-bottom: 6px; color: var(--accent);">02 Generate</div><div class="a-body" style="line-height: 1.8;"><span class="a-accent">/slide-creator --generate</span><br>Pick a style, auto-generate</div></div>
        <div class="g"><div class="a-label" style="margin-bottom: 6px; color: var(--accent);">03 Present</div><div class="a-body" style="line-height: 1.8;"><span class="a-accent">Open in browser</span><br>Zero install, F5 to play</div></div>
      </div>
    </div>
    <div class="reveal" style="margin-top: 12px;">
      <div class="co">Or skip planning — give content + style, generate immediately.</div>
    </div>
  </div>
</section>

<!-- Slide 6: Features — Grid checkpoints -->
<section class="slide" data-notes="Four key capabilities" aria-label="Features">
  <div class="slide-content">
    <div class="reveal a-label" style="margin-bottom: 12px;">Key Features</div>
    <div class="reveal a-h2">Four Capabilities. Full Workflow.</div>
    <div class="reveal" style="margin-top: clamp(16px, 2vw, 24px);">
      <div class="cols2">
        <div class="g" style="text-align: center;">
          <div class="stat"><div class="num">F5</div><div class="label">Playback</div></div>
          <div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">Auto-scale, controls auto-hide</div>
        </div>
        <div class="g" style="text-align: center;">
          <div class="stat"><div class="num">E</div><div class="label">Edit Mode</div></div>
          <div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">contenteditable, Ctrl+S save</div>
        </div>
        <div class="g" style="text-align: center;">
          <div class="stat"><div class="num">P</div><div class="label">Presenter</div></div>
          <div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">Notes, timer, sync via BroadcastChannel</div>
        </div>
        <div class="g" style="text-align: center;">
          <div class="stat"><div class="num">PPTX</div><div class="label">Export</div></div>
          <div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 8px;">/kai-html-export to PPTX</div>
        </div>
      </div>
    </div>
  </div>
</section>

<!-- Slide 7: Install — Evidence list -->
<section class="slide" data-notes="Installation methods" aria-label="Install">
  <div class="slide-content">
    <div class="reveal a-label" style="margin-bottom: 12px;">Installation</div>
    <div class="reveal a-h2">One Command. Zero Dependencies.</div>
    <div class="reveal" style="margin-top: clamp(16px, 2vw, 24px); display: flex; flex-direction: column; gap: 12px;">
      <div class="g">
        <div class="a-label" style="margin-bottom: 6px;">Claude Code</div>
        <div class="a-body">Tell Claude: install kai-slide-creator</div>
      </div>
      <div class="g" style="border-color: rgba(var(--accent-rgb),0.30);">
        <div class="a-label" style="margin-bottom: 6px; color: var(--accent);">OpenClaw</div>
        <div class="a-body"><span class="a-accent">clawhub install kai-slide-creator</span></div>
      </div>
    </div>
    <div class="reveal" style="margin-top: 12px;"><div class="co">Python 3 only. No npm, no node, no build step.</div></div>
  </div>
</section>

<!-- Slide 8: CTA — Stacked action -->
<section class="slide" data-notes="Call to action" aria-label="CTA">
  <div class="slide-content" style="text-align: center; align-items: center;">
    <div class="reveal">
      <span class="pill" style="margin-bottom: 16px;">Start Now</span>
    </div>
    <div class="reveal a-title" style="background: linear-gradient(135deg, #fff 30%, rgba(var(--accent-rgb),0.8)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; margin-bottom: 16px;">Start Now</div>
    <div class="a-divider" style="background: rgba(var(--accent-rgb),0.30); margin: 20px auto; max-width: 80px;"></div>
    <div class="reveal">
      <div class="cmd" style="text-align: center; font-size: clamp(0.9rem, 1.5vw, 1.2rem);">/slide-creator</div>
    </div>
    <div class="reveal a-subtitle" style="margin-top: 12px; text-align: center;">Tell Claude to install kai-slide-creator</div>
  </div>
</section>

<!-- Edit mode UI -->
<div class="edit-hotzone"></div>
<button class="edit-toggle" id="editToggle">Edit</button>

<div id="notes-panel">
  <div id="notes-panel-header">
    <span id="notes-panel-label">SPEAKER NOTES</span>
    <button id="notes-collapse-btn" aria-label="Collapse notes">&#x25B2;</button>
  </div>
  <div id="notes-body">
    <textarea id="notes-textarea" placeholder="Speaker notes for this slide..."></textarea>
  </div>
</div>

<!-- ═══════════════════════ JAVASCRIPT ═══════════════════════ -->
<script>
class SlidePresentation {
  constructor() {
    this.slides = document.querySelectorAll('.slide');
    this.currentSlide = 0;
    this.channel = new BroadcastChannel('slide-creator-presenter');

    this.slides[0]?.classList.add('visible');
    this.slides[0]?.querySelectorAll('.reveal').forEach(r => r.classList.add('visible'));

    this.setupNavDots();
    this.setupObserver();
    this.setupKeyboard();
    this.setupTouch();
    this.setupWheel();
    this.setupPresenter();
    this.updateProgress();
    this.setupEditor();
  }

  setupNavDots() {
    const nav = document.querySelector('.nav-dots');
    if (!nav) return;
    this.slides.forEach((_, i) => {
      const dot = document.createElement('button');
      dot.setAttribute('aria-label', 'Go to slide ' + (i + 1));
      dot.style.cssText = 'width:8px;height:8px;border-radius:50%;border:none;cursor:pointer;background:rgba(255,255,255,0.15);transition:all 0.3s;';
      dot.addEventListener('click', () => this.goTo(i));
      nav.appendChild(dot);
    });
  }

  setupObserver() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          entry.target.classList.add('visible');
          this.currentSlide = Array.prototype.indexOf.call(this.slides, entry.target);
          this.updateProgress();
          this.updateDots();
          this.broadcastState();
          this.updateNotesPanel();
        }
      });
    }, { threshold: 0.5 });
    this.slides.forEach(s => observer.observe(s));
  }

  setupKeyboard() {
    const _self = this;
    document.addEventListener('keydown', function(e) {
      if (e.target.getAttribute('contenteditable')) return;
      if (e.key === 'ArrowDown' || e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown' || e.key === 'Enter') {
        e.preventDefault(); self.next();
      } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft' || e.key === 'PageUp' || e.key === 'Backspace') {
        e.preventDefault(); self.prev();
      } else if (e.key === 'Home') { e.preventDefault(); self.goTo(0); }
      else if (e.key === 'End') { e.preventDefault(); self.goTo(self.slides.length - 1); }
      else if (e.key === 'p' || e.key === 'P') {
        const url = location.href.split('?')[0] + '?presenter';
        window.open(url, 'slide-presenter-' + Date.now(), 'width=1100,menubar=no,toolbar=no');
      } else if (e.key === 'e' || e.key === 'E') {
        if (this.editor && !this.editor.active) this.editor.enter();
        else if (this.editor) this.editor.exit();
      }
    });
  }

  setupTouch() {
    const _self = this;
    let startY = 0;
    document.addEventListener('touchstart', e => { startY = e.touches[0].clientY; }, { passive: true });
    document.addEventListener('touchend', e => {
      const delta = startY - e.changedTouches[0].clientY;
      if (Math.abs(delta) > 50) delta > 0 ? self.next() : self.prev();
    });
  }

  setupWheel() {
    // scroll-snap-type: y mandatory handles wheel natively
  }

  setupPresenter() {
    const _self = this;
    this.channel.addEventListener('message', function(e) {
      if (e.data.type === 'nav-next') self.next();
      else if (e.data.type === 'nav-prev') self.prev();
      else if (e.data.type === 'request-state') self.broadcastState();
    });
  }

  broadcastState() {
    const slide = this.slides[this.currentSlide];
    this.channel.postMessage({
      type: 'state',
      index: this.currentSlide,
      total: this.slides.length,
      notes: slide?.dataset.notes || ''
    });
  }

  goTo(index) {
    this.slides[index]?.scrollIntoView({ behavior: 'smooth' });
  }
  next() { this.goTo(Math.min(this.currentSlide + 1, this.slides.length - 1)); }
  prev() { this.goTo(Math.max(this.currentSlide - 1, 0)); }

  updateProgress() {
    const pct = (this.currentSlide / (this.slides.length - 1)) * 100;
    const bar = document.querySelector('.progress-bar');
    if (bar) bar.style.width = pct + '%';
    var counter = document.getElementById('present-counter');
    if (counter) counter.textContent = (this.currentSlide + 1) + ' / ' + this.slides.length;
  }

  updateDots() {
    document.querySelectorAll('.nav-dots button').forEach((dot, i) => {
      dot.style.background = i === this.currentSlide ? 'var(--accent)' : 'rgba(255,255,255,0.15)';
      dot.style.transform = i === this.currentSlide ? 'scale(1.4)' : 'scale(1)';
    });
  }

  setupEditor() {
    const _self = this;
    const panel = document.getElementById('notes-panel');
    const label = document.getElementById('notes-panel-label');
    const textarea = document.getElementById('notes-textarea');
    const toggle = document.getElementById('editToggle');
    const hotzone = document.querySelector('.edit-hotzone');
    let hideTimeout = null;

    hotzone.addEventListener('mouseenter', function() { clearTimeout(hideTimeout); toggle.classList.add('show'); });
    hotzone.addEventListener('mouseleave', function() { hideTimeout = setTimeout(function() { if (!this.editor?.active) toggle.classList.remove('show'); }, 400); });
    toggle.addEventListener('mouseenter', function() { clearTimeout(hideTimeout); });
    toggle.addEventListener('mouseleave', function() { hideTimeout = setTimeout(function() { if (!this.editor?.active) toggle.classList.remove('show'); }, 400); });
    hotzone.addEventListener('click', function() { self.editor.toggle(); });
    toggle.addEventListener('click', function() { self.editor.toggle(); });

    document.getElementById('notes-panel-header').addEventListener('click', function() {
      panel.classList.toggle('collapsed');
    });

    textarea.addEventListener('input', function() {
      const slide = self.slides[self.currentSlide];
      if (slide) { slide.dataset.notes = textarea.value; self.broadcastState(); }
    });

    document.addEventListener('keydown', function(e) {
      if ((e.ctrlKey || e.metaKey) && e.key === 's') {
        e.preventDefault(); self.saveFile();
      }
    });

    this.editor = {
      active: false,
      toggle: function() { self.editor.active ? self.editor.exit() : self.editor.enter(); },
      enter: function() {
        self.editor.active = true;
        toggle.classList.add('active');
        toggle.textContent = 'Done';
        self.slides.forEach(function(s) {
          s.querySelectorAll('h1,h2,h3,p,li,span,td,th').forEach(function(el) {
            el.setAttribute('contenteditable', 'true');
            el.style.outline = 'none';
          });
        });
        panel.classList.add('active');
        self.updateNotesPanel();
      },
      exit: function() {
        self.editor.active = false;
        toggle.classList.remove('active');
        toggle.textContent = 'Edit';
        self.slides.forEach(function(s) {
          s.querySelectorAll('[contenteditable]').forEach(function(el) {
            el.removeAttribute('contenteditable');
            el.style.outline = '';
          });
        });
        panel.classList.remove('active');
      }
    };

    this._notesLabel = label;
    this._notesTextarea = textarea;
  }

  updateNotesPanel() {
    if (!this.editor?.active) return;
    const slide = this.slides[this.currentSlide];
    this._notesLabel.textContent = 'SPEAKER NOTES — SLIDE ' + (this.currentSlide + 1) + ' / ' + this.slides.length;
    this._notesTextarea.value = slide?.dataset.notes || '';
  }

  saveFile() {
    const html = '<!DOCTYPE html>\n' + document.documentElement.outerHTML;
    const blob = new Blob([html], { type: 'text/html' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = document.title + '.html';
    a.click();
  }
}

/* ═══════════════════════ PRESENTER MODE ═══════════════════════ */
if (new URLSearchParams(location.search).has('presenter')) {
  document.title = 'Presenter — ' + document.title;
  document.body.innerHTML =
    '<style>*{box-sizing:border-box;margin:0}body{background:#111;color:#fff;font-family:system-ui,sans-serif}#pv{display:grid;grid-template-columns:1fr 220px;gap:1rem;padding:1.25rem;align-items:start}.pv-panel{background:#1e1e1e;border-radius:12px;padding:1.25rem}#pv-label{font-size:.6rem;letter-spacing:.18em;text-transform:uppercase;color:#555;margin-bottom:.6rem}#pv-notes{font-size:1.05rem;line-height:1.85;color:#d0d0d0}#pv-right{display:flex;flex-direction:column;gap:1rem}#pv-nav{display:flex;align-items:center;justify-content:space-between;gap:.5rem}.pv-arrow{width:32px;height:32px;border-radius:6px;border:1px solid #333;background:#2a2a2a;color:#aaa;font-size:1rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s;flex-shrink:0}.pv-arrow:hover{background:#383838;color:#fff}#pv-counter{text-align:center;flex:1}#pv-num{font-size:1.6rem;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}#pv-of{color:#555;font-size:.75rem;margin-top:.2rem}#pv-timer-box{text-align:center}#pv-timer-label{font-size:.6rem;letter-spacing:.18em;text-transform:uppercase;color:#555;margin-bottom:.3rem}#pv-timer{font-size:1.5rem;font-weight:700;font-family:monospace;font-variant-numeric:tabular-nums}</style>' +
    '<div id="pv"><div class="pv-panel"><div id="pv-label">Speaker Notes</div><div id="pv-notes">Waiting for main window\u2026</div></div><div id="pv-right"><div class="pv-panel"><div id="pv-nav"><button class="pv-arrow" id="pv-prev">\u2190</button><div id="pv-counter"><div id="pv-num">\u2014</div><div id="pv-of">/ \u2014</div></div><button class="pv-arrow" id="pv-next">\u2192</button></div></div><div class="pv-panel" id="pv-timer-box"><div id="pv-timer-label">Elapsed</div><div id="pv-timer">0:00</div></div></div></div>';

  const ch = new BroadcastChannel('slide-creator-presenter');
  let startTime = null;
  ch.addEventListener('message', function(e) {
    if (e.data.type !== 'state') return;
    if (!startTime) startTime = Date.now();
    document.getElementById('pv-notes').textContent = e.data.notes || '(no notes for this slide)';
    document.getElementById('pv-num').textContent = e.data.index + 1;
    document.getElementById('pv-of').textContent = '/ ' + e.data.total;
  });
  ch.postMessage({ type: 'request-state' });
  setInterval(function() {
    if (!startTime) return;
    const s = Math.floor((Date.now() - startTime) / 1000);
    document.getElementById('pv-timer').textContent = Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
  }, 1000);
  document.getElementById('pv-prev').addEventListener('click', function() { ch.postMessage({ type: 'nav-prev' }); });
  document.getElementById('pv-next').addEventListener('click', function() { ch.postMessage({ type: 'nav-next' }); });
  document.addEventListener('keydown', function(e) {
    if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ' || e.key === 'PageDown' || e.key === 'Enter') ch.postMessage({ type: 'nav-next' });
    else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp' || e.key === 'PageUp' || e.key === 'Backspace') ch.postMessage({ type: 'nav-prev' });
  });
} else {
  /* ═══════════════════════ PRESENT MODE ═══════════════════════ */
  class PresentMode {
    constructor(ctrl) {
      this.ctrl = ctrl;
      this.baseW = 1440; this.baseH = 900;
      this.active = false;
      const btn = document.createElement('button');
      btn.id = 'present-btn'; btn.title = 'Present (F5)';
      btn.setAttribute('aria-label', 'Present'); btn.textContent = '\u25B6';
      document.body.appendChild(btn);
      const counter = document.createElement('div');
      counter.id = 'present-counter'; document.body.appendChild(counter);
      btn.addEventListener('click', () => this.enter());
      document.addEventListener('keydown', e => {
        if (e.key === 'F5') { e.preventDefault(); this.enter(); }
        if (e.key === 'Escape' && this.active) this.exit();
        if ((e.key === 'b' || e.key === 'B') && this.active) document.body.classList.toggle('presenting-black');
      });
      document.addEventListener('fullscreenchange', () => { if (this.active) { setTimeout(() => this._scale(), 100); } if (!document.fullscreenElement && this.active) this.exit(); });
                window.addEventListener('resize', () => { if (this.active) this._scale(); });
    }
    enter() {
      if (this.active) return;
      this.active = true;
      document.body.classList.add('presenting');
      document.documentElement.requestFullscreen?.().catch(() => {});
      this._scale();
      this._show(this.ctrl.currentSlide);
      this._origGoTo = this.ctrl.goTo.bind(this.ctrl);
      this.ctrl.goTo = (i) => {
        const idx = Math.max(0, Math.min(i, this.ctrl.slides.length - 1));
        this.ctrl.currentSlide = idx;
        this.ctrl.slides.forEach((s, j) => s.classList.toggle('visible', j === idx));
        this._show(idx);
        this.ctrl.updateProgress?.();
        this.ctrl.updateDots?.();
        this.ctrl.broadcastState?.();
      };
    }
    _reset() {
                this.ctrl.slides.forEach(slide => { slide.style.transform = ''; slide.classList.remove('p-on'); });
            }
    exit() {
                if (!this.active) return;
                this.active = false;
                document.body.classList.remove('presenting', 'presenting-black');
                this._reset();
      if (document.fullscreenElement) document.exitFullscreen?.();
      if (this._origGoTo) { this.ctrl.goTo = this._origGoTo; this._origGoTo = null; }
      this.ctrl.slides[this.ctrl.currentSlide]?.scrollIntoView({ behavior: 'instant' });
    }
    _scale() {
                const vw = window.innerWidth;
                const vh = window.innerHeight;
                const scale = Math.min(vw / this.baseW, vh / this.baseH);
                this.ctrl.slides.forEach(slide => { slide.style.transform = 'scale(' + scale + ')'; });
            }
    _show(i) {
      document.querySelectorAll('.slide').forEach((s, idx) => s.classList.toggle('p-on', idx === i));
      const c = document.getElementById('present-counter');
      if (c) c.textContent = (i + 1) + ' / ' + this.ctrl.slides.length;
    }

            _updateCounter() {
                const c = document.getElementById('present-counter');
                if (c) c.textContent = (this.ctrl.currentSlide + 1) + ' / ' + this.ctrl.slides.length;
            }
  }

  new PresentMode(new SlidePresentation());
}

/* Watermark */
(function() {
  var slides = document.querySelectorAll('.slide');
  if (!slides.length) return;
  var last = slides[slides.length - 1];
  var credit = document.createElement('div');
  credit.className = 'slide-credit';
  credit.textContent = 'By kai-slide-creator v2.16.0 \u00B7 Aurora Mesh';
  last.appendChild(credit);
})();
</script>
</body>
</html>

path: demos/aurora-mesh-zh.html
content: <!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>slide-creator — Aurora Mesh</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@700&family=DM+Sans:wght@400;500&family=Noto+Sans+SC:wght@400;700&display=swap" rel="stylesheet">
<style>
body { background-color: #0a0a1a; }

:root {
    --bg-primary: #0a0a1a;
    --accent: #00f5c4;
    --text-primary: #ffffff;
    --text-body: rgba(255,255,255,0.70);
    --text-muted: rgba(255,255,255,0.45);
    --card-bg: rgba(255,255,255,0.05);
    --card-border: rgba(255,255,255,0.10);
    --divider: rgba(255,255,255,0.15);
    --font-display: 'Space Grotesk', 'Noto Sans SC', sans-serif;
    --font-body: 'DM Sans', 'Noto Sans SC', sans-serif;
    --title-size: clamp(2.5rem, 7vw, 6rem);
    --h2-size: clamp(1.5rem, 3.5vw, 2.5rem);
    --subtitle-size: clamp(1rem, 2vw, 1.4rem);
    --body-size: clamp(0.75rem, 1.5vw, 1.125rem);
    --slide-padding: clamp(2rem, 5vw, 5rem);
    --content-gap: clamp(1rem, 2vw, 2rem);
}

/* Reset */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-snap-type: y mandatory; height: 100%; }
body {
    font-family: var(--font-body);
    background-color: #0a0a1a;
    background-image:
        radial-gradient(ellipse at 20% 50%, rgba(120,40,200,0.40) 0%, transparent 60%),
        radial-gradient(ellipse at 80% 20%, rgba(0,180,255,0.30) 0%, transparent 50%),
        radial-gradient(ellipse at 60% 80%, rgba(0,255,180,0.20) 0%, transparent 50%);
    background-size: 200% 200%;
    animation: auroraDrift 20s ease-in-out infinite alternate;
    color: var(--text-primary);
    height: 100%;
}

@keyframes auroraDrift {
    0%   { background-position: 0% 50%; }
    33%  { background-position: 50% 20%; }
    66%  { background-position: 80% 80%; }
    100% { background-position: 100% 50%; }
}

body[data-export-progress="false"] .progress-bar,
body[data-export-progress="false"] .nav-dots { display: none !important; }

/* Slide */
.slide {
    width: 100vw; height: 100vh; height: 100dvh;
    scroll-snap-align: start; display: flex; flex-direction: column;
    position: relative; overflow: hidden;
    background: transparent;
}

/* Aurora slide containers */
.aurora-slide {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    width: 100%; height: 100%; padding: var(--slide-padding); text-align: center;
}
.aurora-content {
    width: 100%; max-width: min(90vw, 800px); text-align: left;
}
.aurora-title {
    font-family: var(--font-display);
    font-size: clamp(2.5rem, 7vw, 6rem);
    font-weight: 700; letter-spacing: -0.03em; line-height: 1.05; color: #ffffff;
}
.aurora-subtitle {
    font-size: clamp(1rem, 2vw, 1.4rem); color: rgba(255,255,255,0.70);
    margin-top: 1rem; line-height: 1.6;
}
.aurora-card {
    background: rgba(255,255,255,0.05); backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid var(--card-border); border-radius: 16px;
    padding: clamp(20px, 3vw, 36px);
}
.aurora-card.active {
    border-color: rgba(0,245,196,0.40);
    background: rgba(255,255,255,0.08);
}
.aurora-divider {
    height: 1px; background: rgba(0,245,196,0.3); border: none;
    margin: 16px 0;
}
.aurora-accent, .aurora-emphasis { color: #00f5c4; font-weight: 600; }
.aurora-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 14px; background: rgba(0,245,196,0.12);
    border: 1px solid rgba(0,245,196,0.30); border-radius: 9999px;
    font-size: clamp(0.65rem, 1vw, 0.75rem); font-weight: 600;
    letter-spacing: 0.12em; text-transform: uppercase; color: #00f5c4;
}
.aurora-stat {
    -webkit-background-clip: text; -webkit-text-fill-color: transparent;
    background-image: linear-gradient(135deg, #00f5c4, #00b4ff);
    font-family: var(--font-display);
    font-size: clamp(3rem, 10vw, 8rem); font-weight: 700; line-height: 1;
}

/* Components */
.stat-row { display: flex; gap: clamp(3rem, 6vw, 6rem); justify-content: center; align-items: flex-start; }
.stat-col { display: flex; flex-direction: column; align-items: center; }
.stat-sub { font-size: clamp(0.75rem, 1.2vw, 1rem); color: rgba(255,255,255,0.45); margin-top: 8px; }
.hero-stat {
    font-family: var(--font-display); font-weight: 700; font-size: clamp(28px, 4vw, 48px);
    letter-spacing: -0.03em; color: var(--accent); line-height: 1;
}
.hero-stat-label {
    font-size: clamp(11px, 1.2vw, 13px); font-weight: 500;
    color: var(--text-body); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 4px;
}
.step-item { display: flex; align-items: flex-start; gap: 16px; margin-bottom: clamp(16px, 2vw, 28px); }
.step-num { font-family: var(--font-display); font-weight: 700; font-size: clamp(24px, 3vw, 40px); color: #00f5c4; line-height: 1; flex-shrink: 0; width: 48px; }
.step-text { font-size: clamp(14px, 1.4vw, 17px); line-height: 1.5; color: var(--text-body); }
.step-title { font-weight: 500; color: #fff; font-size: clamp(16px, 1.6vw, 20px); margin-bottom: 4px; }
.feat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px, 2vw, 20px); }
.install-row { display: flex; gap: clamp(16px, 2vw, 24px); margin-top: clamp(16px, 2vw, 28px); flex-wrap: wrap; }
.install-item { flex: 1; min-width: 280px; }
.install-label { font-size: 10px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase; color: rgba(255,255,255,0.35); margin-bottom: 10px; }
.code-block { background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.08); padding: 14px 18px; border-radius: 8px; font-family: 'DM Sans', 'Noto Sans SC', monospace; font-size: clamp(13px, 1.2vw, 15px); color: rgba(255,255,255,0.75); }
.feat-card-title { font-weight: 600; font-size: clamp(14px, 1.4vw, 17px); color: #fff; margin-bottom: 6px; }
.feat-card-desc { font-size: clamp(12px, 1.2vw, 15px); color: rgba(255,255,255,0.5); line-height: 1.5; }
.cta-pill { display: inline-block; background: #00f5c4; color: #0a0a1a; font-family: var(--font-display); font-weight: 700; font-size: clamp(18px, 2vw, 26px); padding: 18px 40px; border-radius: 12px; text-decoration: none; }

/* Reveal */
.reveal { opacity: 0; transform: translateY(20px); transition: opacity 0.7s cubic-bezier(0.16,1,0.3,1), transform 0.7s cubic-bezier(0.16,1,0.3,1); }
.slide.visible .reveal { opacity: 1; transform: translateY(0); }
.reveal:nth-child(1) { transition-delay: 0.05s; }
.reveal:nth-child(2) { transition-delay: 0.15s; }
.reveal:nth-child(3) { transition-delay: 0.25s; }
.reveal:nth-child(4) { transition-delay: 0.35s; }

ul { list-style: none; }
li { margin-bottom: 0.8em; line-height: 1.5; color: var(--text-body); font-size: clamp(14px, 1.4vw, 17px); }

/* ═══════════════════════════════════════════
   Per-slide CSS
   ═══════════════════════════════════════════ */

/* P2: Problem — subtitle as H2 heading */
#slide-2 .aurora-subtitle.h2-title {
    font-size: clamp(1.8rem, 4vw, 3.5rem); margin-top: 0.8rem; color: #fff;
    font-weight: 700; font-family: var(--font-display); letter-spacing: -0.02em;
}
/* P3: Discovery — subtitle heading + style badge items */
#slide-3 .aurora-subtitle.h2-title {
    font-size: clamp(1.8rem, 4vw, 3.5rem); margin-top: 0.8rem; color: #fff;
    font-weight: 700; font-family: var(--font-display); letter-spacing: -0.02em;
}
#slide-3 .style-name { font-weight: 700; }
#slide-3 .style-desc { font-size: 13px; color: var(--text-muted); }
/* P4: Solution — stat row spacing + uniform size */
#slide-4 .stat-row { margin-top: clamp(24px, 3vw, 48px); }
#slide-4 .aurora-stat { font-size: clamp(3rem, 7vw, 5rem); }
/* P5: Workflow — subtitle heading */
#slide-5 .aurora-subtitle.h2-title {
    font-size: clamp(1.8rem, 4vw, 3.5rem); margin-top: 0.8rem; color: #fff;
    font-weight: 700; font-family: var(--font-display); letter-spacing: -0.02em;
}
/* P6: Features — wider content + subtitle heading */
#slide-6 .aurora-content { max-width: min(90vw, 850px); }
#slide-6 .aurora-subtitle.h2-title {
    font-size: clamp(1.8rem, 4vw, 3.5rem); margin-top: 0.8rem; color: #fff;
    font-weight: 700; font-family: var(--font-display); letter-spacing: -0.02em;
}
/* P7: Install — subtitle heading */
#slide-7 .aurora-subtitle.h2-title {
    font-size: clamp(1.8rem, 4vw, 3.5rem); margin-top: 0.8rem; color: #fff;
    font-weight: 700; font-family: var(--font-display); letter-spacing: -0.02em;
}
/* P8: CTA — sub text */
#slide-8 .cta-sub { font-size: clamp(13px, 1.2vw, 16px); color: rgba(255,255,255,0.45); margin-top: 12px; }

/* ═══════════════════════════════════════════
   Universal UI Elements
   ═══════════════════════════════════════════ */
#brand-mark { position: fixed; top: 20px; left: 28px; font-family: 'Space Grotesk', sans-serif; font-weight: 700; font-size: 15px; color: #00f5c4; z-index: 1000; }
.progress-bar { position: fixed; top: 0; left: 0; height: 3px; background: var(--accent); width: 0%; z-index: 100; transition: width 0.3s ease; }
.nav-dots { position: fixed; right: 1.5rem; top: 50%; transform: translateY(-50%); display: flex; flex-direction: column; gap: 8px; z-index: 100; }

/* ═══════════════════════════════════════════
   Present Mode
   ═══════════════════════════════════════════ */
#present-btn { position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9997; width: 44px; height: 44px; border-radius: 50%; background: rgba(0,0,0,0.5); color: rgba(255,255,255,0.8); border: 1.5px solid rgba(255,255,255,0.2); font-size: 1rem; cursor: pointer; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); display: flex; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.2s; pointer-events: none; }
body:hover #present-btn { opacity: 1; pointer-events: auto; }
#present-btn:hover { background: rgba(0,0,0,0.78); }
#present-counter { display: none; position: fixed; bottom: 1.4rem; left: 50%; transform: translateX(-50%); z-index: 9997; font-size: 0.65rem; letter-spacing: 0.14em; font-family: system-ui, sans-serif; color: rgba(255,255,255,0.28); }
body.presenting { background: #000 !important; overflow: hidden !important; }
body.presenting .slide { position: fixed !important; inset: 0; width: 100vw !important; height: 100vh !important; transform-origin: center center; scroll-snap-align: none !important; display: none !important; }
body.presenting .slide.p-on { display: flex !important; }
body.presenting #present-btn { display: none !important; }
body.presenting #present-counter { display: block; }
body.presenting.presenting-black .slide { visibility: hidden !important; }
body.presenting.presenting-black::after { content: ''; position: fixed; inset: 0; background: #000; z-index: 99999; }
.slide-credit { position: absolute; bottom: 8px; right: 14px; font-size: 9px; color: var(--text-muted, #999); opacity: 0.35; pointer-events: none; z-index: 1; font-family: system-ui, sans-serif; }
body.presenting .slide-credit { display: none !important; }

/* ═══════════════════════════════════════════
   Edit Mode
   ═══════════════════════════════════════════ */
.edit-hotzone { position: fixed; top: 0; left: 0; width: 80px; height: 80px; z-index: 10000; cursor: pointer; }
.edit-toggle { position: fixed; top: 16px; left: 16px; opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 10001; padding: 6px 14px; border-radius: 4px; border: 1px solid rgba(255,255,255,0.15); background: rgba(0,0,0,0.6); color: #fff; font-size: 12px; cursor: pointer; font-family: 'DM Sans', sans-serif; }
.edit-toggle.show { opacity: 1; pointer-events: auto; }
.edit-toggle.active { opacity: 1; pointer-events: auto; background: var(--accent); color: #0a0a1a; border-color: var(--accent); }
#notes-panel { display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 9998; background: rgba(8,8,18,0.94); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border-top: 1px solid rgba(255,255,255,0.08); }
#notes-panel.active { display: flex; flex-direction: column; }
#notes-panel-header { display: flex; align-items: center; justify-content: space-between; padding: 0.45rem 1.4rem; cursor: pointer; user-select: none; }
#notes-panel-header:hover { background: rgba(255,255,255,0.03); }
#notes-panel-label { flex: 1; font-size: 0.58rem; letter-spacing: 0.16em; text-transform: uppercase; color: rgba(255,255,255,0.3); }
#notes-drag-hint { flex: 1; display: flex; justify-content: center; }
#notes-drag-hint::after { content: ''; display: block; width: 36px; height: 3px; border-radius: 2px; background: rgba(255,255,255,0.25); }
#notes-collapse-btn { flex: 1; text-align: right; font-size: 1rem; color: rgba(255,255,255,0.4); background: none; border: none; cursor: pointer; transition: color 0.15s, transform 0.2s; }
#notes-collapse-btn:hover { color: rgba(255,255,255,0.85); }
#notes-body { padding: 0 1.4rem 0.75rem; }
#notes-panel.collapsed #notes-body { display: none; }
#notes-panel.collapsed #notes-collapse-btn { transform: rotate(180deg); }
#notes-textarea { width: 100%; height: 72px; min-height: 48px; max-height: 200px; resize: vertical; background: transparent; border: none; outline: none; color: rgba(255,255,255,0.75); font-size: 0.88rem; line-height: 1.65; font-family: system-ui, sans-serif; }
#notes-textarea::placeholder { color: rgba(255,255,255,0.18); }

/* ═══════════════════════════════════════════
   Responsive
   ═══════════════════════════════════════════ */
@media (max-width: 768px) {
    .stat-row { flex-direction: column; gap: clamp(1.5rem, 3vw, 2rem); align-items: center; }
    .stat-divider { width: clamp(60px, 10vw, 100px); height: 1px; }
    .feat-grid { grid-template-columns: 1fr !important; }
    .install-row { flex-direction: column; }
    .aurora-stat { font-size: clamp(3rem, 12vw, 5rem) !important; }
}
@media (max-height: 700px) { :root { --slide-padding: clamp(1rem, 3vw, 3rem); } }
@media (max-height: 600px) { :root { --slide-padding: clamp(0.75rem, 2.5vw, 2rem); } .nav-dots { display: none; } }
@media (max-height: 500px) { :root { --slide-padding: clamp(0.5rem, 2vw, 1.5rem); } }
@media (max-width: 600px) { .feat-grid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.2s !important; } }
</style>
</head>
<body data-export-progress="true" data-preset="Aurora Mesh">
<div class="progress-bar"></div>
<nav class="nav-dots" aria-label="Slide navigation"></nav>
<span id="brand-mark">slide-creator</span>

<!-- P1: Hero — centered headline on aurora -->
<section class="slide" id="slide-1" data-notes="slide-creator 是零依赖 HTML 演示文稿生成器，提供 21 种设计预设。" aria-label="Hero">
<div class="aurora-slide">
    <span class="aurora-badge reveal">开源 · Claude Code · OpenClaw</span>
    <h1 class="aurora-title reveal">slide<span class="aurora-accent">-creator</span></h1>
    <p class="aurora-subtitle reveal">为 Claude Code 和 OpenClaw 打造零依赖 HTML 演示文稿</p>
    <div class="stat-row reveal">
        <div class="stat-col">
            <span class="hero-stat">21</span>
            <span class="hero-stat-label">预设风格</span>
        </div>
        <div class="stat-col">
            <span class="hero-stat">0</span>
            <span class="hero-stat-label">外部依赖</span>
        </div>
        <div class="stat-col">
            <span class="hero-stat">&#8734;</span>
            <span class="hero-stat-label">幻灯片</span>
        </div>
    </div>
    <span class="slide-num-label light">01</span>
</div>
</section>

<!-- P2: Problem — frosted glass card on aurora -->
<section class="slide" id="slide-2" data-notes="现有演示工具的四大痛点：复杂工具链、通用AI审美、平台锁定、模板同质化。" aria-label="Problem">
<div class="aurora-slide">
<div class="aurora-content">
    <span class="aurora-badge reveal">Problem</span>
    <h2 class="aurora-subtitle reveal h2-title">演示工具越做越复杂，结果千篇一律</h2>
    <div class="aurora-divider reveal"></div>
    <ul>
        <li class="reveal">复杂工具链 — 安装配置半天，还没开始做内容</li>
        <li class="reveal">通用 AI 审美 — 每份演示看起来都一样，缺乏品牌辨识度</li>
        <li class="reveal">平台锁定 — 离开特定软件就无法查看或编辑</li>
        <li class="reveal">模板同质化 — 选来选去都是那几个通用模板</li>
    </ul>
    <span class="slide-num-label light">02</span>
</div>
</div>
</section>

<!-- P3: Discovery — glass card with style previews -->
<section class="slide" id="slide-3" data-notes="看图选风格而非描述风格，生成预览后直观选择。" aria-label="Discovery">
<div class="aurora-slide">
<div class="aurora-content">
    <span class="aurora-badge reveal">Discovery</span>
    <h2 class="aurora-subtitle reveal h2-title">看图选风格，而非描述风格</h2>
    <div class="aurora-divider reveal"></div>
    <div class="aurora-card reveal">
        <div class="feat-grid">
            <div><span class="aurora-accent style-name">Bold Signal</span><br><span class="style-desc">数据驱动、高对比</span></div>
            <div><span class="aurora-accent style-name">Aurora Mesh</span><br><span class="style-desc">专业品牌、毛玻璃卡片</span></div>
            <div><span class="aurora-accent style-name">Neon Cyber</span><br><span class="style-desc">开发者、赛博朋克</span></div>
            <div><span class="aurora-accent style-name">Swiss Modern</span><br><span class="style-desc">研究、思想领导力</span></div>
        </div>
    </div>
    <span class="slide-num-label light">03</span>
</div>
</div>
</section>

<!-- P4: Solution — big stats centered -->
<section class="slide" id="slide-4" data-notes="21 种预设风格，100% 浏览器运行，3 分钟快速出稿。" aria-label="Solution">
<div class="aurora-slide">
    <span class="aurora-badge reveal">Solution</span>
    <div class="stat-row reveal">
        <div class="stat-col">
            <span class="aurora-stat">21</span>
            <span class="aurora-subtitle stat-sub">预设风格</span>
            <span class="stat-sub">每种都有独立视觉签名</span>
        </div>
        <div class="stat-divider"></div>
        <div class="stat-col">
            <span class="aurora-stat">100%</span>
            <span class="aurora-subtitle stat-sub">浏览器运行</span>
            <span class="stat-sub">零服务器，零依赖</span>
        </div>
        <div class="stat-divider"></div>
        <div class="stat-col">
            <span class="aurora-stat">3min</span>
            <span class="aurora-subtitle stat-sub">快速出稿</span>
            <span class="stat-sub">从想法到完整演示</span>
        </div>
    </div>
    <span class="slide-num-label light">04</span>
</div>
</section>

<!-- P5: Workflow — steps in glass card -->
<section class="slide" id="slide-5" data-notes="三个命令完成全流程：创建规划、选择风格、生成并打开。" aria-label="Workflow">
<div class="aurora-slide">
<div class="aurora-content">
    <span class="aurora-badge reveal">Workflow</span>
    <h2 class="aurora-subtitle reveal h2-title">三个命令，从想法到演示</h2>
    <div class="aurora-divider reveal"></div>
    <div class="aurora-card reveal">
        <div class="step-item">
            <span class="step-num">01</span>
            <div>
                <div class="step-title">创建规划</div>
                <div class="code-block">/slide-creator --plan "产品发布"</div>
            </div>
        </div>
        <div class="step-item">
            <span class="step-num">02</span>
            <div>
                <div class="step-title">选择风格</div>
                <div class="step-text">从 3 个预览中直观选择最匹配的视觉风格</div>
            </div>
        </div>
        <div class="step-item">
            <span class="step-num">03</span>
            <div>
                <div class="step-title">生成并打开</div>
                <div class="code-block">/slide-creator --generate</div>
            </div>
        </div>
    </div>
    <span class="slide-num-label light">05</span>
</div>
</div>
</section>

<!-- P6: Features -- four glass cards grid -->
<section class="slide" id="slide-6" data-notes="四个核心能力：视口适配、浏览器内编辑、PPTX 导出、两阶段规划。" aria-label="Features">
<div class="aurora-slide">
<div class="aurora-content">
    <span class="aurora-badge reveal">Features</span>
    <h2 class="aurora-subtitle reveal h2-title">四个核心能力，覆盖完整工作流</h2>
    <div class="aurora-divider reveal"></div>
    <div class="feat-grid">
        <div class="aurora-card reveal">
            <div class="feat-card-title">视口适配</div>
            <div class="feat-card-desc">每张幻灯片精确填满视口，clamp() 响应式字号，零溢出</div>
        </div>
        <div class="aurora-card reveal">
            <div class="feat-card-title">浏览器内编辑</div>
            <div class="feat-card-desc">E 键进入编辑模式，实时修改文字和备注，Ctrl+S 保存</div>
        </div>
        <div class="aurora-card reveal">
            <div class="feat-card-title">PPTX 导出</div>
            <div class="feat-card-desc">图片模式像素级还原，Native 模式文字可编辑</div>
        </div>
        <div class="aurora-card reveal">
            <div class="feat-card-title">两阶段规划</div>
            <div class="feat-card-desc">自动模式快速出稿，精修模式深度规划叙事结构</div>
        </div>
    </div>
    <span class="slide-num-label light">06</span>
</div>
</div>
</section>

<!-- P7: Install -- glass card with install commands -->
<section class="slide" id="slide-7" data-notes="Claude Code 和 OpenClaw 两种安装方式。" aria-label="Install">
<div class="aurora-slide">
<div class="aurora-content">
    <span class="aurora-badge reveal">Install</span>
    <h2 class="aurora-subtitle reveal h2-title">一行命令，立即开始</h2>
    <div class="aurora-divider reveal"></div>
    <div class="aurora-card reveal">
        <div class="install-row">
            <div class="install-item">
                <div class="install-label">Claude Code</div>
                <div class="code-block">安装 https://github.com/kaisersong/slide-creator</div>
            </div>
            <div class="install-item">
                <div class="install-label">OpenClaw</div>
                <div class="code-block">clawhub install kai-slide-creator</div>
            </div>
        </div>
    </div>
    <span class="slide-num-label light">07</span>
</div>
</div>
</section>

<!-- P8: CTA -- centered CTA on aurora -->
<section class="slide" id="slide-8" data-notes="使用 /slide-creator 命令立即生成你的第一份演示文稿。" aria-label="CTA">
<div class="aurora-slide">
    <span class="aurora-badge reveal">Ready?</span>
    <h1 class="aurora-title reveal">一个命令，创建无限可能</h1>
    <p class="aurora-subtitle reveal">纯浏览器运行 · 零依赖 · 21 种风格任你选择</p>
    <div class="aurora-divider reveal"></div>
    <span class="cta-pill reveal">/slide-creator</span>
    <span class="cta-sub reveal">支持 Claude Code 和 OpenClaw</span>
    <span class="slide-num-label light">08</span>
</div>
</section>

<div class="edit-hotzone"></div>
<button class="edit-toggle" id="editToggle" title="Edit mode (E)">Edit</button>
<div id="notes-panel">
    <div id="notes-panel-header">
        <div id="notes-panel-label">SPEAKER NOTES</div>
        <div id="notes-drag-hint"></div>
        <button id="notes-collapse-btn" title="Collapse / expand">&#9662;</button>
    </div>
    <div id="notes-body">
        <textarea id="notes-textarea" placeholder="Add speaker notes..."></textarea>
    </div>
</div>

<script>
class SlidePresentation {
    constructor() {
        this.slides = document.querySelectorAll('.slide');
        this.currentSlide = 0;
        this.channel = new BroadcastChannel('slide-creator-presenter');
        this.slides[0]?.classList.add('visible');
        this.slides[0]?.querySelectorAll('.reveal').forEach(function(r) { r.classList.add('visible'); });
        this.setActiveSlide(0);
        this.setupNavDots(); this.setupObserver(); this.setupKeyboard();
        this.setupTouch(); this.setupWheel(); this.setupPresenter(); this.setupEditor(); this.updateProgress();
    }

    setActiveSlide(index) {
        this.slides.forEach((slide, i) => {
            const active = i === index;
            slide.classList.toggle('visible', active);
            slide.querySelectorAll('.reveal').forEach(function(r) { r.classList.toggle('visible', active); });
        });
    }

    setupNavDots() {
        const nav = document.querySelector('.nav-dots');
        if (!nav) return;
        this.slides.forEach((_, i) => {
            const dot = document.createElement('button');
            dot.setAttribute('aria-label', 'Go to slide ' + (i + 1));
            dot.style.cssText = 'width:8px;height:8px;border-radius:50%;border:none;cursor:pointer;background:rgba(255,255,255,0.3);transition:all 0.3s;';
            dot.addEventListener('click', () => this.goTo(i)); nav.appendChild(dot);
        });
        nav.style.cssText = 'position:fixed;right:1.5rem;top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:8px;z-index:100;';
    }

    setupObserver() {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (entry.isIntersecting) {
                    this.currentSlide = [...this.slides].indexOf(entry.target);
                    if (this.currentSlide < 0) return;
                    this.setActiveSlide(this.currentSlide);
                    this.updateProgress(); this.updateDots(); this.broadcastState();
                    this.updateNotesPanel();
                }
            });
        }, { threshold: 0.5 });
        this.slides.forEach(s => observer.observe(s));
    }

    setupKeyboard() {
        document.addEventListener('keydown', (e) => {
            if (e.target.getAttribute('contenteditable') || e.target.tagName === 'TEXTAREA') return;
            if (['ArrowDown','ArrowRight',' ','PageDown','Enter'].includes(e.key)) { e.preventDefault(); this.next(); }
            else if (['ArrowUp','ArrowLeft','PageUp','Backspace'].includes(e.key)) { e.preventDefault(); this.prev(); }
            else if (e.key === 'p' || e.key === 'P') { window.open(location.href.split('?')[0] + '?presenter', 'slide-presenter-' + Date.now(), 'width=1100,menubar=no,toolbar=no,location=no'); }
            else if (e.key === 'e' || e.key === 'E') { if (this.editor && !this.editor.active) this.editor.enter(); else if (this.editor && this.editor.active) this.editor.exit(); }
        });
    }

    setupTouch() {
        let startY = 0;
        document.addEventListener('touchstart', e => { startY = e.touches[0].clientY; }, { passive: true });
        document.addEventListener('touchend', e => { const d = startY - e.changedTouches[0].clientY; if (Math.abs(d) > 50) d > 0 ? this.next() : this.prev(); });
    }

    setupWheel() {
        this.wState = 'idle';
        this.wLastTime = 0;
        this.wFallback = null;
        this.scrollSettleTimer = null;

        const enterDraining = () => {
            if (this.wState !== 'animating') return;
            this.wState = 'draining';
            this.wLastTime = Date.now();
            clearTimeout(this.wFallback);
            this.wFallback = setTimeout(() => { this.wState = 'idle'; }, 1500);
        };

        window.addEventListener('scroll', () => {
            if (this.wState !== 'animating') return;
            clearTimeout(this.scrollSettleTimer);
            this.scrollSettleTimer = setTimeout(enterDraining, 120);
        }, { passive: true });

        document.addEventListener('wheel', (e) => {
            if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT'
              || e.target.getAttribute('contenteditable')) return;
            if (e.target.closest && e.target.closest('#notes-panel')) return;

            let dx = e.deltaX, dy = e.deltaY;
            if (e.deltaMode === WheelEvent.DOM_DELTA_LINE) { dx *= 20; dy *= 20; }
            if (e.deltaMode === WheelEvent.DOM_DELTA_PAGE) { dx *= window.innerHeight; dy *= window.innerHeight; }
            const delta = Math.abs(dy) >= Math.abs(dx) ? dy : dx;
            if (Math.abs(delta) < 6) return;

            e.preventDefault();
            if (this.wState === 'animating') return;

            const now = Date.now();
            if (this.wState === 'draining') {
                const gap = now - this.wLastTime;
                this.wLastTime = now;
                if (gap <= 80) return;
                this.wState = 'idle';
                clearTimeout(this.wFallback);
            }

            this.wState = 'animating';
            clearTimeout(this.scrollSettleTimer);
            const moved = delta > 0 ? this.next() : this.prev();
            if (!moved) {
                this.wState = 'idle';
                return;
            }

            this.scrollSettleTimer = setTimeout(enterDraining, 450);
        }, { passive: false });
    }

    setupPresenter() {
        this.channel.addEventListener('message', e => {
            if (e.data.type === 'nav-next') this.next();
            else if (e.data.type === 'nav-prev') this.prev();
            else if (e.data.type === 'request-state') this.broadcastState();
        });
    }

    broadcastState() {
        const slide = this.slides[this.currentSlide];
        this.channel.postMessage({ type: 'state', index: this.currentSlide, total: this.slides.length, notes: slide?.dataset.notes || '' });
    }

    goTo(index) {
        const idx = Math.max(0, Math.min(index, this.slides.length - 1));
        const moved = idx !== this.currentSlide;
        this.currentSlide = idx;
        this.setActiveSlide(idx);
        this.slides[idx]?.scrollIntoView({ behavior: moved ? 'smooth' : 'auto' });
        this.updateProgress(); this.updateDots(); this.broadcastState();
        this.updateNotesPanel();
        return moved;
    }
    next() { return this.goTo(Math.min(this.currentSlide + 1, this.slides.length - 1)); }
    prev() { return this.goTo(Math.max(this.currentSlide - 1, 0)); }

    updateProgress() {
        const pct = (this.currentSlide / (this.slides.length - 1)) * 100;
        const bar = document.querySelector('.progress-bar');
        if (bar) bar.style.width = pct + '%';
    }

    updateDots() {
        document.querySelectorAll('.nav-dots button').forEach((dot, i) => {
            dot.style.background = i === this.currentSlide ? 'var(--accent)' : 'rgba(255,255,255,0.3)';
            dot.style.transform = i === this.currentSlide ? 'scale(1.3)' : 'scale(1)';
        });
    }

    setupEditor() {
        const panel = document.getElementById('notes-panel');
        const label = document.getElementById('notes-panel-label');
        const textarea = document.getElementById('notes-textarea');
        const toggle = document.getElementById('editToggle');
        const hotzone = document.querySelector('.edit-hotzone');
        let hideTimeout = null;

        hotzone.addEventListener('mouseenter', () => { clearTimeout(hideTimeout); toggle.classList.add('show'); });
        hotzone.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { if (this.editor.active) return; toggle.classList.remove('show'); }, 400); });
        toggle.addEventListener('mouseenter', () => clearTimeout(hideTimeout));
        toggle.addEventListener('mouseleave', () => { hideTimeout = setTimeout(() => { if (this.editor.active) return; toggle.classList.remove('show'); }, 400); });
        hotzone.addEventListener('click', () => this.editor.toggle());
        toggle.addEventListener('click', () => this.editor.toggle());

        document.getElementById('notes-panel-header').addEventListener('click', () => panel.classList.toggle('collapsed'));

        textarea.addEventListener('input', () => {
            const slide = this.slides[this.currentSlide];
            if (slide) { slide.dataset.notes = textarea.value; this.broadcastState(); }
        });

        document.addEventListener('keydown', (e) => {
            if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); this.saveFile(); }
        });

        this.editor = {
            active: false,
            toggle: () => this.editor.active ? this.editor.exit() : this.editor.enter(),
            enter: () => {
                this.editor.active = true;
                toggle.classList.add('active'); toggle.textContent = 'Done';
                this.slides.forEach(s => s.querySelectorAll('h1,h2,h3,p,li,span,td,th').forEach(el => {
                    el.setAttribute('contenteditable', 'true'); el.style.outline = 'none';
                }));
                panel.classList.add('active'); this.updateNotesPanel();
            },
            exit: () => {
                this.editor.active = false;
                toggle.classList.remove('active'); toggle.textContent = 'Edit';
                this.slides.forEach(s => s.querySelectorAll('[contenteditable]').forEach(el => {
                    el.removeAttribute('contenteditable'); el.style.outline = '';
                }));
                panel.classList.remove('active');
            }
        };
        this._notesLabel = label; this._notesTextarea = textarea;
    }

    updateNotesPanel() {
        if (!this.editor?.active) return;
        const slide = this.slides[this.currentSlide];
        this._notesLabel.textContent = 'SPEAKER NOTES \u2014 SLIDE ' + (this.currentSlide + 1) + ' / ' + this.slides.length;
        this._notesTextarea.value = slide?.dataset.notes || '';
    }

    saveFile() {
        const html = '<!DOCTYPE html>\n' + document.documentElement.outerHTML;
        const bytes = new TextEncoder().encode(html);
        fetch(location.pathname, { method: 'PUT', body: bytes, headers: { 'Content-Type': 'text/html' } })
            .catch(() => {
                const a = Object.assign(document.createElement('a'), {
                    href: URL.createObjectURL(new Blob([html], { type: 'text/html' })),
                    download: location.pathname.split('/').pop() || 'presentation.html'
                }); a.click(); URL.revokeObjectURL(a.href);
            });
    }
}

if (new URLSearchParams(location.search).has('presenter')) {
    document.title = 'Presenter \u2014 ' + document.title;
    document.body.innerHTML = `
    <style>
        * { box-sizing: border-box; margin: 0; }
        body { background: #111; color: #fff; font-family: system-ui, sans-serif; }
        #pv { display: grid; grid-template-columns: 1fr 220px; gap: 1rem; padding: 1.25rem; align-items: start; }
        .pv-panel { background: #1e1e1e; border-radius: 12px; padding: 1.25rem; }
        #pv-label { font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; color: #555; margin-bottom: 0.6rem; }
        #pv-notes { font-size: 1.05rem; line-height: 1.85; color: #d0d0d0; }
        #pv-right { display: flex; flex-direction: column; gap: 1rem; }
        #pv-nav { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
        .pv-arrow { width: 32px; height: 32px; border-radius: 6px; border: 1px solid #333; background: #2a2a2a; color: #aaa; font-size: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; }
        .pv-arrow:hover { background: #383838; color: #fff; }
        #pv-counter { text-align: center; flex: 1; }
        #pv-num { font-size: 1.6rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; }
        #pv-of { color: #555; font-size: 0.75rem; margin-top: 0.2rem; }
        #pv-timer { font-size: 1.5rem; font-weight: 700; font-family: monospace; font-variant-numeric: tabular-nums; }
        #pv-timer-label { font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; color: #555; }
    </style>
    <div id="pv">
        <div class="pv-panel"><div id="pv-label">Speaker Notes</div><div id="pv-notes">Waiting for main window...</div></div>
        <div id="pv-right">
            <div class="pv-panel"><div id="pv-nav">
                <button class="pv-arrow" id="pv-prev">\u2190</button>
                <div id="pv-counter"><div id="pv-num">\u2014</div><div id="pv-of">/ \u2014</div></div>
                <button class="pv-arrow" id="pv-next">\u2192</button>
            </div></div>
            <div class="pv-panel"><div id="pv-timer-label">Elapsed</div><div id="pv-timer">0:00</div></div>
        </div>
    </div>`;

    const ch = new BroadcastChannel('slide-creator-presenter');
    let startTime = null;
    const pv = document.getElementById('pv');
    let lastH = 0, roTimer = null;
    new ResizeObserver(() => {
        clearTimeout(roTimer);
        roTimer = setTimeout(() => {
            const h = Math.ceil(pv.getBoundingClientRect().height);
            if (h === lastH) return;
            lastH = h;
            const chrome = window.outerHeight - window.innerHeight;
            window.resizeTo(window.outerWidth, Math.max(260, h + chrome + 4));
        }, 40);
    }).observe(pv);

    ch.addEventListener('message', e => {
        if (e.data.type !== 'state') return;
        if (!startTime) startTime = Date.now();
        document.getElementById('pv-notes').textContent = e.data.notes || '(no notes for this slide)';
        document.getElementById('pv-num').textContent = e.data.index + 1;
        document.getElementById('pv-of').textContent = '/ ' + e.data.total;
    });
    ch.postMessage({ type: 'request-state' });

    setInterval(() => {
        if (!startTime) return;
        const s = Math.floor((Date.now() - startTime) / 1000);
        document.getElementById('pv-timer').textContent = Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
    }, 1000);

    document.getElementById('pv-prev').addEventListener('click', () => ch.postMessage({ type: 'nav-prev' }));
    document.getElementById('pv-next').addEventListener('click', () => ch.postMessage({ type: 'nav-next' }));
    document.addEventListener('keydown', e => {
        if (e.key === 'ArrowRight' || e.key === 'ArrowDown' || e.key === ' ' || e.key === 'PageDown' || e.key === 'Enter') ch.postMessage({ type: 'nav-next' });
        else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp' || e.key === 'PageUp' || e.key === 'Backspace') ch.postMessage({ type: 'nav-prev' });
    });

} else {
class PresentMode {
    constructor(ctrl) {
        this.ctrl = ctrl;
        this.baseW = 1440; this.baseH = 900;
        this.active = false;
        const btn = document.createElement('button');
        btn.id = 'present-btn'; btn.title = 'Present (F5)'; btn.setAttribute('aria-label', 'Present'); btn.textContent = '\u25B6';
        document.body.appendChild(btn);
        const counter = document.createElement('div');
        counter.id = 'present-counter'; document.body.appendChild(counter);
        btn.addEventListener('click', () => this.enter());
        document.addEventListener('keydown', e => {
            if (e.key === 'F5') { e.preventDefault(); this.enter(); }
            if (e.key === 'Escape' && this.active) this.exit();
            if ((e.key === 'b' || e.key === 'B') && this.active) document.body.classList.toggle('presenting-black');
        });
        document.addEventListener('fullscreenchange', () => { if (this.active) { setTimeout(() => this._scale(), 100); } if (!document.fullscreenElement && this.active) this.exit(); });
        window.addEventListener('resize', () => { if (this.active) this._scale(); });
    }
    enter() {
        if (this.active) return;
        this.active = true;
        document.body.classList.add('presenting');
        if (document.documentElement.requestFullscreen) document.documentElement.requestFullscreen().catch(() => {});
        this._scale();
        this._show(this.ctrl.currentSlide);
        this._updateCounter();
        this._origGoTo = this.ctrl.goTo.bind(this.ctrl);
        this.ctrl.goTo = (i) => {
            const idx = Math.max(0, Math.min(i, this.ctrl.slides.length - 1));
            this.ctrl.currentSlide = idx;
            this.ctrl.setActiveSlide(idx);
            this._show(idx);
            this.ctrl.updateProgress?.();
            this.ctrl.updateDots?.();
            this.ctrl.broadcastState?.();
            this.ctrl.updateNotesPanel?.();
        };
    }
    exit() {
        if (!this.active) return;
        this.active = false;
        document.body.classList.remove('presenting', 'presenting-black');
        this._reset();
        if (document.fullscreenElement) document.exitFullscreen?.();
        if (this._origGoTo) { this.ctrl.goTo = this._origGoTo; this._origGoTo = null; }
        this.ctrl.slides[this.ctrl.currentSlide]?.scrollIntoView({ behavior: 'instant' });
    }
    _scale() {
        const vw = window.innerWidth;
        const vh = window.innerHeight;
        const s = Math.min(vw / this.baseW, vh / this.baseH);
        const ox = (vw - this.baseW * s) / 2;
        const oy = (vh - this.baseH * s) / 2;
        this.ctrl.slides.forEach(slide => {
            slide.style.transform = 'translate(' + ox + 'px, ' + oy + 'px) scale(' + s + ')';
        });
    }
    _reset() {
        this.ctrl.slides.forEach(slide => { slide.style.transform = ''; slide.classList.remove('p-on'); });
    }
    _show(i) {
        this.ctrl.slides.forEach((s, idx) => s.classList.toggle('p-on', idx === i));
    }
    _updateCounter() {
        const c = document.getElementById('present-counter');
        if (c) c.textContent = (this.ctrl.currentSlide + 1) + ' / ' + this.ctrl.slides.length;
    }
}
new PresentMode(new SlidePresentation());
}
</script>

<script>
(function() {
    var slides = document.querySelectorAll('.slide');
    if (!slides.length) return;
    var last = slides[slides.length - 1];
    var credit = document.createElement('div');
    credit.className = 'slide-credit';
    credit.textContent = 'By kai-slide-creator v2.27.0 · Aurora Mesh';
    last.appendChild(credit);
})();
</script>
</body>
</html>


path: demos/blue-sky-en.html
content: <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>kai-slide-creator — Zero-Dependency HTML Presentation Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>

:root { --slide-count: 8; }

/* ═══════════════════════════════════════════════
   BLUE SKY DESIGN TOKENS
   ═══════════════════════════════════════════════ */
:root {
  --bg-from:        #f0f9ff;
  --bg-to:          #e0f2fe;
  --card-bg:        rgba(255, 255, 255, 0.70);
  --card-border:    rgba(255, 255, 255, 0.90);
  --text-primary:   #0f172a;
  --text-secondary: #334155;
  --text-muted:     #64748b;
  --accent-blue:    #2563eb;
  --accent-sky:     #0ea5e9;
  --accent-indigo:  #4f46e5;
  --gradient-text:  linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
  --grid-color:     rgba(14, 165, 233, 0.06);
  --orb1-color: rgba(96,  165, 250, 0.35);
  --orb2-color: rgba(99,  102, 241, 0.25);
  --orb3-color: rgba(14,  165, 233, 0.30);
  --amber:     #f59e0b;  --amber-bg: rgba(251,191,36,0.12);
  --red:       #ef4444;  --red-bg:   rgba(239,68,68,0.10);
  --green:     #10b981;  --green-bg: rgba(16,185,129,0.10);
}

::selection { background: #bfdbfe; color: #1e3a8a; }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
  width: 100%; height: 100%; overflow: hidden;
  font-family: 'DM Sans', 'Inter', system-ui, -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  background: linear-gradient(160deg, var(--bg-from), var(--bg-to));
}

/* Noise */
body::before {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
}

/* Grid */
body::after {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--grid-color) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
  background-size: 40px 40px;
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at 50% 50%, black 40%, transparent 100%);
  mask-image:         radial-gradient(ellipse 70% 70% at 50% 50%, black 40%, transparent 100%);
}

/* Orbs */
.orb {
  position: fixed; border-radius: 50%; pointer-events: none; z-index: 0;
  filter: blur(80px);
  transition: top 1.2s cubic-bezier(0.22,1,0.36,1), left 1.2s cubic-bezier(0.22,1,0.36,1),
              width 1.2s cubic-bezier(0.22,1,0.36,1), height 1.2s cubic-bezier(0.22,1,0.36,1);
}
#orb1 { background: var(--orb1-color); }
#orb2 { background: var(--orb2-color); }
#orb3 { background: var(--orb3-color); }

/* Track */
#stage { position: fixed; inset: 0; overflow: hidden; z-index: 1; }
#track {
  display: flex;
  width: calc(100vw * var(--slide-count));
  height: 100%;
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
.slide {
  width: 100vw; flex-shrink: 0; height: 100vh; height: 100dvh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: clamp(28px, 4vw, 56px) clamp(32px, 5vw, 80px);
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, var(--bg-from), var(--bg-to));
}
.slide-content {
  width: 100%; max-width: min(90vw, 1000px);
  display: flex; flex-direction: column; position: relative; z-index: 1;
}

/* Typography */
.a-title {
  font-family: 'Inter', 'DM Sans', sans-serif;
  font-size: clamp(2rem, 5.5vw, 4.5rem);
  font-weight: 800; letter-spacing: -0.03em;
  line-height: 1.05; color: var(--text-primary);
}
.a-h2 {
  font-family: 'Inter', 'DM Sans', sans-serif;
  font-size: clamp(1.25rem, 3.2vw, 2.25rem);
  font-weight: 700; letter-spacing: -0.02em;
  color: var(--text-primary); line-height: 1.2;
  margin-bottom: clamp(0.5rem, 1.5vw, 1rem);
}
.a-subtitle {
  font-size: clamp(0.85rem, 1.5vw, 1.2rem);
  color: var(--text-secondary); line-height: 1.6;
  margin-top: 0.5rem;
}
.a-body {
  font-size: clamp(0.75rem, 1.3vw, 0.95rem);
  color: var(--text-secondary); line-height: 1.7;
}
.a-label {
  font-size: clamp(0.6rem, 1vw, 0.75rem);
  font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--accent-sky);
}
.a-accent { color: var(--accent-blue); font-weight: 600; }
.a-muted { color: var(--text-muted); }

/* Glassmorphism */
.g {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 20px;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  box-shadow: 0 10px 40px -10px rgba(37,99,235,0.12), 0 1px 3px rgba(0,0,0,0.04),
              0 1px 0 rgba(255,255,255,0.8) inset;
  padding: clamp(14px, 2.5vw, 24px);
}

/* Gradient text */
.gt {
  background: var(--gradient-text);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Cloud ambient */
.cloud {
  position: absolute; border-radius: 50%; pointer-events: none;
  filter: blur(60px);
  animation: cloud-drift 8s ease-in-out infinite;
}
@keyframes cloud-drift {
  0%, 100% { transform: translateY(0) scale(1); }
  50%       { transform: translateY(-20px) scale(1.05); }
}

/* Cloud bank */
.cloud-layer {
  position: absolute; bottom: 0; left: 0; width: 100%; height: 75%;
  pointer-events: none; overflow: hidden; z-index: 2;
}
.cloud-strip {
  position: absolute; bottom: 0; left: 0; display: flex; height: 100%;
  animation: cloud-scroll 30s linear infinite;
}
.cloud-strip.fast { animation-duration: 18s; }
@keyframes cloud-scroll {
  from { transform: translateX(0); } to { transform: translateX(-1920px); }
}
.cloud-group { position: absolute; bottom: 0; }
.cloud-puff  { position: absolute; background: white; border-radius: 50%; }

/* Stats */
.stat { text-align: center; padding: clamp(12px, 2vw, 20px); }
.stat .num {
  font-family: 'Inter', sans-serif;
  font-size: clamp(1.5rem, 3vw, 2.5rem);
  font-weight: 800; color: var(--accent-blue);
}
.stat .label {
  font-size: clamp(0.6rem, 1vw, 0.75rem);
  color: var(--text-muted);
  text-transform: uppercase; letter-spacing: 0.1em;
  margin-top: 4px;
}

/* Pill badges */
.pill {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 14px; border-radius: 9999px;
  font-size: clamp(0.6rem, 1vw, 0.7rem);
  font-weight: 600; letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--accent-blue);
  background: rgba(37,99,235,0.08);
  border: 1px solid rgba(37,99,235,0.15);
}

kbd {
  display: inline-block; padding: 2px 8px;
  font-size: 0.8em; font-weight: 600;
  background: rgba(15,23,42,0.06);
  border: 1px solid rgba(15,23,42,0.12);
  border-radius: 6px; color: var(--accent-blue);
  vertical-align: middle;
}

.co, .info-callout {
  padding: clamp(8px, 1.5vw, 14px) clamp(12px, 2vw, 18px);
  border-radius: 10px;
  font-size: clamp(0.65rem, 1.1vw, 0.8rem);
  line-height: 1.6;
}
.co {
  background: rgba(37,99,235,0.05);
  border-left: 3px solid var(--accent-blue);
  color: var(--text-secondary);
}
.info-callout {
  background: rgba(14,165,233,0.05);
  border-left: 3px solid var(--accent-sky);
  color: var(--text-secondary);
}

.cmd {
  background: rgba(15,23,42,0.03);
  border: 1px solid rgba(15,23,42,0.08);
  border-radius: 10px;
  padding: clamp(8px, 1.5vw, 14px) clamp(12px, 2vw, 18px);
  font-family: 'DM Sans', monospace;
  font-size: clamp(0.7rem, 1.2vw, 0.9rem);
  color: var(--accent-blue); line-height: 1.6;
}
.cmd::before { content: '> '; color: var(--text-muted); }

/* Step pattern */
.step { position: relative; padding-left: 32px; }
.step::before {
  content: attr(data-step);
  position: absolute; left: 0; top: 0;
  font-size: 0.65rem; font-weight: 700;
  color: var(--accent-blue);
  text-transform: uppercase; letter-spacing: 0.1em;
}

/* Layer rows */
.layer {
  display: flex; gap: 16px;
  padding: clamp(8px, 1.2vw, 12px) 0;
  border-bottom: 1px solid rgba(15,23,42,0.06);
  align-items: flex-start;
}
.layer:last-child { border-bottom: none; }
.layer-num {
  font-family: 'Inter', sans-serif;
  font-size: 0.75rem; font-weight: 700;
  color: var(--accent-sky); min-width: 24px;
}

/* Layouts */
.cols2 { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px, 2vw, 20px); }
.cols3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(12px, 2vw, 20px); }
.cols4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(8px, 1.5vw, 16px); }

.bento {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-auto-rows: minmax(60px, auto);
  gap: clamp(8px, 1.5vw, 14px);
}
.bento .span2 { grid-column: span 2; }

/* Table */
.a-table {
  width: 100%; border-collapse: collapse;
  font-size: clamp(0.65rem, 1.1vw, 0.8rem);
}
.a-table th {
  text-align: left; padding: 8px 12px;
  border-bottom: 2px solid rgba(37,99,235,0.15);
  color: var(--accent-blue); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  font-size: clamp(0.55rem, 1vw, 0.7rem);
}
.a-table td {
  padding: 8px 12px;
  border-bottom: 1px solid rgba(15,23,42,0.06);
  color: var(--text-secondary);
}
.a-table tr:last-child td { border-bottom: none; }

/* Chapter nums */
.chapter-num {
  font-family: 'Inter', sans-serif;
  font-size: clamp(3rem, 10vw, 8rem);
  font-weight: 800;
  background: var(--gradient-text);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text; line-height: 1;
}

.a-eof {
  font-family: 'Inter', sans-serif;
  font-size: clamp(1.5rem, 4vw, 3rem);
  font-weight: 800;
  background: var(--gradient-text);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text; text-align: center;
}

/* Reveal animation */
.reveal {
  opacity: 0; transform: translateY(20px);
  transition: opacity 0.7s cubic-bezier(0.22,1,0.36,1),
              transform 0.7s cubic-bezier(0.22,1,0.36,1);
}
.slide.active .reveal { opacity: 1; transform: translateY(0); }
.reveal:nth-child(1) { transition-delay: 0.05s; }
.reveal:nth-child(2) { transition-delay: 0.15s; }
.reveal:nth-child(3) { transition-delay: 0.25s; }

/* Pagination */
#nav-dots {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 8px; align-items: center; z-index: 10;
}
.dot {
  height: 6px; border-radius: 999px; cursor: pointer; border: none;
  background: rgba(147,197,253,0.5);
  transition: background 0.4s, width 0.4s; width: 6px;
}
.dot.active { background: #2563eb; width: 28px; }

/* Counter */
#slide-counter {
  position: fixed; top: 24px; left: 36px; z-index: 10;
  font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
  color: var(--text-muted); opacity: 0.6;
}

/* Progress bar */
#progress-bar {
  position: fixed; top: 0; left: 0; height: 3px; z-index: 11;
  background: linear-gradient(90deg, var(--accent-blue), var(--accent-sky));
  width: 0%; transition: width 0.7s cubic-bezier(0.22,1,0.36,1);
}

/* Keyboard hint */
.keyboard-hint {
  position: fixed; bottom: 24px; right: 24px; z-index: 10;
  font-size: 11px; color: var(--text-muted); opacity: 0.4;
}

/* Responsive */
@media (max-height: 700px) { .a-title { font-size: clamp(1.2rem, 4vw, 2.5rem); } }
@media (max-height: 600px) { .a-title { font-size: clamp(1rem, 3.5vw, 2rem); } .nav-dots, .keyboard-hint, .decorative { display: none; } }
@media (max-width: 600px) { .cols2, .cols3, .cols4, .bento { grid-template-columns: 1fr; } .bento .span2 { grid-column: span 1; } }
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.2s !important; } }

/* ═══════════════════════════════════════════════
   PRESENT MODE
   ═══════════════════════════════════════════════ */
body.presenting { background: #000 !important; overflow: hidden !important; }
body.presenting .slide { position: fixed !important; inset: 0; width: 100vw !important; height: 100vh !important; transform-origin: center center; scroll-snap-align: none !important; display: none !important; }
body.presenting .slide.p-on { display: flex !important; z-index: 1; }
body.presenting #nav-dots,
body.presenting .keyboard-hint,
body.presenting .slide-credit { display: none !important; }
body.presenting #present-btn { display: none !important; }
body.presenting #slide-counter { display: none !important; }
body.presenting #progress-bar { display: none !important; }

.slide-credit {
  position: absolute; bottom: 8px; right: 14px;
  font-size: 9px; color: var(--text-muted);
  opacity: 0.35; pointer-events: none; z-index: 1;
  font-family: system-ui, sans-serif;
}
body.presenting .slide-credit { display: none !important; }

/* Edit mode */
.edit-hotzone {
  position: fixed; top: 0; left: 0;
  width: 80px; height: 80px; z-index: 10000; cursor: pointer;
}
.edit-toggle {
  position: fixed; top: 16px; left: 16px;
  opacity: 0; pointer-events: none;
  transition: opacity 0.3s ease; z-index: 10001;
  background: var(--card-bg); backdrop-filter: blur(12px);
  border: 1px solid var(--card-border); border-radius: 6px;
  padding: 3px 8px; font-size: 0.65rem; color: var(--text-muted); cursor: pointer;
}
.edit-toggle.show { opacity: 1; pointer-events: auto; }
.edit-toggle.active { opacity: 1; pointer-events: auto; background: var(--accent-blue); color: #fff; }

#notes-panel {
  display: none; position: fixed; bottom: 0; left: 0; right: 0;
  z-index: 9998; background: rgba(240,249,255,0.96);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid rgba(15,23,42,0.08);
}
#notes-panel.active { display: flex; flex-direction: column; }
#notes-panel-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.45rem 1.4rem; cursor: pointer; user-select: none;
}
#notes-panel-header:hover { background: rgba(15,23,42,0.02); }
#notes-panel-label {
  flex: 1; font-size: 0.58rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--text-muted);
}
#notes-collapse-btn {
  flex: 1; text-align: right;
  font-size: 1rem; color: var(--text-muted);
  background: none; border: none; cursor: pointer;
  transition: color 0.15s, transform 0.2s;
}
#notes-collapse-btn:hover { color: var(--text-primary); }
#notes-panel.collapsed #notes-collapse-btn { transform: rotate(180deg); }
#notes-textarea {
  width: 100%; height: 72px; min-height: 48px; max-height: 200px;
  resize: vertical; background: transparent; border: none; outline: none;
  color: var(--text-secondary); font-size: 0.88rem; line-height: 1.65;
  font-family: system-ui, sans-serif; padding: 0 1.4rem 0.75rem;
  caret-color: var(--accent-blue);
}
</style>
</head>
<body data-preset="Blue Sky">

<div id="orb1" class="orb" style="width:500px;height:500px;top:-10%;left:60%;"></div>
<div id="orb2" class="orb" style="width:400px;height:400px;top:40%;left:-5%;"></div>
<div id="orb3" class="orb" style="width:350px;height:350px;top:60%;left:30%;"></div>

<div id="progress-bar"></div>
<div id="slide-counter"></div>
<div class="keyboard-hint">← → or click to navigate</div>

<div id="stage"><div id="track">

<!-- Slide 1: Cover -->
<section class="slide" data-notes="Welcome! This is kai-slide-creator, a zero-dependency HTML presentation generator.">
<div class="slide-content" style="text-align: center; align-items: center;">
  <div class="reveal a-label" style="margin-bottom: 8px;">kai-slide-creator v2.16.0</div>
  <div class="reveal a-title gt" style="margin-bottom: 16px;">kai-slide-creator</div>
  <div class="reveal a-subtitle">Zero-Dependency HTML Presentation Generator</div>
  <div class="reveal" style="margin-top: clamp(20px, 3vw, 36px); display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;">
    <span class="pill">21 Presets</span>
    <span class="pill" style="color: var(--accent-indigo); border-color: rgba(79,70,229,0.15); background: rgba(79,70,229,0.08);">Zero Deps</span>
    <span class="pill" style="color: var(--accent-sky); border-color: rgba(14,165,233,0.15); background: rgba(14,165,233,0.08);">Pure Browser</span>
  </div>
</div>
<div class="cloud-layer" style="z-index: 0;">
  <div class="cloud-strip">
    <div class="cloud-group" style="left: 5%;"><div class="cloud-puff" style="width: 200px; height: 200px; opacity: 0.6;"></div></div>
    <div class="cloud-group" style="left: 25%;"><div class="cloud-puff" style="width: 160px; height: 160px; opacity: 0.5;"></div></div>
    <div class="cloud-group" style="left: 50%;"><div class="cloud-puff" style="width: 240px; height: 240px; opacity: 0.7;"></div></div>
    <div class="cloud-group" style="left: 75%;"><div class="cloud-puff" style="width: 180px; height: 180px; opacity: 0.55;"></div></div>
  </div>
</div>
</section>

<!-- Slide 2: Chapter — The Problem -->
<section class="slide" data-notes="Traditional presentation tools have many pain points.">
<div class="slide-content">
  <div class="reveal chapter-num">01</div>
  <div class="reveal" style="margin-top: -24px;">
    <div class="a-h2">Why We Need Another Presentation Tool</div>
    <div class="a-subtitle">The limits of traditional tools</div>
  </div>
  <div class="reveal" style="margin-top: clamp(16px, 2vw, 24px);">
    <div class="cols2">
      <div class="g">
        <div class="a-label" style="margin-bottom: 8px; color: var(--red);">Traditional Tool Problems</div>
        <div class="a-body" style="line-height: 2.2;">PPT templates are all the same<br>AI generates generic aesthetics<br>Exports lose design details</div>
      </div>
      <div class="g" style="border-color: rgba(37,99,235,0.30);">
        <div class="a-label" style="margin-bottom: 8px;">Our Approach</div>
        <div class="a-body" style="line-height: 2.2;">
          <span class="a-accent">Start from design presets</span><br>21 signature visual languages<br>
          <span class="a-accent">Pure HTML output</span><br>Every file runs in any browser<br>
          <span class="a-accent">In-browser editing</span><br>Edit content, save as HTML
        </div>
      </div>
    </div>
  </div>
  <div class="reveal" style="margin-top: 12px;"><div class="co">You own the content, AI owns the structure and visual expression.</div></div>
</div>
</section>

<!-- Slide 3: 21 Presets -->
<section class="slide" data-notes="21 design presets, each with a signature visual language.">
<div class="slide-content">
  <div class="reveal a-h2">21 Design Presets, Each with Signature Visual Language</div>
  <div class="reveal a-subtitle" style="margin-top: -4px;">Avoid generic AI aesthetics</div>
  <div class="reveal" style="margin-top: clamp(12px, 2vw, 20px);">
    <div class="bento">
      <div class="g span2" style="border-left: 3px solid var(--accent-blue);"><div class="a-label">Bold Signal</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Pitch deck — high contrast, bold type</div></div>
      <div class="g span2" style="border-left: 3px solid var(--accent-sky);"><div class="a-label" style="color: var(--accent-sky);">Blue Sky</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Clean tech — sky gradient, glass cards</div></div>
      <div class="g" style="border-left: 3px solid #39d353;"><div class="a-label" style="color: #39d353;">Terminal Green</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Dev tools — monospace, scan lines</div></div>
      <div class="g" style="border-left: 3px solid var(--text-muted);"><div class="a-label" style="color: var(--text-muted);">Enterprise Dark</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Data dashboard — dark panels</div></div>
      <div class="g span2" style="border-left: 3px solid var(--accent-indigo);"><div class="a-label" style="color: var(--accent-indigo);">Modern Newspaper</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Research — editorial layout</div></div>
      <div class="g span2" style="border-color: rgba(37,99,235,0.20);"><div class="a-label">Aurora Mesh</div><div class="a-body" style="font-size: clamp(0.65rem, 1vw, 0.75rem); margin-top: 4px;">Product launch — animated gradient, frosted glass</div></div>
    </div>
  </div>
</div>
</section>

<!-- Slide 4: Chapter — Core Features -->
<section class="slide" data-notes="Three core modules support the full presentation lifecycle.">
<div class="slide-content">
  <div class="reveal chapter-num">02</div>
  <div class="reveal" style="margin-top: -24px;">
    <div class="a-h2">Three Core Modules, Full-Flow Presentations</div>
    <div class="a-subtitle">Play, present, edit — all in one file</div>
  </div>
  <div class="reveal" style="margin-top: clamp(16px, 2vw, 24px);">
    <div class="cols3">
      <div class="g" style="text-align: center;"><div class="stat"><div class="num">F5</div><div class="label">Play Mode</div></div><div class="a-body" style="font-size: clamp(0.6rem, 1vw, 0.75rem); margin-top: 8px;">Fullscreen, auto-scale, smart controls</div></div>
      <div class="g" style="text-align: center;"><div class="stat"><div class="num">P</div><div class="label">Presenter Mode</div></div><div class="a-body" style="font-size: clamp(0.6rem, 1vw, 0.75rem); margin-top: 8px;">Notes, timer, page count — synced window</div></div>
      <div class="g" style="text-align: center;"><div class="stat"><div class="num">E</div><div class="label">In-Browser Edit</div></div><div class="a-body" style="font-size: clamp(0.6rem, 1vw, 0.75rem); margin-top: 8px;">Click to edit, Ctrl+S to save</div></div>
    </div>
  </div>
</div>
</section>

<!-- SLIDE 5: Three Modes -->
<section class="slide">
<div class="slide-content" style="text-align:center;align-items:center;">
  <div class="reveal a-label" style="margin-bottom:12px;">Core Modes</div>
  <div class="reveal a-h2" style="text-align:center;">Play, Present, Edit — One File</div>
  <div class="reveal" style="margin-top:clamp(20px,3vw,36px);display:flex;gap:clamp(16px,3vw,32px);justify-content:center;">
    <div class="reveal g" style="text-align:center;padding:20px;"><div class="stat"><div class="num">F5</div><div class="label">Play</div></div><div class="a-body" style="font-size:clamp(0.65rem,1vw,0.75rem);margin-top:8px;">Fullscreen, auto-scale</div></div>
    <div class="reveal g" style="text-align:center;padding:20px;"><div class="stat"><div class="num">P</div><div class="label">Present</div></div><div class="a-body" style="font-size:clamp(0.65rem,1vw,0.75rem);margin-top:8px;">Notes, timer, sync</div></div>
    <div class="reveal g" style="text-align:center;padding:20px;"><div class="stat"><div class="num">E</div><div class="label">Edit</div></div><div class="a-body" style="font-size:clamp(0.65rem,1vw,0.75rem);margin-top:8px;">contenteditable, Ctrl+S</div></div>
  </div>
</div>
</section>

<!-- SLIDE 6: Commands -->
<section class="slide">
<div class="slide-content">
  <div class="reveal a-label" style="margin-bottom:12px;">Commands</div>
  <div class="reveal a-h2">Three Commands, One Pipeline</div>
  <div class="reveal" style="margin-top:clamp(16px,2vw,24px);display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(12px,2vw,20px);">
    <div class="reveal g"><div class="a-label" style="margin-bottom:6px;">01 Plan</div><div class="cmd" style="margin-bottom:4px;">/slide-creator --plan</div><div class="a-body" style="font-size:clamp(0.65rem,1vw,0.75rem);">Generate outline</div></div>
    <div class="reveal g"><div class="a-label" style="margin-bottom:6px;">02 Generate</div><div class="cmd" style="margin-bottom:4px;">/slide-creator --generate</div><div class="a-body" style="font-size:clamp(0.65rem,1vw,0.75rem);">Build with quality checks</div></div>
    <div class="reveal g"><div class="a-label" style="margin-bottom:6px;">03 Review</div><div class="cmd" style="margin-bottom:4px;">/slide-creator --review</div><div class="a-body" style="font-size:clamp(0.65rem,1vw,0.75rem);">Inspect and fix</div></div>
  </div>
</div>
</section>

<!-- SLIDE 7: Zero Dependencies -->
<section class="slide">
<div class="slide-content" style="text-align:center;align-items:center;">
  <div class="reveal a-label" style="margin-bottom:12px;">Foundation</div>
  <div class="reveal a-h2" style="text-align:center;">Nothing Between You and the Browser</div>
  <div class="reveal" style="margin-top:clamp(20px,3vw,36px);display:flex;gap:clamp(24px,4vw,48px);justify-content:center;">
    <div class="reveal stat"><div class="num">0</div><div class="label">Dependencies</div></div>
    <div class="reveal stat"><div class="num">21</div><div class="label">Presets</div></div>
    <div class="reveal stat"><div class="num">1</div><div class="label">Requirement</div></div>
  </div>
  <div class="reveal a-body" style="text-align:center;margin-top:16px;color:var(--text-muted, var(--text-m, #64748b));">HTML, CSS, JavaScript. That is all.</div>
</div>
</section>

<!-- SLIDE 8: CTA -->
<section class="slide">
<div class="slide-content" style="text-align:center;align-items:center;">
  <div class="reveal">
    <span class="pill" style="margin-bottom:16px;">Start Now</span>
  </div>
  <div class="reveal a-title" style="margin-bottom:16px;">Start with a Preset</div>
  <div class="reveal" style="margin-top:12px;">
    <div class="cmd" style="text-align:center;font-size:clamp(0.9rem,1.5vw,1.2rem);">/slide-creator</div>
  </div>
  <div class="reveal a-subtitle" style="margin-top:12px;text-align:center;">Tell Claude to install kai-slide-creator</div>
</div>
</section>
<div class="edit-hotzone"></div>
<button class="edit-toggle" id="editToggle" title="Edit mode (E)">✏ Edit</button>
<div id="notes-panel">
  <div id="notes-panel-header">
    <div id="notes-panel-label">SPEAKER NOTES — SLIDE 1 / 8</div>
    <button id="notes-collapse-btn" title="Collapse / expand">▾</button>
  </div>
  <textarea id="notes-textarea" placeholder="Add speaker notes for this slide…"></textarea>
</div>

<script>
/* ===========================================
   BLUE SKY PRESENTATION CONTROLLER
   Unique architecture: #stage/#track + translateX
   =========================================== */
(function() {
  'use strict';
  var TOTAL = 12;
  var slides = document.querySelectorAll('.slide');
  var track = document.getElementById('track');
  var dotsContainer = document.getElementById('nav-dots');
  var counterEl = document.getElementById('slide-counter');
  var progressBar = document.getElementById('progress-bar');
  var idx = 0;
  var wState = 'idle';
  var wLastTime = 0;

  // Generate nav dots
  for (var i = 0; i < TOTAL; i++) {
    var d = document.createElement('div');
    d.className = 'dot';
    (function(j) { d.addEventListener('click', function() { goTo(j); }); })(i);
    dotsContainer.appendChild(d);
  }

  function goTo(i) {
    if (i < 0 || i >= TOTAL) return;
    idx = i;
    track.style.transform = 'translateX(-' + (idx * 100) + 'vw)';
    wState = 'animating';
    track.addEventListener('transitionend', function handler() {
      track.removeEventListener('transitionend', handler);
      wState = 'draining';
      wLastTime = 0;
    }, { once: true });
    slides.forEach(function(s, j) {
      var isActive = j === idx;
      s.classList.toggle('active', isActive);
      if (isActive) {
        s.querySelectorAll('.reveal').forEach(function(r) { r.classList.add('visible'); });
      }
    });
    // Update dots
    dotsContainer.querySelectorAll('.dot').forEach(function(dot, j) {
      dot.classList.toggle('active', j === idx);
    });
    // Update counter
    counterEl.textContent = String(idx + 1).padStart(2, '0') + ' / ' + String(TOTAL).padStart(2, '0');
    // Update progress bar
    progressBar.style.width = ((idx / (TOTAL - 1)) * 100) + '%';
    // Broadcast presenter state
    try { bc.postMessage({ type: 'state', index: idx, total: TOTAL, notes: slides[idx] ? slides[idx].dataset.notes || '' : '' }); } catch(e) {}
  }

  // Keyboard
  document.addEventListener('keydown', function(e) {
    if (e.target.getAttribute('contenteditable')) return;
    if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); goTo(idx + 1); }
    else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); goTo(idx - 1); }
    else if (e.key === 'Home') { e.preventDefault(); goTo(0); }
    else if (e.key === 'End') { e.preventDefault(); goTo(TOTAL - 1); }
    else if (e.key === 'p' || e.key === 'P') { window.open(location.href.split('?')[0] + '?presenter', 'slide-presenter-' + Date.now(), 'width=1100,menubar=no,toolbar=no'); }
    else if (e.key === 'F5' || e.key === 'F') { if (window.enterPresent) window.enterPresent(); }
    else if (e.key === 'Escape') { if (window.pm && pm.active) pm.exit(); }
  });

  // Touch
  var tx = 0;
  document.addEventListener('touchstart', function(e) { tx = e.touches[0].clientX; }, { passive: true });
  document.addEventListener('touchend', function(e) { var d = tx - e.changedTouches[0].clientX; if (Math.abs(d) > 50) goTo(idx + (d > 0 ? 1 : -1)); });

  // Wheel (gap-based momentum detection)
  document.addEventListener('wheel', function(e) {
    var now = Date.now();
    if (wState === 'animating') return;
    if (wState === 'draining') {
      var gap = now - wLastTime;
      wLastTime = now;
      if (gap < 80) return; // momentum tail — ignore
      wState = 'idle'; // new intentional swipe
    }
    // wState === 'idle'
    goTo(idx + (e.deltaY > 0 ? 1 : -1));
  }, { passive: true });

  // BroadcastChannel for presenter sync
  var bc = new BroadcastChannel('slide-creator-presenter');
  bc.addEventListener('message', function(e) {
    if (e.data.type === 'request-state') {
      bc.postMessage({ type: 'state', index: idx, total: TOTAL, notes: slides[idx] ? slides[idx].dataset.notes || '' : '' });
    }
  });

  // Editor
  function setupEditor() {
    var hotzone = document.querySelector('.edit-hotzone');
    var btn = document.getElementById('editToggle');
    var panel = document.getElementById('notes-panel');
    var textarea = document.getElementById('notes-textarea');
    var label = document.getElementById('notes-panel-label');
    var editing = false;
    var hideTimeout = null;

    hotzone.addEventListener('mouseenter', function() { clearTimeout(hideTimeout); btn.classList.add('show'); });
    hotzone.addEventListener('mouseleave', function() { hideTimeout = setTimeout(function() { if (!editing) btn.classList.remove('show'); }, 400); });
    btn.addEventListener('mouseenter', function() { clearTimeout(hideTimeout); });
    btn.addEventListener('mouseleave', function() { hideTimeout = setTimeout(function() { if (!editing) btn.classList.remove('show'); }, 400); });

    function toggleEdit() {
      editing = !editing;
      btn.classList.toggle('active', editing);
      btn.textContent = editing ? '\u2713 Done' : '\u270F Edit';
      document.querySelectorAll('.a-title, .a-h2, .a-body, .a-subtitle, .pill, .cmd, .a-label, .a-eof, .stat .num, .stat .label, .step, .layer .a-body').forEach(function(el) {
        el.contentEditable = editing ? 'true' : 'false';
      });
      panel.classList.toggle('active', editing);
      if (editing) {
        label.textContent = 'SPEAKER NOTES — SLIDE ' + (idx + 1) + ' / ' + TOTAL;
        textarea.value = slides[idx] ? slides[idx].dataset.notes || '' : '';
      }
    }

    hotzone.addEventListener('click', toggleEdit);
    btn.addEventListener('click', toggleEdit);

    document.getElementById('notes-panel-header').addEventListener('click', function() {
      panel.classList.toggle('collapsed');
    });

    textarea.addEventListener('input', function() {
      if (slides[idx]) slides[idx].dataset.notes = textarea.value;
      try { bc.postMessage({ type: 'state', index: idx, total: TOTAL, notes: textarea.value }); } catch(e) {}
    });

    document.addEventListener('keydown', function(e) {
      if ((e.key === 'e' || e.key === 'E') && !e.target.getAttribute('contenteditable') && e.target.tagName !== 'TEXTAREA') {
        toggleEdit();
      }
      if ((e.ctrlKey || e.metaKey) && e.key === 's' && editing) {
        e.preventDefault();
        var html = document.documentElement.outerHTML;
        var a = document.createElement('a');
        a.href = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
        a.download = document.title + '.html';
        a.click();
      }
    });
  }
  setupEditor();

  // Present Mode
  class PresentMode {
    constructor() { this.active = false; this.W = 1440; this.H = 900; }
    enter() {
      this.active = true;
      document.body.classList.add('presenting');
      document.documentElement.requestFullscreen?.().catch(function() {});
      window.addEventListener('resize', this.calc.bind(this));
      this.calc();
      this.sync();
      var self = this;
      this._origGoTo = goTo;
      goTo = function(i) { self._origGoTo(i); if (self.active) self.sync(); };
    }
    exit() {
      this.active = false;
      document.body.classList.remove('presenting');
      window.removeEventListener('resize', this.calc.bind(this));
      slides.forEach(function(s) { s.classList.remove('p-on'); });
      if (document.fullscreenElement) document.exitFullscreen?.();
      goTo = this._origGoTo;
    }
    calc() {
      var sw = window.innerWidth, sh = window.innerHeight;
      var scale = Math.min(sw / this.W, sh / this.H);
      document.body.style.setProperty('--pdw', this.W + 'px');
      document.body.style.setProperty('--pdh', this.H + 'px');
      document.body.style.setProperty('--ps', scale);
      document.body.style.setProperty('--pox', ((sw - this.W * scale) / 2) + 'px');
      document.body.style.setProperty('--poy', ((sh - this.H * scale) / 2) + 'px');
    }
    sync() {
      slides.forEach(function(s, i) { s.classList.remove('p-on'); if (i === idx) s.classList.add('p-on'); });
      var ctr = document.getElementById('present-counter');
      if (ctr) ctr.textContent = (idx + 1) + ' / ' + TOTAL;
    }
  }

  window.enterPresent = function() { if (!window.pm) window.pm = new PresentMode(); window.pm.enter(); };

  // Presenter mode (?presenter)
  if (location.search.includes('presenter')) {
    document.body.innerHTML = '<div style="padding:20px;font-family:system-ui,sans-serif;color:#0f172a;background:#f0f9ff;height:100vh;overflow:auto;"><h2 style="color:#2563eb;font-size:1.2rem;margin-bottom:16px;">Presenter Mode</h2><div id="p-slide" style="background:var(--card-bg);border:1px solid var(--card-border);border-radius:12px;padding:12px;margin-bottom:12px;font-size:0.8rem;">Loading...</div><div id="p-notes" style="background:var(--card-bg);border:1px solid var(--card-border);border-radius:12px;padding:12px;font-size:0.75rem;color:#334155;min-height:100px;">No notes yet.</div><div id="p-timer" style="margin-top:12px;font-size:0.9rem;color:#0ea5e9;">00:00</div><div style="margin-top:12px;display:flex;gap:8px;"><button onclick="bc.postMessage({type:\'nav\',dir:-1})" style="background:var(--card-bg);border:1px solid var(--card-border);color:#0f172a;padding:6px 14px;border-radius:6px;cursor:pointer;font-family:inherit;">Previous</button><button onclick="bc.postMessage({type:\'nav\',dir:1})" style="background:var(--card-bg);border:1px solid var(--card-border);color:#0f172a;padding:6px 14px;border-radius:6px;cursor:pointer;font-family:inherit;">Next</button></div></div>';
    var pS = document.getElementById('p-slide'), pN = document.getElementById('p-notes'), pT = document.getElementById('p-timer');
    var pb = new BroadcastChannel('slide-creator-presenter');
    pb.addEventListener('message', function(e) {
      if (e.data.type === 'state') {
        pS.textContent = 'Slide ' + (e.data.index + 1) + ' / ' + e.data.total;
        pN.textContent = e.data.notes || 'No notes for this slide.';
      }
    });
    pb.postMessage({ type: 'request-state' });
    var sec = 0; setInterval(function() { sec++; pT.textContent = String(Math.floor(sec/60)).padStart(2,'0') + ':' + String(sec%60).padStart(2,'0'); }, 1000);
  }

  // Init
  slides[0].classList.add('active');
  slides[0].querySelectorAll('.reveal').forEach(function(r) { r.classList.add('visible'); });
  dotsContainer.querySelector('.dot').classList.add('active');
  counterEl.textContent = '01 / ' + String(TOTAL).padStart(2, '0');
  progressBar.style.width = '0%';

  // Watermark injection
  var lastSlide = slides[slides.length - 1];
  if (lastSlide) {
    var credit = document.createElement('div');
    credit.className = 'slide-credit';
    credit.textContent = 'By kai-slide-creator v2.16.0 \u00b7 Blue Sky';
    lastSlide.appendChild(credit);
  }
})();
</script>

</body>
</html>

path: demos/blue-sky-zh.html
content: <!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>kai-slide-creator — 零依赖 HTML 演示文稿生成器</title>
<style>

:root { --slide-count: 14; }

/* ═══════════════════════════════════════════════
   BLUE SKY DESIGN TOKENS (untouched from starter)
   ═══════════════════════════════════════════════ */
:root {
  --bg-from:        #f0f9ff;
  --bg-to:          #e0f2fe;
  --card-bg:        rgba(255, 255, 255, 0.70);
  --card-border:    rgba(255, 255, 255, 0.90);
  --text-primary:   #0f172a;
  --text-secondary: #334155;
  --text-muted:     #64748b;
  --accent-blue:    #2563eb;
  --accent-sky:     #0ea5e9;
  --accent-indigo:  #4f46e5;
  --gradient-text:  linear-gradient(135deg, #1e3a8a 0%, #3b82f6 100%);
  --grid-color:     rgba(14, 165, 233, 0.06);
  --orb1-color: rgba(96,  165, 250, 0.35);
  --orb2-color: rgba(99,  102, 241, 0.25);
  --orb3-color: rgba(14,  165, 233, 0.30);
  --amber:     #f59e0b;  --amber-bg: rgba(251,191,36,0.12);
  --red:       #ef4444;  --red-bg:   rgba(239,68,68,0.10);
  --green:     #10b981;  --green-bg: rgba(16,185,129,0.10);
}

::selection { background: #bfdbfe; color: #1e3a8a; }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
  width: 100%; height: 100%; overflow: hidden;
  font-family: 'DM Sans', 'Inter', system-ui, -apple-system, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  background: linear-gradient(160deg, var(--bg-from), var(--bg-to));
}

/* Noise */
body::before {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
}

/* Grid */
body::after {
  content: ''; position: fixed; inset: 0; pointer-events: none; z-index: 0;
  background-image:
    linear-gradient(var(--grid-color) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
  background-size: 40px 40px;
  -webkit-mask-image: radial-gradient(ellipse 70% 70% at 50% 50%, black 40%, transparent 100%);
  mask-image:         radial-gradient(ellipse 70% 70% at 50% 50%, black 40%, transparent 100%);
}

/* Orbs */
.orb {
  position: fixed; border-radius: 50%; pointer-events: none; z-index: 0;
  filter: blur(80px);
  transition: top 1.2s cubic-bezier(0.22,1,0.36,1), left 1.2s cubic-bezier(0.22,1,0.36,1),
              width 1.2s cubic-bezier(0.22,1,0.36,1), height 1.2s cubic-bezier(0.22,1,0.36,1);
}
#orb1 { background: var(--orb1-color); }
#orb2 { background: var(--orb2-color); }
#orb3 { background: var(--orb3-color); }

/* Track */
#stage { position: fixed; inset: 0; overflow: hidden; z-index: 1; }
#track {
  display: flex;
  width: calc(100vw * var(--slide-count));
  height: 100%;
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
  will-change: transform;
}
.slide {
  width: 100vw; flex-shrink: 0; height: 100vh;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: clamp(28px, 4vw, 56px) clamp(32px, 5vw, 80px);
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, var(--bg-from), var(--bg-to));
}

/* Glassmorphism */
.g {
  background: var(--card-bg);
  border: 1px solid var(--card-border);
  border-radius: 20px;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  box-shadow: 0 10px 40px -10px rgba(37,99,235,0.12), 0 1px 3px rgba(0,0,0,0.04),
              0 1px 0 rgba(255,255,255,0.8) inset;
}

/* Gradient text */
.gt {
  background: var(--gradient-text);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Cloud ambient */
.cloud {
  position: absolute; border-radius: 50%; pointer-events: none;
  filter: blur(60px);
  animation: cloud-drift 8s ease-in-out infinite;
}
@keyframes cloud-drift {
  0%, 100% { transform: translateY(0) scale(1); }
  50%       { transform: translateY(-20px) scale(1.05); }
}

/* Cloud bank (cover) */
.cloud-layer {
  position: absolute; bottom: 0; left: 0; width: 100%; height: 75%;
  pointer-events: none; overflow: hidden; z-index: 2;
}
.cloud-strip {
  position: absolute; bottom: 0; left: 0; display: flex; height: 100%;
  animation: cloud-scroll 30s linear infinite;
}
.cloud-strip.fast { animation-duration: 18s; }
@keyframes cloud-scroll {
  from { transform: translateX(0); } to { transform: translateX(-1920px); }
}
.cloud-group { position: absolute; bottom: 0; }
.cloud-puff  { position: absolute; background: white; border-radius: 50%; }

/* Pagination */
#nav-dots {
  position: fixed; bottom: 24px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 8px; align-items: center; z-index: 10;
}
.dot {
  height: 6px; border-radius: 999px; cursor: pointer; border: none;
  background: rgba(147,197,253,0.5);
  transition: background 0.4s, width 0.4s; width: 6px;
}
.dot.active { background: #2563eb; width: 28px; }

/* Counter */
#slide-counter {
  position: fixed; top: 24px; left: 36px; z-index: 10;
  font-size: 12px; font-weight: 700; letter-spacing: 0.1em;
  color: var(--text-muted);
  font-family: 'SF Mono', 'Fira Code', monospace;
}

/* Typography */
h1 { font-size: clamp(2.2rem, 5.5vw, 4.5rem); font-weight: 800; line-height: 1.1; letter-spacing: -0.02em; }
h2 { font-size: clamp(1.6rem, 3.5vw, 2.6rem);  font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; }
h3 { font-size: clamp(1.0rem, 2.0vw, 1.5rem);  font-weight: 700; line-height: 1.3; }
h4 { font-size: clamp(0.9rem, 1.6vw, 1.2rem);  font-weight: 600; }
p, li { font-size: clamp(0.82rem, 1.4vw, 1.05rem); line-height: 1.6; color: var(--text-secondary); }

/* Layout */
.cols2 { display: grid; grid-template-columns: 1fr 1fr;        gap: 16px; width: 100%; }
.cols3 { display: grid; grid-template-columns: 1fr 1fr 1fr;    gap: 14px; width: 100%; }
.cols4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; width: 100%; }
.bento { display: grid; grid-template-columns: repeat(4, 1fr); grid-auto-rows: 160px; gap: 12px; width: 100%; }
.bento .g { padding: 16px 18px; overflow: hidden; }
.bento .span2 { grid-column: span 2; }
.bento .span3 { grid-column: span 3; }
.bento .row2  { grid-row: span 2; }

.divider {
  width: 56px; height: 3px; border-radius: 2px;
  background: linear-gradient(90deg, #2563eb, #0ea5e9);
  margin: 8px 0 14px;
}

/* Chapter slide */
.slide.chapter { background: linear-gradient(135deg, rgba(219,234,254,0.7), rgba(224,242,254,0.9)); text-align: center; }
.chapter-num {
  font-size: clamp(5rem, 14vw, 11rem); font-weight: 900; line-height: 1;
  background: linear-gradient(135deg, rgba(37,99,235,0.15), rgba(14,165,233,0.28));
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text; margin-bottom: -16px;
}

/* Components */
.pill {
  display: inline-flex; align-items: center;
  padding: 4px 14px; border-radius: 999px;
  font-size: 12px; font-weight: 600; letter-spacing: 0.05em;
  background: rgba(37,99,235,0.10); color: var(--accent-blue);
  border: 1px solid rgba(37,99,235,0.20);
}
.pill.green { background: rgba(16,185,129,0.12); color: #065f46; border-color: rgba(16,185,129,0.25); }

.stat {
  font-size: clamp(2.8rem, 7vw, 5.5rem); font-weight: 900; line-height: 1;
  background: var(--gradient-text);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text; letter-spacing: -0.02em;
}

ul.bl { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 7px; }
ul.bl li { display: flex; align-items: flex-start; gap: 8px; color: var(--text-secondary); }
ul.bl li::before { content: '\25B8'; color: var(--accent-sky); flex-shrink: 0; margin-top: 2px; }

.step {
  width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(135deg, #2563eb, #0ea5e9);
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-weight: 800; font-size: 0.88rem;
}

.layer {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 14px 18px; border-radius: 14px;
  background: rgba(255,255,255,0.60);
  border: 1px solid rgba(255,255,255,0.90);
  border-left: 4px solid var(--accent-sky);
}

.ctable { width: 100%; border-collapse: collapse; font-size: 0.86rem; }
.ctable th { background: rgba(37,99,235,0.10); color: var(--accent-blue); font-weight: 700; padding: 10px 14px; text-align: left; }
.ctable td { padding: 9px 14px; border-bottom: 1px solid rgba(14,165,233,0.12); vertical-align: top; }
.ctable tr:last-child td { border-bottom: none; }
.ctable .yes { color: #10b981; font-weight: 600; }
.ctable .no  { color: #94a3b8; }

.info {
  background: rgba(37,99,235,0.07); border: 1px solid rgba(37,99,235,0.20);
  border-left: 4px solid var(--accent-blue);
  border-radius: 12px; padding: 12px 16px;
  font-size: 0.84rem; color: var(--text-secondary); line-height: 1.6;
}
.co {
  background: var(--amber-bg); border: 1px solid rgba(245,158,11,0.28);
  border-left: 4px solid var(--amber);
  border-radius: 12px; padding: 12px 16px;
  font-size: 0.84rem; color: var(--text-secondary); line-height: 1.6;
}
.co strong { color: #92400e; }

/* Keyboard key badge */
kbd {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 2px 8px; border-radius: 6px; font-size: 0.78rem; font-weight: 700;
  font-family: 'SF Mono', 'Fira Code', monospace;
  background: rgba(255,255,255,0.85); color: var(--text-primary);
  border: 1px solid rgba(37,99,235,0.20);
  box-shadow: 0 2px 0 rgba(37,99,235,0.15);
  letter-spacing: 0.02em;
}

/* Inline code */
code {
  font-family: 'SF Mono', 'Fira Code', monospace;
  background: rgba(37,99,235,0.08); color: #1e3a8a;
  padding: 1px 6px; border-radius: 5px; font-size: 0.88em;
}

/* Command block */
.cmd {
  background: rgba(15,23,42,0.06); border: 1px solid rgba(37,99,235,0.15);
  border-radius: 10px; padding: 10px 16px;
  font-family: 'SF Mono', 'Fira Code', monospace;
  font-size: 0.85rem; color: #1e3a8a;
  letter-spacing: 0.01em;
}

/* Entrance animation */
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(22px); }
  to   { opacity: 1; transform: translateY(0); }
}
.slide.active > * { animation: fadeUp 0.5s cubic-bezier(0.22,1,0.36,1) both; }
.slide.active > *:nth-child(2) { animation-delay: 0.06s; }
.slide.active > *:nth-child(3) { animation-delay: 0.12s; }
.slide.active > *:nth-child(4) { animation-delay: 0.18s; }
.slide.active > *:nth-child(5) { animation-delay: 0.24s; }
.slide.active > *:nth-child(6) { animation-delay: 0.30s; }

@media (prefers-reduced-motion: reduce) {
  #track, .orb, .cloud { transition: none !important; animation: none !important; }
}

/* ═══════════════════════════════════════════════════════
   PRESENT BUTTON — hover bottom-right to reveal
   ═══════════════════════════════════════════════════════ */
#present-btn {
  position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 9997;
  width: 44px; height: 44px; border-radius: 50%;
  background: rgba(255,255,255,0.78); color: rgba(15,23,42,0.65);
  border: 1.5px solid rgba(37,99,235,0.22);
  font-size: 1rem; cursor: pointer;
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity 0.2s; pointer-events: none;
}
body:hover #present-btn { opacity: 1; pointer-events: auto; }
#present-btn:hover { background: rgba(255,255,255,0.96); }

#present-counter {
  display: none; position: fixed; bottom: 1.4rem; left: 50%;
  transform: translateX(-50%); z-index: 9997;
  font-size: 0.65rem; letter-spacing: 0.14em; font-family: system-ui, sans-serif;
  color: rgba(255,255,255,0.55);
}

/* ═══════════════════════════════════════════════════════
   PRESENTATION MODE — CSS scaling, 1440x900 canvas
   ═══════════════════════════════════════════════════════ */
body.presenting { background: #000 !important; overflow: hidden !important; }
body.presenting .slide { position: fixed !important; inset: 0; width: 100vw !important; height: 100vh !important; transform-origin: center center; scroll-snap-align: none !important; display: none !important; }
body.presenting .slide.p-on { display: flex !important; }
body.presenting #present-btn { display: none !important; }
body.presenting #present-counter { display: block; }
body.presenting #nav-dots,
body.presenting #slide-counter,
body.presenting .orb,
body.presenting #notes-panel { display: none !important; }

/* ═══════════════════════════════════════════════════════
   EDIT MODE — hotzone top-left, toggle button, notes panel
   ═══════════════════════════════════════════════════════ */
.edit-hotzone {
  position: fixed; top: 0; left: 0;
  width: 80px; height: 80px; z-index: 10000; cursor: pointer;
}
.edit-toggle {
  position: fixed; top: 16px; left: 16px;
  padding: 6px 16px; border-radius: 999px; border: 1px solid rgba(37,99,235,0.20);
  font-size: 12px; font-weight: 600; letter-spacing: 0.04em; font-family: inherit;
  background: rgba(255,255,255,0.88); color: var(--text-muted); cursor: pointer;
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 2px 12px rgba(37,99,235,0.08);
  opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 10001;
}
.edit-toggle.show { opacity: 1; pointer-events: auto; }
.edit-toggle.active { opacity: 1; pointer-events: auto; background: var(--accent-blue); color: #fff; border-color: var(--accent-blue); }

[contenteditable] { border-radius: 4px; cursor: text; outline: none; }
[contenteditable]:hover { outline: 1px dashed rgba(37,99,235,0.35); background: rgba(219,234,254,0.20); }
[contenteditable]:focus { outline: 2px solid rgba(37,99,235,0.55); background: rgba(219,234,254,0.30); }

#notes-panel {
  display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 9998;
  background: rgba(8,8,18,0.94);
  backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
  border-top: 1px solid rgba(255,255,255,0.08);
}
#notes-panel.active { display: flex; flex-direction: column; }
#notes-panel-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.45rem 1.4rem; cursor: pointer; user-select: none;
}
#notes-panel-header:hover { background: rgba(255,255,255,0.03); }
#notes-panel-label {
  flex: 1; font-size: 0.58rem; letter-spacing: 0.16em;
  text-transform: uppercase; color: rgba(255,255,255,0.3);
}
#notes-drag-hint { flex: 1; display: flex; justify-content: center; }
#notes-drag-hint::after {
  content: ''; display: block; width: 36px; height: 3px;
  border-radius: 2px; background: rgba(255,255,255,0.25);
}
#notes-panel-header:hover #notes-drag-hint::after { background: rgba(255,255,255,0.45); }
#notes-collapse-btn {
  flex: 1; text-align: right; font-size: 1rem; color: rgba(255,255,255,0.4);
  background: none; border: none; cursor: pointer; transition: color 0.15s, transform 0.2s;
}
#notes-collapse-btn:hover { color: rgba(255,255,255,0.85); }
#notes-panel.collapsed #notes-collapse-btn { transform: rotate(180deg); }
#notes-body { padding: 0 1.4rem 0.75rem; }
#notes-panel.collapsed #notes-body { display: none; }
#notes-textarea {
  width: 100%; height: 72px; min-height: 48px; max-height: 200px;
  resize: vertical; background: transparent; border: none; outline: none;
  color: rgba(255,255,255,0.75); font-size: 0.88rem; line-height: 1.65;
  font-family: system-ui, sans-serif; caret-color: var(--accent-blue);
}
#notes-textarea::placeholder { color: rgba(255,255,255,0.18); }

</style>
</head>
<body data-preset="Blue Sky">

<div class="orb" id="orb1"></div>
<div class="orb" id="orb2"></div>
<div class="orb" id="orb3"></div>
<div id="slide-counter">01 / 14</div>
<div id="nav-dots"></div>

<div class="edit-hotzone"></div>
<button class="edit-toggle" id="editToggle" title="Edit mode (E)">&#x270F; Edit</button>

<div id="notes-panel">
  <div id="notes-panel-header">
    <div id="notes-panel-label">SPEAKER NOTES — SLIDE 1 / 14</div>
    <div id="notes-drag-hint"></div>
    <button id="notes-collapse-btn" title="Collapse / expand">&#x25BE;</button>
  </div>
  <div id="notes-body">
    <textarea id="notes-textarea" placeholder="Add speaker notes for this slide…"></textarea>
  </div>
</div>

<div id="stage">
<div id="track">

  <!-- ══════════════════════════════════════════
       01 — COVER
       ══════════════════════════════════════════ -->
  <section class="slide cover" style="overflow:hidden;" data-notes="欢迎！kai-slide-creator 是零依赖 HTML 演示文稿生成器 — 21 种设计预设，纯浏览器运行。">

    <svg width="0" height="0" style="position:absolute;pointer-events:none;">
      <defs>
        <filter id="cloud-filter">
          <feTurbulence type="fractalNoise" baseFrequency="0.012" numOctaves="4" seed="5" result="noise"/>
          <feDisplacementMap in="SourceGraphic" in2="noise" scale="60" xChannelSelector="R" yChannelSelector="G"/>
        </filter>
      </defs>
    </svg>

    <!-- Ambient orbs -->
    <div style="position:absolute;width:50%;height:60%;top:5%;left:-10%;border-radius:50%;background:rgba(96,165,250,0.28);filter:blur(90px);pointer-events:none;z-index:1;"></div>
    <div style="position:absolute;width:55%;height:65%;top:8%;right:-12%;border-radius:50%;background:rgba(14,165,233,0.22);filter:blur(100px);pointer-events:none;z-index:1;"></div>
    <div style="position:absolute;width:35%;height:40%;bottom:32%;left:30%;border-radius:50%;background:rgba(99,102,241,0.18);filter:blur(80px);pointer-events:none;z-index:1;"></div>

    <!-- Scrolling cloud bank -->
    <div class="cloud-layer" style="filter:url(#cloud-filter);">
      <div class="cloud-strip">
        <div style="position:relative;width:1920px;height:100%;flex-shrink:0;">
          <div class="cloud-group" style="left:192px;bottom:-54px;width:576px;height:216px;opacity:0.65;">
            <div class="cloud-puff" style="bottom:0;left:10%;width:288px;height:288px;filter:blur(18px);"></div>
            <div class="cloud-puff" style="bottom:22px;left:30%;width:384px;height:384px;filter:blur(22px);"></div>
            <div class="cloud-puff" style="bottom:-22px;left:60%;width:346px;height:346px;filter:blur(18px);"></div>
          </div>
          <div class="cloud-group" style="left:1100px;bottom:-86px;width:480px;height:194px;opacity:0.5;">
            <div class="cloud-puff" style="bottom:0;left:0%;width:230px;height:230px;filter:blur(14px);"></div>
            <div class="cloud-puff" style="bottom:32px;left:40%;width:346px;height:346px;filter:blur(20px);"></div>
            <div class="cloud-puff" style="bottom:11px;left:70%;width:288px;height:288px;filter:blur(16px);"></div>
          </div>
        </div>
        <div style="position:relative;width:1920px;height:100%;flex-shrink:0;">
          <div class="cloud-group" style="left:192px;bottom:-54px;width:576px;height:216px;opacity:0.65;">
            <div class="cloud-puff" style="bottom:0;left:10%;width:288px;height:288px;filter:blur(18px);"></div>
            <div class="cloud-puff" style="bottom:22px;left:30%;width:384px;height:384px;filter:blur(22px);"></div>
            <div class="cloud-puff" style="bottom:-22px;left:60%;width:346px;height:346px;filter:blur(18px);"></div>
          </div>
          <div class="cloud-group" style="left:1100px;bottom:-86px;width:480px;height:194px;opacity:0.5;">
            <div class="cloud-puff" style="bottom:0;left:0%;width:230px;height:230px;filter:blur(14px);"></div>
            <div class="cloud-puff" style="bottom:32px;left:40%;width:346px;height:346px;filter:blur(20px);"></div>
            <div class="cloud-puff" style="bottom:11px;left:70%;width:288px;height:288px;filter:blur(16px);"></div>
          </div>
        </div>
      </div>
      <div class="cloud-strip fast" style="z-index:2;">
        <div style="position:relative;width:1920px;height:100%;flex-shrink:0;">
          <div class="cloud-group" style="left:580px;bottom:-70px;width:420px;height:170px;opacity:0.42;">
            <div class="cloud-puff" style="bottom:0;left:5%;width:200px;height:200px;filter:blur(12px);"></div>
            <div class="cloud-puff" style="bottom:20px;left:35%;width:300px;height:300px;filter:blur(16px);"></div>
            <div class="cloud-puff" style="bottom:-15px;left:65%;width:250px;height:250px;filter:blur(13px);"></div>
          </div>
        </div>
        <div style="position:relative;width:1920px;height:100%;flex-shrink:0;">
          <div class="cloud-group" style="left:580px;bottom:-70px;width:420px;height:170px;opacity:0.42;">
            <div class="cloud-puff" style="bottom:0;left:5%;width:200px;height:200px;filter:blur(12px);"></div>
            <div class="cloud-puff" style="bottom:20px;left:35%;width:300px;height:300px;filter:blur(16px);"></div>
            <div class="cloud-puff" style="bottom:-15px;left:65%;width:250px;height:250px;filter:blur(13px);"></div>
          </div>
        </div>
      </div>
    </div>

    <div style="text-align:center;position:relative;z-index:10;">
      <span class="pill" style="margin-bottom:20px;display:inline-block;">21 种设计预设</span>
      <h1 class="gt" style="margin-bottom:14px;">kai-slide-creator</h1>
      <p style="font-size:1.1rem;max-width:560px;margin:0 auto 28px;">
        零依赖 HTML 演示文稿生成器<br>从设计预设开始，让内容自己说话
      </p>
      <div style="display:flex;gap:14px;justify-content:center;flex-wrap:wrap;">
        <div class="g" style="padding:16px 28px;text-align:center;">
          <div class="stat" style="font-size:2.8rem;">21</div>
          <p style="font-size:0.78rem;margin-top:2px;">设计预设</p>
        </div>
        <div class="g" style="padding:16px 28px;text-align:center;">
          <div class="stat" style="font-size:2.8rem;">0</div>
          <p style="font-size:0.78rem;margin-top:2px;">依赖</p>
        </div>
        <div class="g" style="padding:16px 28px;text-align:center;">
          <div class="stat" style="font-size:2.8rem;">100%</div>
          <p style="font-size:0.78rem;margin-top:2px;">浏览器运行</p>
        </div>
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       02 — CHAPTER: 问题
       ══════════════════════════════════════════ -->
  <section class="slide chapter" data-notes="解释传统幻灯片工具的摩擦点。让对比表发言 — 停留在 PPTX 兼容性和模板千篇一律的痛点。">
    <div class="chapter-num">01</div>
    <h2 class="gt">为什么需要另一种演示工具</h2>
    <p style="font-size:1rem;color:var(--text-muted);margin-top:8px;">现有工具的局限</p>
  </section>

  <!-- ══════════════════════════════════════════
       03 — 问题详情：对比卡片
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="深入对比传统工具与 slide-creator 的差异。重点在 AI 生成通用审美、导出丢失设计细节这些问题。">
    <div style="max-width:860px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">为什么选择 slide-creator</span>
      <h2 class="gt">为什么需要另一种演示工具</h2>
      <div class="divider"></div>
      <div class="cols2">
        <div class="g" style="padding:22px 24px;border-left:4px solid #ef4444;">
          <h4 style="color:#b91c1c;margin-bottom:10px;">传统工具的痛点</h4>
          <ul class="bl">
            <li>PPT 模板千篇一律，找不到差异化表达</li>
            <li>AI 生成通用审美，缺乏签名视觉语言</li>
            <li>导出丢失设计细节，跨设备展示不一致</li>
          </ul>
        </div>
        <div class="g" style="padding:22px 24px;border-left:4px solid #10b981;">
          <h4 style="color:#065f46;margin-bottom:10px;">slide-creator 的思路</h4>
          <ul class="bl">
            <li>从设计预设出发，每种都有签名视觉语言</li>
            <li>纯 HTML 运行，任何浏览器打开都一样</li>
            <li>浏览器内可编辑，修改即刻持久化</li>
          </ul>
        </div>
      </div>
      <div class="info" style="margin-top:14px;">
        <strong>核心理念：</strong>你拥有内容，AI 拥有结构和视觉表现。
        你没有提供的数字永远不会被编造 —— 只用 <code>[INSERT VALUE]</code> 占位符。
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       04 — 21 种设计预设
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="21 个主题跨越 4 个类别 — 内容类型路由自动建议最适合的。Blue Sky 对业务内容最通用。">
    <div style="max-width:940px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">设计预设</span>
      <h2 class="gt">
        <span class="title-line" style="display:block;">21 种设计预设</span>
        <span class="title-line" style="display:block;">每种都有签名视觉语言</span>
      </h2>
      <div class="divider"></div>
      <div class="bento" style="grid-template-columns:repeat(2,1fr);grid-auto-rows:120px;">
        <div class="g span2" style="padding:20px;">
          <div style="font-size:1.6rem;margin-bottom:8px;">Bold Signal</div>
          <h4 style="margin-bottom:4px;">商业路演</h4>
          <p style="font-size:0.8rem;">高对比度深色背景，大号渐变标题，投资者/VC 路演首选</p>
        </div>
        <div class="g span2" style="padding:20px;">
          <div style="font-size:1.6rem;margin-bottom:8px;">Blue Sky</div>
          <h4 style="margin-bottom:4px;">清新技术</h4>
          <p style="font-size:0.8rem;">天空渐变 + 玻璃卡片 + 网格叠加，适合产品介绍和技术分享</p>
        </div>
        <div class="g" style="padding:16px 18px;">
          <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
            <div style="width:10px;height:10px;border-radius:50%;background:#10b981;flex-shrink:0;"></div>
            <h4 style="font-size:0.88rem;">Terminal Green</h4>
          </div>
          <p style="font-size:0.76rem;color:var(--text-muted);">开发工具 / API 文档</p>
        </div>
        <div class="g" style="padding:16px 18px;">
          <div style="display:flex;align-items:center;gap:8px;margin-bottom:10px;">
            <div style="width:10px;height:10px;border-radius:50%;background:#0f172a;flex-shrink:0;"></div>
            <h4 style="font-size:0.88rem;">Enterprise Dark</h4>
          </div>
          <p style="font-size:0.76rem;color:var(--text-muted);">数据看板 / KPI 报告</p>
        </div>
        <div class="g span2" style="padding:20px;">
          <div style="font-size:1.6rem;margin-bottom:8px;">Modern Newspaper</div>
          <h4 style="margin-bottom:4px;">研究报告</h4>
          <p style="font-size:0.8rem;">窄列排版 + 衬线字体，适合长篇思想领导力内容</p>
        </div>
        <div class="g span2" style="padding:20px;">
          <div style="font-size:1.6rem;margin-bottom:8px;">Aurora Mesh</div>
          <h4 style="margin-bottom:4px;">产品发布</h4>
          <p style="font-size:0.8rem;">极光渐变叠加 + 光球效果，SaaS 产品发布首选</p>
        </div>
      </div>
      <div class="info" style="margin-top:10px;padding:9px 14px;">
        深色 &middot; 5 个主题 &nbsp;&nbsp; 浅色 &middot; 5 个主题 &nbsp;&nbsp; 专业 &middot; 4 个主题 &nbsp;&nbsp; v2.x 新增 &middot; 7 个主题
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       05 — CHAPTER: 核心功能
       ══════════════════════════════════════════ -->
  <section class="slide chapter" data-notes="三个核心模块：播放模式、演讲者模式、浏览器内编辑。一个文件全部搞定。">
    <div class="chapter-num">02</div>
    <h2 class="gt">三个核心模块，支撑全流程演示</h2>
    <p style="font-size:1rem;color:var(--text-muted);margin-top:8px;">播放、演讲、编辑 —— 一个文件全部搞定</p>
  </section>

  <!-- ══════════════════════════════════════════
       06 — 播放模式
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="按 F5 或点击播放按钮进入全屏。幻灯片缩放以填充任何屏幕。无需安装任何播放器。">
    <div style="max-width:840px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">播放</span>
      <h2 class="gt">播放模式 — 按 F5 即全屏</h2>
      <div class="divider"></div>
      <div style="display:flex;flex-direction:column;gap:11px;">
        <div class="layer">
          <div class="step">1</div>
          <div>
            <h4 style="margin-bottom:4px;"><kbd>F5</kbd> 或点击播放按钮进入全屏</h4>
            <p>自动隐藏控制栏和计数器，纯净无干扰的演示体验。</p>
          </div>
        </div>
        <div class="layer">
          <div class="step">2</div>
          <div>
            <h4 style="margin-bottom:4px;">幻灯片自动缩放适配屏幕</h4>
            <p>保持设计比例不变形，1440x900 画布 CSS 缩放到任何尺寸。</p>
          </div>
        </div>
        <div class="layer">
          <div class="step">3</div>
          <div>
            <h4 style="margin-bottom:4px;">方向键 / 滚轮 / 触摸滑动翻页</h4>
            <p>弹簧物理过渡动画 <code>cubic-bezier(0.22, 1, 0.36, 1)</code>，自然不生硬。</p>
          </div>
        </div>
        <div class="layer">
          <div class="step">4</div>
          <div>
            <h4 style="margin-bottom:4px;">无需安装任何播放器</h4>
            <p>浏览器就是投影仪 —— 零依赖，打开即用。</p>
          </div>
        </div>
      </div>
      <div class="info" style="margin-top:14px;">
        零依赖播放体验，任何设备打开即用
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       07 — 演讲者模式
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="按 P 键打开演讲者窗口，与主窗口同步翻页。观众看不到备注，你能看到一切。">
    <div style="max-width:860px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">演讲</span>
      <h2 class="gt">演讲者模式 — P 键打开同步窗口</h2>
      <div class="divider"></div>
      <div class="cols2">
        <div class="g" style="padding:22px 24px;">
          <h4 style="margin-bottom:10px;">观众看到的</h4>
          <ul class="bl">
            <li>全屏幻灯片</li>
            <li>纯净无干扰</li>
            <li>自动缩放适配</li>
          </ul>
        </div>
        <div class="cmd" style="padding:18px 20px;">
          <div style="margin-bottom:8px;">
            <strong>演讲者窗口</strong>
          </div>
          <div style="display:flex;flex-direction:column;gap:6px;">
            <div style="display:flex;align-items:center;gap:8px;">
              <kbd>P</kbd> <span style="color:var(--text-secondary);font-size:0.82rem;">打开同步窗口</span>
            </div>
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="color:var(--text-secondary);font-size:0.82rem;">演讲者备注</span>
            </div>
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="color:var(--text-secondary);font-size:0.82rem;">倒计时器</span>
            </div>
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="color:var(--text-secondary);font-size:0.82rem;">当前页 / 总页数</span>
            </div>
            <div style="display:flex;align-items:center;gap:8px;">
              <span style="color:var(--text-secondary);font-size:0.82rem;">翻页控制</span>
            </div>
          </div>
        </div>
      </div>
      <div class="co" style="margin-top:14px;">
        <strong>按 P 键打开</strong>，与主窗口同步翻页。观众永远看不到你的备注。
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       08 — 浏览器内编辑
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="演示编辑模式：按 E 键进入，点击任何文字修改，Ctrl+S 保存。不用打开 PowerPoint 或 Figma。">
    <div style="max-width:860px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">编辑</span>
      <h2 class="gt">浏览器内编辑 — 所见即所改</h2>
      <div class="divider"></div>
      <div style="display:flex;flex-direction:column;gap:11px;">
        <div class="layer">
          <div style="font-size:1.5rem;flex-shrink:0;">&#x270F;</div>
          <div>
            <h4 style="margin-bottom:4px;">按 <kbd>E</kbd> 键进入编辑模式</h4>
            <p>所有文字变为可编辑，点击任何标题、段落或列表项即可修改。</p>
          </div>
        </div>
        <div class="layer">
          <div style="font-size:1.5rem;flex-shrink:0;">&#x1F4BB;</div>
          <div>
            <h4 style="margin-bottom:4px;">直接点击文字修改</h4>
            <p>实时预览效果，无需切换到 PowerPoint 或 Figma。</p>
          </div>
        </div>
        <div class="layer">
          <div style="font-size:1.5rem;flex-shrink:0;">&#x1F4BE;</div>
          <div>
            <h4 style="margin-bottom:4px;"><kbd>Ctrl+S</kbd> 保存为 HTML</h4>
            <p>修改持久化到文件，另存为网页即可捕获更改。</p>
          </div>
        </div>
      </div>
      <div class="info" style="margin-top:14px;">
        编辑模式默认开启，无需额外配置
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       09 — CHAPTER: 使用方法
       ══════════════════════════════════════════ -->
  <section class="slide chapter" data-notes="一条命令，三分钟出稿。从规划到生成，全程自动化。">
    <div class="chapter-num">03</div>
    <h2 class="gt">一条命令，三分钟出稿</h2>
    <p style="font-size:1rem;color:var(--text-muted);margin-top:8px;">从规划到生成，全程自动化</p>
  </section>

  <!-- ══════════════════════════════════════════
       10 — 使用方法详情
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="三个命令：--plan 生成可编辑的结构化大纲。--generate 将大纲转换为样式化的 HTML。--review 对已有 HTML 执行检查点。">
    <div style="max-width:860px;width:100%;">
      <div class="cols3">
        <div class="g" style="padding:18px 20px;">
          <div class="cmd" style="margin-bottom:10px;">/slide-creator --plan</div>
          <ul class="bl">
            <li>生成 BRIEF.json IR</li>
            <li>可编辑后再生成</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <div class="cmd" style="margin-bottom:10px;">/slide-creator --generate</div>
          <ul class="bl">
            <li>从 BRIEF.json 生成 HTML</li>
            <li>执行 16 项质量校验</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <div class="cmd" style="margin-bottom:10px;">/slide-creator --review</div>
          <ul class="bl">
            <li>对已有 HTML 执行检查点</li>
            <li>自动修复常见问题</li>
          </ul>
        </div>
      </div>
      <div class="divider"></div>
      <div class="info">
        也可直接给内容 + 风格，立即生成：<code>/slide-creator "你的主题" Blue Sky</code>
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       11 — 内容类型与风格推荐
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="什么内容配什么风格，一眼匹配。自动建议适合内容类型的风格系列。">
    <div style="max-width:860px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">风格推荐</span>
      <h2 class="gt">什么内容配什么风格，一眼匹配</h2>
      <div class="divider"></div>
      <div class="cols2">
        <div class="g" style="padding:18px 20px;">
          <h4 style="margin-bottom:10px;">数据报告 / KPI 看板</h4>
          <ul class="bl">
            <li>Data Story</li>
            <li>Enterprise Dark</li>
            <li>Swiss Modern</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <h4 style="margin-bottom:10px;">商业路演 / VC Deck</h4>
          <ul class="bl">
            <li>Bold Signal</li>
            <li>Aurora Mesh</li>
            <li>Enterprise Dark</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <h4 style="margin-bottom:10px;">开发工具 / API 文档</h4>
          <ul class="bl">
            <li>Terminal Green</li>
            <li>Neon Cyber</li>
            <li>Neo-Retro Dev</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <h4 style="margin-bottom:10px;">研究 / 思想领导力</h4>
          <ul class="bl">
            <li>Modern Newspaper</li>
            <li>Paper &amp; Ink</li>
            <li>Swiss Modern</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <h4 style="margin-bottom:10px;">创意 / 个人品牌</h4>
          <ul class="bl">
            <li>Vintage Editorial</li>
            <li>Split Pastel</li>
            <li>Neo-Brutalism</li>
          </ul>
        </div>
        <div class="g" style="padding:18px 20px;">
          <h4 style="margin-bottom:10px;">产品发布 / SaaS</h4>
          <ul class="bl">
            <li>Aurora Mesh</li>
            <li>Glassmorphism</li>
            <li>Electric Studio</li>
          </ul>
        </div>
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       12 — 技术特点
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="零依赖，纯浏览器运行。不依赖服务器、npm、构建工具。生成的 HTML 文件完整自包含。">
    <div style="max-width:860px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">技术架构</span>
      <h2 class="gt">零依赖，纯浏览器运行</h2>
      <div class="divider"></div>
      <div class="cols4" style="gap:10px;align-items:start;">
        <div class="g" style="padding:22px;text-align:center;">
          <div class="stat">0</div>
          <p style="font-size:0.78rem;margin-top:6px;">npm 包依赖</p>
        </div>
        <div class="g" style="padding:22px;text-align:center;">
          <div class="stat">3</div>
          <p style="font-size:0.78rem;margin-top:6px;">核心文件 HTML/CSS/JS</p>
        </div>
        <div class="g" style="padding:22px;text-align:center;">
          <div class="stat">&#x221E;</div>
          <p style="font-size:0.78rem;margin-top:6px;">可扩展场景</p>
        </div>
        <div class="g" style="padding:22px;text-align:center;">
          <div class="stat">1</div>
          <p style="font-size:0.78rem;margin-top:6px;">浏览器即运行环境</p>
        </div>
      </div>
      <div class="info" style="margin-top:14px;">
        生成的 HTML 文件完整自包含，双击即可在浏览器中打开
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       13 — 版本历史
       ══════════════════════════════════════════ -->
  <section class="slide" data-notes="持续迭代，每周都在变强。从 v1.x 到 v2.16，功能不断增强。">
    <div style="max-width:820px;width:100%;">
      <span class="pill" style="margin-bottom:14px;display:inline-block;">版本历史</span>
      <h2 class="gt">持续迭代，每周都在变强</h2>
      <div class="divider"></div>
      <div style="display:flex;flex-direction:column;gap:11px;">
        <div class="layer">
          <div class="step">1</div>
          <div>
            <h4 style="margin-bottom:4px;">v1.x — 基本框架</h4>
            <p>播放模式、编辑模式、首批 7 个设计预设</p>
          </div>
        </div>
        <div class="layer">
          <div class="step">2</div>
          <div>
            <h4 style="margin-bottom:4px;">v1.5 — 风格扩展</h4>
            <p>21 个预设、Cloud Bank Hero、Orbital Rings</p>
          </div>
        </div>
        <div class="layer">
          <div class="step">3</div>
          <div>
            <h4 style="margin-bottom:4px;">v2.x — 智能生成</h4>
            <p>AI 规划、Review 系统、PPT 转换</p>
          </div>
        </div>
        <div class="layer">
          <div class="step">4</div>
          <div>
            <h4 style="margin-bottom:4px;">v2.16 — 渲染引擎升级</h4>
            <p>主题卡片、组件修饰符、26 项回归测试</p>
          </div>
        </div>
      </div>
      <div class="co" style="margin-top:14px;">
        <strong>GitHub + ClawHub 双渠道发布</strong>，持续迭代中
      </div>
    </div>
  </section>

  <!-- ══════════════════════════════════════════
       14 — CLOSING
       ══════════════════════════════════════════ -->
  <section class="slide cover" style="overflow:hidden;" data-notes="行动呼吁：在 Claude Code 中输入 /slide-creator 以开始使用。问答时间。">

    <div class="cloud" style="width:440px;height:440px;background:rgba(147,197,253,0.30);top:-160px;right:-90px;animation-duration:11s;"></div>
    <div class="cloud" style="width:320px;height:320px;background:rgba(196,233,251,0.35);bottom:-100px;left:-60px;animation-duration:9s;animation-delay:-2s;"></div>

    <div style="text-align:center;position:relative;z-index:10;max-width:680px;">
      <span class="pill" style="margin-bottom:20px;display:inline-block;">开始使用</span>
      <h2 class="gt" style="margin-bottom:16px;">
        <span class="title-line" style="display:block;">从设计预设开始</span>
        <span class="title-line" style="display:block;">让内容自己说话</span>
      </h2>
      <div class="g" style="padding:28px 36px;">
        <p style="margin-bottom:18px;font-size:0.95rem;">打开 Claude Code 并输入：</p>
        <div class="cmd" style="text-align:left;margin-bottom:12px;">/slide-creator "你的主题" Blue Sky</div>
        <div class="cmd" style="text-align:left;margin-bottom:18px;">/slide-creator --plan "你的主题" &nbsp;&nbsp;<span style="color:#64748b;"># 先查看大纲</span></div>
        <div style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:8px;">
          <div style="text-align:center;">
            <kbd>E</kbd>
            <p style="font-size:0.72rem;margin-top:4px;color:var(--text-muted);">编辑模式</p>
          </div>
          <div style="text-align:center;">
            <kbd>F5</kbd>
            <p style="font-size:0.72rem;margin-top:4px;color:var(--text-muted);">全屏</p>
          </div>
          <div style="text-align:center;">
            <kbd>&rarr;</kbd>
            <p style="font-size:0.72rem;margin-top:4px;color:var(--text-muted);">下一页</p>
          </div>
          <div style="text-align:center;">
            <kbd>&larr;</kbd>
            <p style="font-size:0.72rem;margin-top:4px;color:var(--text-muted);">上一页</p>
          </div>
        </div>
      </div>
    </div>
  </section>

</div><!-- /#track -->
</div><!-- /#stage -->

<script>
/* ═══════════════════════════════════════════════════════
   ORB POSITIONS — one entry per slide [l%,t%,sz, l%,t%,sz, l%,t%,sz]
   ═══════════════════════════════════════════════════════ */
const orbPositions = [
  /* 01 Cover    */ [-15,-20,500,  80,-10,400,  50, 65,350],
  /* 02 Ch:问题  */ [-20, 40,460,  75,-15,400,  55, 70,300],
  /* 03 对比     */ [-10, 30,460,  72,-10,380,  40, 70,280],
  /* 04 预设     */ [ 60,-10,440, -10, 50,380,  40, 75,300],
  /* 05 Ch:功能  */ [-20, 40,460,  78,-15,400,  50, 72,300],
  /* 06 播放     */ [-15, 35,500,  76,  0,420,  55, 65,290],
  /* 07 演讲者   */ [ 65, 15,440, -15, 60,380,  40, 80,300],
  /* 08 编辑     */ [-10, 25,480,  80,-10,400,  50, 70,310],
  /* 09 Ch:用法  */ [-20, 40,460,  75,-15,400,  55, 70,300],
  /* 10 命令     */ [ 60,-15,440, -10, 55,380,  45, 75,290],
  /* 11 内容类型 */ [-15, 30,480,  78, -5,400,  50, 68,300],
  /* 12 技术     */ [-10, 25,480,  80,-10,400,  50, 70,310],
  /* 13 版本     */ [-15, 30,480,  78, -5,400,  50, 68,300],
  /* 14 Closing  */ [-15,-20,500,  80,-10,400,  50, 60,350],
];

/* ═══════════════════════════════════════════════════════
   PRESENTER WINDOW (activated when URL contains ?presenter)
   Shows speaker notes, elapsed timer, and nav controls.
   Syncs with the main window via BroadcastChannel.
   ═══════════════════════════════════════════════════════ */
if (new URLSearchParams(location.search).has('presenter')) {
  document.title = 'Presenter — ' + document.title;
  document.body.innerHTML = `
  <style>
    * { box-sizing: border-box; margin: 0; }
    body { background: #111; color: #fff; font-family: system-ui, sans-serif; }
    #pv { display: grid; grid-template-columns: 1fr 220px; gap: 1rem; padding: 1.25rem; align-items: start; }
    .pv-panel { background: #1e1e1e; border-radius: 12px; padding: 1.25rem; }
    #pv-label { font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; color: #555; margin-bottom: 0.6rem; }
    #pv-notes { font-size: 1.05rem; line-height: 1.85; color: #d0d0d0; }
    #pv-right { display: flex; flex-direction: column; gap: 1rem; }
    #pv-nav { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
    .pv-arrow { width: 32px; height: 32px; border-radius: 6px; border: 1px solid #333; background: #2a2a2a; color: #aaa; font-size: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.15s, color 0.15s; flex-shrink: 0; }
    .pv-arrow:hover { background: #383838; color: #fff; }
    #pv-counter { text-align: center; flex: 1; }
    #pv-num { font-size: 1.6rem; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1; }
    #pv-of { color: #555; font-size: 0.75rem; margin-top: 0.2rem; }
    #pv-timer-box { text-align: center; }
    #pv-timer-label { font-size: 0.6rem; letter-spacing: 0.18em; text-transform: uppercase; color: #555; margin-bottom: 0.3rem; }
    #pv-timer { font-size: 1.5rem; font-weight: 700; font-family: monospace; font-variant-numeric: tabular-nums; }
  </style>
  <div id="pv">
    <div class="pv-panel">
      <div id="pv-label">Speaker Notes</div>
      <div id="pv-notes">Waiting for main window…</div>
    </div>
    <div id="pv-right">
      <div class="pv-panel">
        <div id="pv-nav">
          <button class="pv-arrow" id="pv-prev">&larr;</button>
          <div id="pv-counter">
            <div id="pv-num">—</div>
            <div id="pv-of">/ —</div>
          </div>
          <button class="pv-arrow" id="pv-next">&rarr;</button>
        </div>
      </div>
      <div class="pv-panel" id="pv-timer-box">
        <div id="pv-timer-label">Elapsed</div>
        <div id="pv-timer">0:00</div>
      </div>
    </div>
  </div>`;

  const ch = new BroadcastChannel('slide-creator-presenter');
  let startTime = null;

  const pv = document.getElementById('pv');
  let lastH = 0, roTimer = null;
  new ResizeObserver(() => {
    clearTimeout(roTimer);
    roTimer = setTimeout(() => {
      const h = Math.ceil(pv.getBoundingClientRect().height);
      if (h === lastH) return;
      lastH = h;
      const chrome = window.outerHeight - window.innerHeight;
      window.resizeTo(window.outerWidth, Math.max(260, h + chrome + 4));
    }, 40);
  }).observe(pv);

  ch.addEventListener('message', e => {
    if (e.data.type !== 'state') return;
    if (!startTime) startTime = Date.now();
    document.getElementById('pv-notes').textContent = e.data.notes || '(no notes for this slide)';
    document.getElementById('pv-num').textContent = e.data.index + 1;
    document.getElementById('pv-of').textContent = `/ ${e.data.total}`;
  });
  ch.postMessage({ type: 'request-state' });

  setInterval(() => {
    if (!startTime) return;
    const s = Math.floor((Date.now() - startTime) / 1000);
    document.getElementById('pv-timer').textContent =
      `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`;
  }, 1000);

  document.getElementById('pv-prev').addEventListener('click', () => ch.postMessage({ type: 'nav-prev' }));
  document.getElementById('pv-next').addEventListener('click', () => ch.postMessage({ type: 'nav-next' }));
  document.addEventListener('keydown', e => {
    if (e.key === 'ArrowRight' || e.key === ' ') ch.postMessage({ type: 'nav-next' });
    else if (e.key === 'ArrowLeft') ch.postMessage({ type: 'nav-prev' });
  });

} else {

/* ═══════════════════════════════════════════════════════
   MAIN NAVIGATION
   ═══════════════════════════════════════════════════════ */
const slides = Array.from(document.querySelectorAll('#track .slide'));
const TOTAL  = slides.length;
const track  = document.getElementById('track');
const dotsEl = document.getElementById('nav-dots');
const ctrEl  = document.getElementById('slide-counter');
const o1 = document.getElementById('orb1');
const o2 = document.getElementById('orb2');
const o3 = document.getElementById('orb3');
let idx  = 0;
let wState = 'idle';
let wLastTime = 0;

// Build dots
for (let i = 0; i < TOTAL; i++) {
  const dot = document.createElement('button');
  dot.className = 'dot';
  dot.setAttribute('aria-label', `第 ${i + 1} 页`);
  dot.addEventListener('click', () => goTo(i));
  dotsEl.appendChild(dot);
}

function goTo(i) {
  idx = Math.max(0, Math.min(i, TOTAL - 1));
  wState = 'animating';
  track.style.transform = `translateX(${-idx * 100}vw)`;
  track.addEventListener('transitionend', function handler() {
    track.removeEventListener('transitionend', handler);
    wState = 'draining';
    wLastTime = 0;
  }, { once: true });
  updateDots();
  updateCounter();
  updateNotes();
  moveOrbs();
}

function updateDots() {
  dotsEl.querySelectorAll('.dot').forEach((d, i) => {
    d.classList.toggle('active', i === idx);
  });
}

function updateCounter() {
  const n = String(idx + 1).padStart(2, '0');
  const t = String(TOTAL).padStart(2, '0');
  ctrEl.textContent = `${n} / ${t}`;
}

function moveOrbs() {
  const pos = orbPositions[idx] || orbPositions[0];
  o1.style.left = pos[0] + '%'; o1.style.top = pos[1] + '%'; o1.style.width = pos[2] + 'px'; o1.style.height = pos[2] + 'px';
  o2.style.left = pos[3] + '%'; o2.style.top = pos[4] + '%'; o2.style.width = pos[5] + 'px'; o2.style.height = pos[5] + 'px';
  o3.style.left = pos[6] + '%'; o3.style.top = pos[7] + '%'; o3.style.width = pos[8] + 'px'; o3.style.height = pos[8] + 'px';
}

// Keyboard
document.addEventListener('keydown', e => {
  if (e.target.getAttribute('contenteditable')) return;
  if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); goTo(idx + 1); }
  else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); goTo(idx - 1); }
  else if (e.key === 'Home') { e.preventDefault(); goTo(0); }
  else if (e.key === 'End') { e.preventDefault(); goTo(TOTAL - 1); }
  else if (e.key === 'p' || e.key === 'P') {
    const url = location.href.split('?')[0] + '?presenter';
    window.open(url, 'slide-presenter-' + Date.now(), 'width=1100,menubar=no,toolbar=no');
  }
  else if (e.key === 'F5' || e.key === 'F') {
    if (window.enterPresent) window.enterPresent();
  }
});

// Touch
let tx = 0;
document.addEventListener('touchstart', e => { tx = e.touches[0].clientX; }, { passive: true });
document.addEventListener('touchend', e => {
  const d = tx - e.changedTouches[0].clientX;
  if (Math.abs(d) > 50) goTo(idx + (d > 0 ? 1 : -1));
});

// Wheel (gap-based momentum detection)
document.addEventListener('wheel', e => {
  const now = Date.now();
  if (wState === 'animating') return;
  if (wState === 'draining') {
    const gap = now - wLastTime;
    wLastTime = now;
    if (gap < 80) return; // momentum tail — ignore
    wState = 'idle'; // new intentional swipe
  }
  // wState === 'idle'
  goTo(idx + (e.deltaY > 0 ? 1 : -1));
}, { passive: true });

// BroadcastChannel (sync with presenter window)
const bc = new BroadcastChannel('slide-creator-presenter');
bc.addEventListener('message', e => {
  if (e.data.type === 'request-state') {
    bc.postMessage({ type: 'state', index: idx, total: TOTAL, notes: slides[idx]?.dataset.notes || '' });
  }
});

function updateNotes() {
  document.getElementById('notes-panel-label').textContent = `SPEAKER NOTES — SLIDE ${idx + 1} / ${TOTAL}`;
  document.getElementById('notes-textarea').value = slides[idx]?.dataset.notes || '';
  bc.postMessage({ type: 'state', index: idx, total: TOTAL, notes: slides[idx]?.dataset.notes || '' });
}

// Init
moveOrbs();
updateDots();
updateCounter();
slides[0]?.classList.add('active');
slides[0]?.querySelectorAll('.reveal').forEach(r => r.classList.add('visible'));

// ═══════════════════════════════════════════════════════
// EDIT MODE
// ═══════════════════════════════════════════════════════
function setupEditor() {
  const hotzone = document.querySelector('.edit-hotzone');
  const btn = document.getElementById('editToggle');
  const panel = document.getElementById('notes-panel');
  let editing = false;

  function saveFile() {
    const html = document.documentElement.outerHTML;
    const blob = new Blob([html], { type: 'text/html' });
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    a.download = document.title + '.html';
    a.click();
    URL.revokeObjectURL(a.href);
  }

  hotzone.addEventListener('click', () => {
    editing = !editing;
    btn.classList.toggle('active', editing);
    btn.classList.add('show');
    document.querySelectorAll('#track h1, #track h2, #track h3, #track h4, #track p, #track li, #track span.pill, #track .cmd').forEach(el => {
      el.contentEditable = editing ? 'true' : 'false';
    });
  });

  btn.addEventListener('click', () => {
    editing = !editing;
    btn.classList.toggle('active', editing);
    document.querySelectorAll('#track h1, #track h2, #track h3, #track h4, #track p, #track li, #track span.pill, #track .cmd').forEach(el => {
      el.contentEditable = editing ? 'true' : 'false';
    });
  });

  document.addEventListener('keydown', e => {
    if (e.key === 'e' || e.key === 'E') {
      if (e.target.getAttribute('contenteditable')) return;
      hotzone.click();
    }
    if ((e.ctrlKey || e.metaKey) && e.key === 's') {
      if (editing) {
        e.preventDefault();
        saveFile();
      }
    }
  });

  // Notes panel
  const header = document.getElementById('notes-panel-header');
  header.addEventListener('click', () => panel.classList.toggle('collapsed'));
}
setupEditor();

// ═══════════════════════════════════════════════════════
// PRESENT MODE
// ═══════════════════════════════════════════════════════
class PresentMode {
  constructor() {
    this.presenting = false;
    this.W = 1440;
    this.H = 900;
    this.setup();
  }
  setup() {
    document.addEventListener('keydown', e => {
      if (this.presenting && (e.key === 'Escape' || e.key === 'F5')) {
        e.preventDefault();
        this.exit();
      }
      if (this.presenting && (e.key === 'ArrowRight' || e.key === ' ')) {
        e.preventDefault();
        goTo(idx + 1);
      }
      if (this.presenting && (e.key === 'ArrowLeft')) {
        e.preventDefault();
        goTo(idx - 1);
      }
    });
  }
  enter() {
    this.presenting = true;
    document.body.classList.add('presenting');
    this.calc();
    window.addEventListener('resize', () => this.calc());
    this.syncSlides();
  }
  exit() {
    this.presenting = false;
    document.body.classList.remove('presenting');
    window.removeEventListener('resize', () => this.calc());
    slides.forEach(s => s.classList.remove('p-on'));
    slides[idx]?.classList.add('active');
  }
  calc() {
    const sw = window.innerWidth;
    const sh = window.innerHeight;
    const scale = Math.min(sw / this.W, sh / this.H);
    const pw = this.W * scale;
    const ph = this.H * scale;
    const ox = (sw - pw) / 2;
    const oy = (sh - ph) / 2;
    document.body.style.setProperty('--pdw', this.W + 'px');
    document.body.style.setProperty('--pdh', this.H + 'px');
    document.body.style.setProperty('--ps', scale);
    document.body.style.setProperty('--pox', ox + 'px');
    document.body.style.setProperty('--poy', oy + 'px');
  }
  syncSlides() {
    slides.forEach((s, i) => {
      s.classList.remove('active');
      s.classList.toggle('p-on', i === idx);
    });
  }
}

function enterPresent() {
  if (!window.pm) window.pm = new PresentMode();
  window.pm.enter();
}

// Present button
const presentBtn = document.getElementById('present-btn');
if (presentBtn) {
  presentBtn.addEventListener('click', () => enterPresent());
}

const pm = new PresentMode();

// Update slides on navigation in present mode
const origGoTo = goTo;
goTo = function(i) {
  origGoTo(i);
  if (pm.presenting) pm.syncSlides();
};

// Watermark injection
var lastSlide = slides[slides.length - 1];
if (lastSlide) {
  var credit = document.createElement('div');
  credit.className = 'slide-credit';
  credit.textContent = 'By kai-slide-creator v2.27.0 · Blue Sky';
  lastSlide.appendChild(credit);
}

}
</script>

</body>
</html>


path: demos/mode-paths/auto-BRIEF.json
content: {
  "schema_version": 1,
  "brief_id": "slide-creator-auto-demo",
  "mode": "auto",
  "language": "zh-CN",
  "title": "Slide Creator",
  "audience": "正在评估是否采用 slide-creator 的产品经理和开发者",
  "desired_action": "用真实内容试跑第一套 deck",
  "deck": {
    "deck_type": "product-demo",
    "page_count": 8,
    "output_format": "html-slides"
  },
  "style": {
    "preset": "Blue Sky",
    "tone": "清爽、可信、偏产品介绍",
    "visual_density": "medium"
  },
  "content": {
    "source_policy": "distill-only",
    "must_include": [
      "slide-creator 是什么",
      "工作流足够短",
      "浏览器原生输出"
    ],
    "must_avoid": [
      "泛泛的开场",
      "连续重复布局",
      "与 thesis 无关的背景信息"
    ]
  },
  "narrative": {
    "thesis": "slide-creator 把从 prompt 到演示 deck 的关键步骤压缩成一个轻量可复用工作流。",
    "page_roles": [
      "cover",
      "problem",
      "solution",
      "workflow",
      "style-discovery",
      "output-contract",
      "best-fit",
      "closing"
    ],
    "slides": [
      {
        "slide_number": 1,
        "role": "cover",
        "title": "Slide Creator",
        "key_point": "从 prompt 到可演示 HTML deck",
        "visual": "hero statement"
      },
      {
        "slide_number": 2,
        "role": "problem",
        "title": "好内容经常被糟糕的演示稿拖累",
        "key_point": "模板老旧、排版不稳、改一版成本高",
        "visual": "comparison cards"
      },
      {
        "slide_number": 3,
        "role": "solution",
        "title": "slide-creator 让 deck 生成更稳定",
        "key_point": "先提炼 BRIEF，再生成 HTML",
        "visual": "flow diagram"
      },
      {
        "slide_number": 4,
        "role": "workflow",
        "title": "典型路径足够短",
        "key_point": "BRIEF.json 是执行真相源",
        "visual": "timeline"
      },
      {
        "slide_number": 5,
        "role": "style-discovery",
        "title": "风格选择是 wow moment",
        "key_point": "先看 3 个预览，看图做决定",
        "visual": "preview strip"
      },
      {
        "slide_number": 6,
        "role": "output-contract",
        "title": "输出不是一次性截图",
        "key_point": "浏览器原生运行、可播放、默认可编辑",
        "visual": "capability badges"
      },
      {
        "slide_number": 7,
        "role": "best-fit",
        "title": "适合高质量日常演示场景",
        "key_point": "产品发布、内部汇报、技术分享都能复用",
        "visual": "use case cards"
      },
      {
        "slide_number": 8,
        "role": "closing",
        "title": "先出一版，再精修",
        "key_point": "默认快，关键 deck 再锁质量",
        "visual": "closing CTA"
      }
    ]
  },
  "runtime": {
    "editing_mode": true,
    "presenter_mode": true,
    "watermark_mode": "injected-last-slide",
    "export_intent": "none"
  },
  "plan_view": {
    "emit_planning_view": false,
    "planning_view_path": "PLANNING.md"
  },
  "timing": {
    "estimate": {
      "plan": "1-2 min",
      "generate": "1-3 min",
      "validate": "<1 min",
      "polish": "0-2 min",
      "total": "3-6 min"
    },
    "actual": {
      "plan": "0m 00s",
      "generate": "0m 00s",
      "validate": "0m 00s",
      "polish": "0m 00s",
      "total": "0m 00s"
    }
  },
  "notes": ""
}


path: evals/baselines/2026-05-17/live-codex/explicit-generate/style-rubric.json
content: {
  "case_id": "explicit-generate",
  "overall_pass": true,
  "score": 88,
  "checks": [
    {
      "id": "narrative_arc",
      "pass": true,
      "score": 5,
      "notes": "The deck moves from KPI momentum to rollout proof, residency risk, and the next-week expansion decision."
    },
    {
      "id": "visual_rhythm",
      "pass": true,
      "score": 4,
      "notes": "The Data Story structure is varied enough, though two adjacent slides share a similar insight-card rhythm."
    },
    {
      "id": "evidence_fidelity",
      "pass": true,
      "score": 5,
      "notes": "Activation, latency, staged cohorts, incidents, residency risk, and decision facts are preserved without invented values."
    },
    {
      "id": "no_ai_slop",
      "pass": true,
      "score": 4,
      "notes": "No placeholders or raw IR appear; minor polish issues remain in a chart label and one title line break."
    },
    {
      "id": "preset_appropriateness",
      "pass": true,
      "score": 5,
      "notes": "Data Story is a good fit for this KPI-heavy rollout decision."
    },
    {
      "id": "speaker_support",
      "pass": true,
      "score": 4,
      "notes": "All slides include useful speaker notes, but the notes are concise rather than fully speaker-ready."
    }
  ],
  "summary": "Live deck is complete and faithful, with minor polish and visual rhythm issues that should remain visible in the baseline."
}


path: evals/baselines/2026-05-17/preset-surface-phase1/skill-runs/negative-html-export/trace.normalized.json
content: {
  "runner": "fixture",
  "trace_format_version": "normalized-v1",
  "tool_calls": [],
  "shell_commands": [],
  "failed_shell_commands": [],
  "read_paths": [
    "SKILL.md"
  ],
  "write_paths": [],
  "artifact_paths": [],
  "input_tokens": 5200,
  "output_tokens": 850,
  "wall_ms": 14000,
  "skill_evidence": {
    "skill_contract_read": true,
    "slide_flow_observed": false,
    "brief_observed": false,
    "strict_validate_observed": false
  },
  "runner_warnings": [
    "routing_check_only"
  ]
}

path: evals/baselines/2026-05-17/preset-surface-phase1/support-blue-sky-fixture/packet.json
content: {
  "preset": "Blue Sky",
  "preset_support_tier": "production",
  "quality_tier": null,
  "latency_ms": {
    "packet": 0.0,
    "render": 0.0,
    "assemble": 0.0
  }
}

path: evals/baselines/2026-05-17/preset-surface-phase1/support-paper-ink-fixture/packet.json
content: {
  "preset": "Paper & Ink",
  "preset_support_tier": "supported",
  "quality_tier": null,
  "latency_ms": {
    "packet": 0.0,
    "render": 0.0,
    "assemble": 0.0
  }
}

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