Skill: feeicn/slide-writer (version 1.0.0)

--- name: slide-writer display_name: Slide-Writer description: 把想法、大纲、文档或草稿变成结构清晰、设计精良的企业级 HTML 演示文稿。 version: 1.0.0 author: Feei homepage: https://github.com/FeeiCN/slide-writer repository: https://github.com/FeeiCN/slide-writer license: MIT tags: - presentation - slides - ppt - keynote - html - storytelling - enterprise entry: SKILL.md language: zh-CN compatible_with: - claude-code - codex --- # Slide-Writer 把任意输入（想法、大纲、草稿文档、会议纪要）转化为企业级 HTML 演示文稿，自动匹配所在公司的品牌主题。 ## 核心原则 1. **品牌一致** — 从请求中自动识别公司，应用对应品牌色主题；内置 14 家互联网公司主题。 2. **内容为王** — 自动润色每句话：去冗余、精简、让表达更有力。 3. **不溢出** — 每张幻灯片必须在 100vh 内完整呈现，内容多则分页，绝不出现滚动条。 4. **单文件交付** — 输出单个 `.html` 文件，CSS/JS 全部内联，零依赖，浏览器直接打开。 --- ## Phase 0：准备 ### 自动更新（第一步，必须执行） ```bash git pull --ff-only ``` 成功 → 静默继续；失败（无网络 / 有冲突）→ 静默忽略，继续使用当前版本。 ### 模式识别 - **Mode A：全新制作** — 从主题/大纲/草稿创建，进入 Phase 1。 - **A1：内容压缩** — 用户提供了演讲稿、大纲或草稿（输入字数 ≥ 200 字），目标是提炼精简，应用演讲稿转PPT规则和密度上限。 - **A2：主题创作** — 用户只提供了主题/标题（输入字数 < 200 字），目标是**主动生成丰富内容**，充分展开每个子话题，**不受演讲稿压缩规则约束**，卡片描述上限放宽至 120 字，页数默认 10–15 页。 - **Mode B：增强改稿** — 在现有 HTML 上修改或扩充内容： 1. 完整读取现有 HTML，识别所有 `<section>` 的 `id`、`class`、标题，输出当前幻灯片清单 2. **范围判断**：修改 ≤ 3 张 → 精确改稿；修改 > 30% 或新增 > 原有 → 建议用 Mode A 重新生成 3. 识别原有主题（用户未要求更换则保留） 4. 按用户描述（序号、标题关键词）定位目标 `<section>`，**确认后再操作** 5. 只改被点名的 `<section>`，新增页插入逻辑最合适的位置 6. 未被点名的幻灯片保持原样，不做"顺手优化" 7. 改完检查：目录议程项数与内容页数是否一致？章节逻辑是否连贯？不一致则列出，询问用户是否同步更新 8. 执行与 Mode A 相同的质量检查 ### 主题检测 读取 [themes/_index.md](themes/_index.md)，从用户请求提取公司关键词，优先级从高到低： 1. 用户明确指定的公司名、产品名 2. 署名中的部门/团队名称 3. 内容中出现的公司名 - 识别到 → 记录主题 ID 和 `themes/[id].md` 路径 - 未识别到 → 默认蚂蚁集团+支付宝双 Logo 主题 - 多品牌冲突 → 按 `themes/_index.md` 优先级链执行 --- ## Phase 1：内容收集 一次性收集以下信息（运行环境支持 `AskUserQuestion` 则用它补问，否则发一条消息补问）： | 字段 | 说明 | |---|---| | **公司** | 所在公司或品牌名称（用于匹配主题色） | | **主题** | 演讲 / 汇报的核心主题 | | **受众** | 管理层 / 团队内部 / 跨部门 / 外部客户 | | **页数** | 5–8 页 / 10–15 页 / 20 页以上 | | **内容** | 粘贴大纲、草稿或关键要点；只有主题时说明即可 | | **署名** | 演讲者姓名 + 角色/岗位/Title（用于封面） | | **日期** | 演讲日期（默认今天） | **缺失字段的推断规则：** - **公司** → 从内容、署名、部门名称推断；仍无法识别则默认蚂蚁集团 - **受众** → 含"汇报/review/决策/管理层" → 管理层；含"分享/复盘/团队" → 团队内部；含"提案/客户/合作" → 外部客户；无法判断 → 内部汇报 - **页数** → 按模式和字数推断： - **Mode A2（主题创作）**：默认 10–15 页，主题内涵丰富（含多子话题、历史文化、数据等）可到 15–20 页； - **Mode A1（内容压缩）**：< 500 字 → 4–6 页；500–1500 字 → 8–12 页；1500–3000 字 → 12–18 页；> 3000 字 → 18 页以上； - 推断结果与内容明显不符时，进入 Phase 2 前显式确认 - **日期** → 默认今天 - **署名** → 从内容或当前用户信息推断；无法推断则默认"演讲者" Phase 1 完成后通知：`已收集：[公司] · [受众] · [页数] 页 · [署名]` --- ## Phase 2：结构规划 ### 必须包含的页面 | 页面类型 | Slide Class | 豁免条件 | |---|---|---| | 封面页 | `slide-cover` | 无豁免 | | 目录页 | `slide-white` | 总页数 ≤ 6 时省略 | | 章节过渡页 | `slide-section` | 该章节内容页 < 3 时省略；章节页总数不超过内容页的 1/3 | | 结尾页 | `slide-qa` | 无豁免 | > **小型 PPT（≤ 6 页）**：封面 + 内容页 + 结尾，不强插目录和章节页。 ### 演讲稿转 PPT 规则（**仅 Mode A1** 执行，Mode A2 跳过） 1. **PPT 是辅助，不是原文上墙** — 禁止逐段照搬，单页文字量必须明显少于讲稿密度。 2. **一页一判断** — 提炼一个结论句作为标题，保留 2–4 个支撑点；多论点拆成多页。 3. **留金句，删铺垫** — 保留数字、判断、类比；删寒暄、重复、口语化过渡；引用只取 1 句。 4. **3 秒可读** — 满足任一条即达标：① 单页总字数 ≤ 80 字；② bullet ≤ 4 条且每条 ≤ 15 字；③ 数字 ≤ 3 个且说明 ≤ 30 字；④ 视觉块占页面 ≥ 40%。 5. **第一视角** — 标题优先用"我们 / 今天想讲"，不用"作者认为""整场发言围绕"。 不适合搬到 PPT 的内容：开场寒暄、重复表达、口语化连接词、大段故事细节、抒情句、可口头补充的背景说明。 ### 主题创作原则（**仅 Mode A2** 执行） 1. **内容深度优先** — 每个子话题充分展开，给出背景、细节、意义，让读者看完幻灯片本身就能获得完整信息，无需演讲者补充。 2. **完整性** — 凡主题中含有明确数量的事物（如"十景"、"三原则"、"五步骤"），必须全部列出，不得因排版便利而截断；超出单页容量则拆分为多页并在标题注明"（上）""（下）"。 3. **引用原话** — 文化/历史类主题应引用原始诗句、史料或名言，不用泛泛描述代替。 4. **卡片描述上限放宽** — 信息卡片描述 ≤ 120 字，步骤卡描述 ≤ 80 字，单页总字数 ≤ 300 字；超出则拆页，不压缩内容。 5. **标题是结论，不是话题** — 即便是介绍性内容，标题也应给出判断（"苏堤是人工干预自然的最美典范"而非"苏堤简介"）。 ### 视觉化原则 1. **先做结构，再放文字** — 优先用数字、卡片、对比、流程承载内容。 2. **主视觉节奏清晰** — 多个视觉区块间必须有明确层级和分组；内容超出屏幕时拆页，不缩字号强塞。 3. **把段落改成组件** — 长段优先改写成组件；可复用 `stat-block`、`step-card`、`quote-box`、`agenda-item`，也可按内容需要设计新的页面骨架与信息组件。 4. **关键词可扫读** — 数字、判断句必须一眼能扫到；长句只作补充。 5. **多横向，少纵向** — 优先并列卡片、对比、矩阵，少用连续纵向文字。 6. **每 3–4 页变换节奏** — 穿插章节页、金句页、大字判断页或轻内容页。 7. **先判内容形态，再选版式** — 见「内容类型 → 组件选择」表。 8. **标题区固定** — 所有内容页标题区位置一致，不因正文密度漂移。 9. **SVG 优先** — 数量对比、趋势、占比、流程优先用内联 SVG；禁止纯数字+文字句子描述可视化数据。 10. **视觉效果分层** — 重点卡片叠加渐变背景 + 顶部色条 + pill 标签；径向渐变光圈作点缀，不要每张卡都加。 11. **组件库是起点，不是边界** — `components.md` 提供的是高频可复用参考，不是完整上限；当主题更适合地图、时间带、关系网、年表、分镜、注释图、海报式大字、档案卡、实物标注、时间切片、城市剖面等表达时，应优先为内容发明更合适的新组件。 ### 内容类型 → 组件选择 在写每页 HTML 前，先判断内容形态，再选布局。优先级：**页面骨架 > 成组组件 > 普通 bullet**。下表是推荐起点，不是限制清单；若现有组件不够贴合，应自定义新骨架而不是勉强套用。 | 内容形态 | 推荐组件 / 骨架 | 避免 | |---|---|---| | 全文总览 / 章节预告 | `agenda-item`、分组目录页 | 普通 bullet 平铺目录 | | 开场判断 / 问题定义 | 开场钩子页、`highlight-box` + `info-card` | 整页长段正文 | | 单一核心结论 | `highlight-box`、`stat-block`、大数字 | 密集列表或多主题混页 | | 核心结论 + 数据支撑 | `insight-panel` | 判断句和数字分散在两张独立卡片 | | 并列要点 / 三原则 | `three-col`、`info-card`、`stat-block` | 三个平级观点写成纵向长段 | | 双对象对比 | `two-col`、左右图文混排 | 单列顺序写法 | | 分阶段推进 | `step-card`、`step-flow-grid` | 普通 bullet 描述"第一第二第三" | | 月度 / 季度路线图 | `roadmap-col` | 普通 step-card 忽略时间轴感 | | 工作流 / 研发流程 | `flow-item`（4列×N行） | 有序列表平铺 8 个步骤 | | 能力成熟度 / 等级体系 | `level-row` | 普通 bullet 描述各等级 | | 组织支撑 / 能力体系 | `support-board`、`support-card`、`role-card` | 多段正文描述体系 | | 责任清单 / 人员分工 | `support-mini-card` + `@负责人` | 普通 bullet 列名字 | | 多角色流转 / 跨方协同 | `demand-flow-board` | 静态列表描述角色 | | 数据 / 占比 / 趋势 | `stat-block`、进度条、SVG 图表、表格 | 只写数字句子无图形支撑 | | 排名 / 优先级 | `bar-rank` | 纯数字列表 | | 业务线 / 产品集合 | `tag-cloud` | 等高等大标签平铺 | | 产品截图 | `browser-mockup` | 裸放图片无上下文 | | 引用原话 | `quote-box`、`insight` | 堆多段长引用 | | 结尾总结 | 分组总结页、`agenda-item`、`highlight-box` | 结尾再展开大量新信息 | **组件约束：** - 先选骨架，再选骨架内组件；强结构页（`support-board`、`demand-flow-board`）不退回普通卡片页。 - 卡片未铺满一行时容器收窄居中：1张→40%、2张→60%、3张→80%、4张以上→100%；用 `style="max-width:X%;margin:0 auto;"` 写在容器上。 - 内容偏少时 `slide-body` 显式加 `justify-content:center`，避免内容贴在上半屏。 - `components.md` 是参考库，不是白名单。允许自定义 HTML、CSS class 和新的信息组件，只要遵守当前模板的 CSS 变量、字号体系、留白系统与交互约束。 - 自定义组件应优先解决“现有组件表达不清”的问题，而不是只换视觉皮肤；新组件必须有清晰的信息任务，例如表达时间跨度、地理关系、因果链、空间层级、证据标注、版本差异、结构剖面。 - 新组件优先保持语义化命名，例如 `timeline-band`、`map-note-card`、`artifact-callout`、`split-metric-stage`、`city-axis-board`，避免无语义的 `box-1`、`layout-a`。 - 新组件可以是整页骨架，不必退化成卡片拼贴；允许使用注释线、时间轴带、分层底图、局部放大框、编号锚点、档案标签、印章式视觉、报纸栏、策展墙、编年表、示意地图等展示方式。 - **叙述型卡片页禁止 5 列**：含标题+描述+bullet 的解释型页面最多 4 列；5 列只用于极短标签卡或纯图标卡。 - **禁止"上实下空"**：页面底部留白明显大于顶部时，视为未完成布局。 ### 新组件设计规则 当需要超出 `components.md` 时，按以下规则设计： 1. **先定义信息任务** — 先说清楚这页要表达的是时间、空间、关系、证据、对照、层次还是叙事，再决定组件形态。 2. **一页只引入 1 个主创意** — 可以有辅助组件，但主视觉语言必须单一，避免一页内同时出现时间轴 + 地图 + 大表格 + 多层卡片。 3. **可扫读优先于炫技** — 新组件必须让读者 3 秒内看懂主结论，不能为了“新”而牺牲信息获取速度。 4. **结构先于装饰** — 先确定信息区块、阅读顺序、锚点和对齐关系，再加渐变、阴影、纹理、印章、边框等装饰。 5. **允许为主题定制组件** — 历史类可用编年轴、文物注释卡、古今对照图；战略类可用战区图、优先级棋盘；产品类可用分镜、界面放大镜、漏斗剧场；教育类可用黑板式推演、题眼拆解图。 6. **复用已有设计语言** — 即便新建组件，也要继续使用现有颜色变量、圆角、阴影、字号层级和 reveal 动画，不要做成另一个完全割裂的系统。 7. **能抽象再抽象** — 如果某个新组件在 2 页以上重复出现，第二次开始应抽成稳定 class 命名，而不是每页一套散装内联样式。 ### 内容密度规则 | 内容类型 | Mode A2（主题创作） | Mode A1（内容压缩） | |---|---|---| | 文字要点 | 10–15 条 bullet，每条 ≤ 40 字 | **6–8 条**，每条 ≤ 25 字 | | 信息卡片（横向） | 5–6 列，描述 ≤ 120 字 | **4–5 列**，描述 ≤ 50 字 | | 信息卡片（纵向） | 单列 ≤ 10 张，描述 ≤ 120 字/张 | 单列 ≤ 6 张，描述 ≤ 35 字/张 | | 数据统计 | 6–9 个 stat-block，配 1–2 句解读 | 6 个，配 1 句解读 | | 对比/流程 | 4–6 个 step-card，描述 ≤ 80 字 | **4 个**，描述 ≤ 35 字 | | 表格 | 最多 12 行 × 6 列 | 最多 8 行 × 5 列 | | 引用原话 | 1–3 段，每段 ≤ 150 字 | 1 段，**≤ 80 字** | | 卡片描述正文 | **3–5 句话**，≤ 120 字 | **2–3 句话**，≤ 50 字 | | 单页总字数 | ≤ 300 字 | ≤ 150 字 | 超出限制 → 自动拆成多页，页间用连贯的过渡标题衔接。 ### 布局多样性 **规划完成后，进入 Phase 3 前必须执行以下自检：** 1. 统计各类型布局的页数（多列卡片、流程、bullet、纯视觉） 2. 检查局部连续约束： - `three-col` / `info-card` ≥ 4 连续 → 中间插入过渡页或大字判断页 - `step-card` ≥ 3 连续 → 第 4 页换其他骨架 - `styled-list` ≥ 4 连续 → 至少 1 页改为卡片或图表 3. 检查全局约束： - 多列卡片页占内容页 > 55% → 替换超出部分 - 总页数 > 10 且无纯视觉页 → 插入至少 1 页（建议放中段） 4. 有调整则在规划清单中用 `[调整]` 标注改动位置 ### 文字润色（自动执行） 1. **去冗余** — 删除"我们认为""需要指出的是"等无信息量前缀。 2. **名词化** — "进行分析"→"分析"，"做出决策"→"决策"。 3. **数字化** — 只润色原文已有数字的表达（"百分之三十七"→"37%"）；禁止编造原文无源的具体数字。 - ✅ 原文含"50+"→ 保留"50+ 轮测试"；原文含"故障率""下降"→ 可补"故障率下降 40%" - ❌ 原文"取得成果"→ 不可杜撰"节省 200 万元"；原文"反馈不错"→ 不可杜撰"满意度 92%" 4. **动词有力** — 用"推进""落地""打通""提升"替代"做""进行""开展"。 5. **层级清晰** — 主标题是结论/判断，副标题是补充说明，内容是支撑。 6. **引用克制** — 只保留最有代表性的 1 句；同页不出现多段长引用。 Phase 2 完成后通知：`规划完成，共 N 张，开始生成…` --- ## Phase 3：生成 HTML ### Step 3.1：准备文件 **① 复制模板** ```bash cp template.html [输出文件名].html ``` - 禁止直接编辑 `template.html` 本身 - 文件名使用英文小写 + 连字符，例如 `antgroup-q1-review.html` **② 并行读取**（同时发起） - **`themes/[id].md`** — 获取 CSS 变量、`.slide-section` / `.slide-qa` 覆盖、logo 路径 - **`components.md`** — 只 Grep Phase 2 中用到的组件章节，不必全读 - **`components.md`** — 作为高频组件参考；若规划包含创新布局，只需读取相关章节借鉴命名、字号和留白，不要把它当成白名单 - **`icons.md`**（含图标槽时按需 Grep）— 中文主题 → 英文图标名参考： | 中文关键词 | 推荐图标名 | |---|---| | 安全、防护、权限、合规 | `shield`, `lock`, `eye-off` | | 数据、分析、统计、报告 | `bar-chart-2`, `pie-chart`, `trending-up` | | 用户、客户、团队、人员 | `users`, `user`, `user-check` | | 流程、步骤、推进、路径 | `arrow-right`, `git-branch`, `navigation` | | 目标、战略、方向、规划 | `target`, `flag`, `compass` | | 技术、系统、架构、平台 | `cpu`, `server`, `layers` | | 效率、性能、速度、优化 | `zap`, `clock`, `activity` | | 成本、金融、收益、预算 | `dollar-sign`, `credit-card`, `trending-down` | | 协同、沟通、对接、整合 | `link`, `share-2`, `message-circle` | | 风险、问题、告警、异常 | `alert-triangle`, `x-circle`, `bell` | | 创新、产品、功能、特性 | `star`, `package`, `box` | | 运营、推广、增长、运作 | `trending-up`, `repeat`, `refresh-cw` | ### Step 3.2：填充占位符 `template.html` 已内置蚂蚁集团默认值（双 logo、页脚文字）。标题、主题样式、logo 全部改为脚本直接写回 HTML，不再手工替换。 **① 标题** **② 主题样式** **③ Logo** ```bash # `theme-id` 默认为 `ant-group` python3 scripts/apply-template-branding.py [文件名].html \ --title "[演讲主题]" \ --speaker "[演讲者]" \ --theme-id [theme-id] ``` 规则： - `theme-id=ant-group` 时保留 `<!-- %%THEME_STYLE%% -->` - 其他主题从 `themes/[id].md` 的 `## CSS` 代码块注入 `<style>...</style>` - `#globalLogoGroup` 是唯一 logo 节点，**禁止在任何 `<section>` 内写 logo** - logo 文件缺失时按脚本 fallback；必要时自动隐藏并输出 warning **④ 幻灯片内容（必填）** ``` old: <!-- %%SLIDES%% --> new: 所有 <section> 幻灯片 HTML ``` **主题应用说明：** - `.slide-section` / `.slide-qa` 必须用 `!important` 覆盖基础 CSS 中的硬编码渐变 - `--primary-pale` 会自动影响 `info-card` 背景、`agenda-item` hover 等，无需逐一覆盖 ### Step 3.3：写幻灯片 每张写入前先输出进度：`→ 03/12 技术架构现状`（序号补零到总数位数） **封面页** ```html <section class="slide slide-cover" id="slide-1"> <div class="cover-arcs" aria-hidden="true"> <div class="arc arc-1"></div><div class="arc arc-2"></div><div class="arc arc-3"></div> </div> <div class="cover-top reveal" style="display:flex;align-items:center;"> <span style="color:rgba(255,255,255,0.65);font-size:clamp(0.65rem,1.1vw,0.85rem);">[部门]</span> </div> <div class="cover-main"> <h1 class="cover-title reveal">[主标题]</h1> <p class="cover-subtitle reveal">[副标题]</p> <p class="reveal" style="font-size:clamp(0.85rem,1.5vw,1.1rem);color:#fff;font-weight:700;">[姓名｜角色/Title]</p> </div> <div class="cover-bottom"><div class="cover-date reveal">[日期]</div></div> </section> ``` **内容页（白色）** ```html <section class="slide slide-white" id="slide-N"> <div class="slide-header center-stack"> <span class="header-mark"></span> <h2 class="header-title">[结论/判断句]</h2> <p class="header-sub">[补充说明]</p> </div> <div class="slide-body" style="justify-content:center;"> <!-- 组件区 --> </div> </section> ``` **章节过渡页** ```html <section class="slide slide-section" id="slide-N"> <p class="section-num reveal">PART [N]</p> <h2 class="section-title reveal">[章节标题]</h2> <p class="section-desc reveal">[一句话说明本章节要回答的问题]</p> </section> ``` **结尾页** ```html <section class="slide slide-qa" id="slide-N"> <h2 class="qa-title reveal">Q&amp;A</h2> <p class="qa-sub reveal">[感谢语]</p> </section> ``` **写入约束（所有幻灯片必须遵守）：** | 场景 | 写法 | |---|---| | 普通间距、字号 | `clamp()` 或 CSS 变量 | | 固定 px 尺寸 | `calc(Npx * var(--vp-scale, 1))` | | 容器最大宽度 | `min(calc(XYZpx * var(--vp-scale, 1)), 100%)` | | `max-width` 写死 px | **禁止** | | 内容区宽度 | `<div style="width:min(100%,var(--deck-safe-width));margin:0 auto;">` | **密度自检**（每页必须执行，按模式选对应上限）： | 检查项 | A2 主题创作上限 | A1 内容压缩上限 | 处理 | |---|---|---|---| | bullet 条数 | ≤ 15 条 | ≤ 8 条 | 超出则拆页 | | 每条 bullet 字数 | ≤ 40 字 | ≤ 25 字 | 超出则压缩 | | 单页总字数 | ≤ 300 字 | ≤ 150 字 | 超出则拆页 | | info-card 描述 | ≤ 120 字/张 | ≤ 35 字/张 | A1 超出则精简；A2 超出则拆页 | | 标题类型 | 结论句 | 结论句 | 话题词改判断句 | --- ## Phase 4：输出 **Step 4.1：保存** 文件名英文小写 + 连字符；中文关键词转拼音或英译。 **Step 4.2：单体化兜底扫描** logo 应在 Step 3.2 ③ 直接从 `themes/logos/*.txt` 写入。此步骤仅处理其他外部图片遗漏的边缘情况： ```bash python3 scripts/inline-images.py [文件名].html ``` **Step 4.3：Smoke Test（必须执行，不得跳过）** 生成完成后立即运行静态检查；失败时必须修复后重跑，不得跳过也不得仅凭”看起来没问题”省略： ```bash ./scripts/smoke-test.sh [文件名].html ``` > **执行后在回复中明确写出：** `smoke-test: passed（N 张幻灯片）` 或 `smoke-test: FAILED — [原因]`。未写出视为未执行。 **Step 4.4：用浏览器直接打开文件（必须执行，不得跳过）** ```bash open [文件名].html ``` > **执行后在回复中明确写出：** `已用 open 打开浏览器` 或 `受环境限制无法打开 — [原因]`。未写出视为未执行。 成功标准：浏览器直接打开本地 HTML 文件，且不依赖本地 HTTP 服务。 **Step 4.5：最终交付摘要（必须输出，格式固定）** 在回复的最后输出以下固定格式的摘要块，所有字段必填，缺任何一项视为未完成： ``` 📦 交付摘要 文件路径：[绝对路径或相对路径] 文件大小：[X KB] 幻灯片数：[N] 张 主题：[theme-id] smoke-test：通过 / 失败（[原因]） 浏览器：已打开 / 无法打开（[原因]） 导航：方向键 / 空格翻页 · 右侧圆点 · F 全屏 ``` ### Phase 4 完成门槛（硬性约束） > **在以下全部条件满足前，禁止输出”完成””Done””已生成”或任何等价的结束表述。** - [ ] Step 4.1 文件已保存，文件名符合命名规范 - [ ] Step 4.2 `inline-images.py` 已执行 - [ ] Step 4.3 `smoke-test.sh` 已执行，结果已在回复中明确写出 - [ ] Step 4.4 `open` 已执行，结果已在回复中明确写出 - [ ] Step 4.5 交付摘要已按固定格式输出 - [ ] Step 4.5 已向用户回报上述结果 如果任一步未完成，最终答复必须改为“当前进度 + 阻塞原因”，不能伪装成完整交付。 --- ## 附录 A：支持文件 | 文件 | 用途 | 何时读取 | |---|---|---| | [template.html](template.html) | 预构建引擎壳（含完整 CSS/JS） | Step 3.1 `cp`（必须） | | [themes/_index.md](themes/_index.md) | 主题识别规则 + 子品牌表 + logo 索引 | Phase 0 主题检测 | | `themes/[id].md` | CSS 变量 + logo 路径 + 补充规则 | Step 3.1 并行读取 | | [components.md](components.md) | 组件 HTML 片段参考 | Step 3.1 按需 Grep | | [icons.md](icons.md) | 287 个 Feather Icons 内联 SVG | 有图标槽时按需 Grep | | `themes/logos/` | 品牌 logo 的 data URI 文本文件 | Step 3.2 ③ 直接读取 `.txt` | | [examples/index.html](examples/index.html) | 完整示例文档 | 需要查阅组件骨架时 | --- ## 附录 B：进度通知规范 格式统一为纯文本，不用 markdown 列表或标题。 | 时机 | 格式 | 示例 | |---|---|---| | Phase 1 完成后 | 一句话确认元数据 | `已收集：腾讯 · 管理层汇报 · 12 页 · 张三 总监` | | Phase 2 完成后 | 一句话说明页数 | `规划完成，共 12 张，开始生成…` | | Step 3.3 每张写入前 | `→ 序号/总数 标题` | `→ 03/12 技术架构现状` | | Step 4.2 完成后 | 一句话含文件大小 | `单体化完成，1.2 MB，无外部依赖。` | | Step 4.3 | 见 Phase 4 Step 4.3 | — | 序号统一补零到总数位数（总数 12 则写 `01`，总数 100 则写 `001`）。 --- ## 附录 C：执行检查清单 ### Phase 0 - [ ] `git pull --ff-only`（失败则静默忽略） - [ ] 提取公司关键词，确定主题文件路径 ### Phase 1 - [ ] 一次性收集所有字段；缺失项按推断规则补全 - [ ] 页数推断结果与内容不符时，显式确认 ### Phase 2 - [ ] 演讲稿输入：应用"演讲稿转换规则"，禁止逐段照搬 - [ ] 内容密度符合对应场景上限 - [ ] 布局多样性自检通过（局部连续 + 全局比例） - [ ] 封面、目录、章节过渡、结尾均已规划 ### Phase 3 - [ ] `cp template.html` 完成 - [ ] 填充 ① 标题 ② 主题样式 ③ logo（base64）④ 幻灯片内容 - [ ] 当前主题所需的 logo `.txt` 文件存在并已直接写入；文件缺失时执行 Fallback - [ ] 每张幻灯片密度自检通过 - [ ] 所有 info-card / step-card 包含图标 ### Phase 4 - [ ] Step 4.1 保存 - [ ] Step 4.2 扫描无残留外部引用 - [ ] Step 4.3 `smoke-test` 已执行并检查结果 - [ ] Step 4.4 已执行浏览器直接打开本地文件；若受限，已明确说明阻塞 - [ ] Step 4.5 已告知用户文件信息与验证结果 ### 最终答复模板（必须对照） - [ ] 文件路径 - [ ] 文件大小 / 幻灯片总数 / 主题名称 - [ ] `smoke-test` 结果 - [ ] 浏览器打开结果或阻塞原因 - [ ] 下一步可选动作 ### 禁止项 - ❌ 在 `<section>` 内写 logo 代码 - ❌ `max-width` 写死 px - ❌ 直接编辑 `template.html` - ❌ 编造原文无源的具体数字 - ❌ 演讲稿逐段照搬 - ❌ info-card 不加图标 - ❌ 少量卡片在全宽容器里偏左留白 --- ## 非目标 - 不生成 .pptx，只输出单个 HTML - 不自动抓取互联网内容，除非用户提供素材 - 不伪造演讲者身份、职位、部门 - 不擅自修改未被点名的现有页面 - 不保证所有品牌主题与官方品牌规范完全一致

path: README.md
content: <p align="center">
  <img src="examples/slide-writer.png" alt="Slide-Writer" width="200"/>
</p>

# Slide-Writer

[![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/FeeiCN/slide-writer/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Live Demo](https://img.shields.io/badge/Live%20Demo-feei.cn-blue.svg)](https://feei.cn/slide-writer/examples/)
[![中文](https://img.shields.io/badge/文档-中文-red.svg)](README.zh-CN.md)

> Focus on goals, viewpoints, and judgment. Slide-Writer handles structure, writing, refinement, and presentation.

<p align="center">
  <a href="https://feei.cn/slide-writer/examples/">
    <img src="examples/before-after.png" alt="Slide-Writer Demo" width="100%"/>
  </a>
</p>

## Quick Start

> Works best with **Claude Code** or **Codex**, paired with a capable model — Claude Sonnet, Claude Opus, or GPT-4o recommended.

```bash
# Claude Code
git clone https://github.com/FeeiCN/slide-writer.git ~/.claude/skills/slide-writer

# Codex
git clone https://github.com/FeeiCN/slide-writer.git ~/.agents/skills/slide-writer
```

```text
/slide-writer Generate a presentation on "Why do humans need to eat?" using Alipay style.
```

```text
/slide-writer Use the speech draft in examples/tencent-pony-ma.md to generate a presentation.
```

```text
/slide-writer I have a speech tomorrow. Turn examples/alibaba-ai-rollout.md into a deck.
```

## Core Features

**Any input → enterprise-grade deck**: sentence, outline, speech draft, notes, or existing HTML — Slide-Writer restructures and rewrites it into a presentation-ready format.

**14 brand themes, auto-detected**: covers Ant Group, Alibaba, Tencent, ByteDance, and more. Drop a keyword and the right theme, logo, and colors are applied automatically.

**Single-file delivery**: outputs one standalone HTML file — CSS, JS, and images all embedded. Opens in any browser. No PowerPoint, no Keynote, no dependencies.

**Always up to date**: pulls the latest themes, components, and generation rules automatically on every run.

## Repository Structure

- `SKILL.md`: skill definition and execution rules
- `themes/_index.md`: theme identification and logo index
- `themes/[id].md`: per-theme style and logo rules
- `components.md`: page component library
- `template.html`: baseline generation shell
- `examples/index.html`: page skeleton gallery and full example deck
- `examples/`: sample inputs and outputs
- `TESTING.md`: testing notes

### Quick Test

1. Pick one sample from [examples](examples).
2. Ask the model to generate a `test-*.html` based on this repo's `SKILL.md`.
3. Run:

```bash
./scripts/preview.sh
```

4. Open `http://localhost:8000/test-xxx.html` in a browser.

See [TESTING.md](TESTING.md) for the full testing flow and regression checklist.

---

If Slide-Writer saved you time, consider giving it a ⭐ — it helps others find the project.


path: README.zh-CN.md
content: <p align="center">
  <img src="examples/slide-writer.png" alt="Slide-Writer" width="200"/>
</p>

# Slide-Writer

[![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)](https://github.com/FeeiCN/slide-writer/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![在线演示](https://img.shields.io/badge/在线演示-feei.cn-blue.svg)](https://feei.cn/slide-writer/examples/)
[![English](https://img.shields.io/badge/Docs-English-blue.svg)](README.md)

> 您只需专注目标、观点与判断，Slide-Writer 负责结构、写作、优化与呈现。

<p align="center">
  <a href="https://feei.cn/slide-writer/examples/">
    <img src="examples/before-after.png" alt="Slide-Writer Demo" width="100%"/>
  </a>
</p>

## 快速开始

> 推荐搭配 **Claude Code** 或 **Codex** 使用，模型建议选择 Claude Sonnet、Claude Opus 或 GPT-4o。

```bash
# Claude Code
git clone https://github.com/FeeiCN/slide-writer.git ~/.claude/skills/slide-writer

# Codex
git clone https://github.com/FeeiCN/slide-writer.git ~/.agents/skills/slide-writer
```

```text
/slide-writer 帮我生成一个「人为什么要吃饭」的演讲 PPT，使用支付宝风格。
```

```text
/slide-writer 基于演讲稿 examples/tencent-pony-ma.md，生成一个演讲 PPT。
```

```text
/slide-writer 我明天有一个演讲，基于 examples/alibaba-ai-rollout.md 帮我生成一个 PPT。
```

## 核心特性

**任意输入 → 企业级 PPT**：一句话、大纲、演讲稿、笔记、已有 HTML 均可——Slide-Writer 自动完成重构、改写和演示化排版。

**14 家品牌主题，自动识别**：覆盖蚂蚁、阿里、腾讯、字节等。输入关键词即自动匹配主题、Logo 和配色，无需手动配置。

**单文件交付**：输出一个独立 HTML 文件，CSS、JS、图片全部内置。浏览器直接打开，无需 PowerPoint 或 Keynote。

**自动保持最新**：每次运行自动拉取最新主题、组件和生成规则，无需手动更新。

## 仓库结构

- `SKILL.md`：Skill 定义与执行规则
- `themes/_index.md`：主题识别与 Logo 索引
- `themes/[id].md`：单主题样式与 Logo 规则
- `components.md`：页面组件库
- `template.html`：基础生成壳
- `examples/index.html`：页面骨架演示与完整示例文档
- `examples/`：示例输入与输出
- `TESTING.md`：测试说明

### 快速测试

1. 选一个 [examples](examples) 里的样例作为输入。
2. 让模型基于本仓库里的 `SKILL.md` 生成 `test-*.html` 到仓库根目录。
3. 运行：

```bash
./scripts/preview.sh
```

4. 浏览器打开 `http://localhost:8000/test-xxx.html` 预览。

更完整的测试流程和回归清单见 [TESTING.md](TESTING.md)。

---

如果 Slide-Writer 帮你节省了时间，欢迎点个 ⭐ — 让更多人发现这个项目。


path: TESTING.md
content: # Testing Slide-Writer

目标是让 skill 调整后可以快速做三件事：复现生成、打开预览、按清单验收。

## 本地开发说明

在当前仓库开发或调试 skill 时，优先复用本地文件，不要回退到 Claude 全局目录中的旧副本。

## 快速流程

1. 在当前仓库中挑一个测试样例：
   - `examples/antgroup-quarterly-review.md`
   - `examples/alibaba-ai-rollout.md`
   - `examples/revise-existing-deck.md`
2. 让模型基于 `SKILL.md` 生成或修改 HTML，并把结果写到仓库根目录，例如 `test-antgroup.html`。全新测试稿默认从 `template.html` 出发，并在需要组件参考时查阅 `examples/index.html`，不要从旧的 `test-*.html` 继续复制。
3. 先在仓库根目录运行 `./scripts/smoke-test.sh test-antgroup.html`。
4. 再运行 `./scripts/preview.sh`。
5. 浏览器打开 `http://localhost:8000/test-antgroup.html`。
6. 按下面的验收清单检查。

## 推荐测试指令

### 新生成

```text
使用当前目录的 slide-writer skill，基于 examples/antgroup-quarterly-review.md 生成一个单文件 HTML 演示稿，输出到 ./test-antgroup.html。先严格读取本仓库里的 SKILL.md、themes/_index.md、对应的 themes/[id].md、components.md、template.html 和 examples/index.html，再开始生成。所有测试稿都从 template.html 出发，并参考 examples/index.html，不要从已有 test-*.html 派生。
```

### 改现有稿

```text
使用当前目录的 slide-writer skill，读取 ./test-antgroup.html，并根据 examples/revise-existing-deck.md 的要求直接改稿，覆盖原文件。
```

## 静态 Smoke Test

```bash
./scripts/smoke-test.sh test-antgroup.html
```

这个脚本会做轻量检查：

- HTML 中引用的本地 logo 文件是否存在
- 是否保留 `progress-bar`、`nav-dots`、`fullscreenBtn`、`footnote`
- 是否包含封面、目录、章节页、结尾页
- 是否仍然存在明显错误的根目录 logo 路径

## 手工验收清单

- 主题识别正确：能根据关键词命中对应公司主题。
- Logo 路径有效：页面里没有坏图标。
- 页面无滚动条：每一页都完整落在 `100vh` 内。
- 封面署名完整：默认包含“姓名｜角色 / 岗位 / Title”，而不只是名字。
- 层级清晰：标题是结论句，副标题是补充说明。
- 内容页标题区固定：不同内容页的标题和副标题位置保持一致，不因正文多少而上下漂移。
- 内容密度受控：长列表、表格、卡片没有溢出。
- 页面骨架优先：高频页面优先复用模板中已有的骨架（如分组目录、双阶段流程、横向支撑板、流转看板），而不是每页从零拼布局。
- 如果输入是演讲稿：没有把原文逐段搬上 PPT，单页文字明显少于讲稿原文密度。
- 如果输入是演讲稿：每页都是“判断/结论 + 支撑”，而不是大段讲稿摘抄。
- 如果输入是演讲稿：整体更像上台演讲用的 PPT，而不是阅读型资料页。
- 如果输入是演讲稿：副标题、bullet、quote 都足够短，观众能在几秒内抓住重点。
- 如果输入是演讲稿：默认保留第一视角，不要出现明显的编辑转述口吻。
- 页面有明显视觉结构：不是整页连续文字，至少使用数字、卡片、对比、引用、章节页中的一种。
- 连续几页不能都长得一样：应有节奏变化，而不是所有页都是同一种文字卡片。
- 目录、章节页、结尾页都存在。
- 键盘翻页、导航点、进度条仍然可用。
- 浏览器直接打开时样式正常，不依赖构建工具。
- `./scripts/smoke-test.sh` 通过，没有缺失文件或关键结构报错。

## 回归重点

- 改 `themes/_index.md` 或任一 `themes/[id].md` 后，至少跑一个蚂蚁系和一个阿里系样例。
- 改 `components.md` 后，至少检查 `info-card`、`agenda-item`、`step-card`、`stat-block`，以及页面级骨架（如分组目录或支撑板）是否仍然可用。
- 改 `template.html` 或 `examples/index.html` 后，至少检查目录页、内容页、章节页、结尾页和 logo / progress / nav / fullscreen 行为，并确认所有内容页标题区位置一致。
- 改 `SKILL.md` 的生成规则后，至少同时测一次全新生成和一次增强改稿。

## 产物约定

- 建议把临时测试产物命名为 `test-*.html` 放在仓库根目录。
- 如果需要长期保留样例，再手动整理到独立目录，避免污染根目录素材。


path: components.md
content: # Component Library — Slide-Writer

所有组件均已在 CSS 中定义，直接使用对应 class 即可。添加 `reveal` class 启用入场动画（向上淡入）、`reveal-left` 启用从左淡入动画。

---

## 布局容器

### 两列 / 三列 Grid
```html
<!-- 两列等宽 -->
<div class="two-col">
    <!-- 左列内容 -->
    <!-- 右列内容 -->
</div>

<!-- 三列等宽 -->
<div class="three-col">
    <!-- 列1 --><!-- 列2 --><!-- 列3 -->
</div>
```

---

## 数据展示

### 统计数字块 `stat-block`
大号数字配说明文字，适合展示关键指标。可选在数字上方加 `stat-icon`（从 `icons.md` 选取）。
```html
<div class="stat-block">
    <div class="stat-icon"><!-- 从 icons.md 选图标 SVG，无则省略 --></div>
    <div class="stat-num">85%</div>          <!-- 蓝色大数字 -->
    <div class="stat-label">代码生成采纳率</div>
</div>

<!-- 红色变体（stat-icon 颜色自动跟随） -->
<div class="stat-block">
    <div class="stat-icon"><!-- SVG --></div>
    <div class="stat-num red">-40%</div>
    <div class="stat-label">故障率下降</div>
</div>
```

### 目标卡片 `goal-card`
超大数字 + 标题 + 描述，适合展示 OKR 或关键目标。可选在数字上方加 `goal-icon`（从 `icons.md` 选取）。
```html
<div style="display:flex;gap:1rem;">
    <div class="goal-card">
        <div class="goal-icon"><!-- 从 icons.md 选图标 SVG，无则省略 --></div>
        <div class="goal-num-big">85<span class="unit">%</span></div>
        <div class="goal-label">AI 代码覆盖率目标</div>
        <div class="goal-sublabel">2025 年底前达成</div>
    </div>
</div>
```

---

## 信息卡片

### 信息卡 `info-card`（最常用）
左侧彩色竖线，灰色背景，适合要点/特性展示。每张卡**必须**包含 `card-icon`，图标从 `icons.md` 按标题关键词选取。
```html
<div class="info-card">                          <!-- 默认蓝色竖线 -->
    <div class="card-icon"><!-- 从 icons.md 选图标 SVG --></div>
    <div class="card-title">高可用</div>
    <div class="card-body">说明文字，两三句话即可。</div>
</div>

<!-- 颜色变体：在 class 后加 red / green / orange，图标颜色自动跟随 -->
<div class="info-card red">...</div>
<div class="info-card green">...</div>
<div class="info-card orange">...</div>
```

### 步骤卡 `step-card`
渐变背景，适合展示分步骤/阶段。每张卡**必须**包含 `step-icon`，图标从 `icons.md` 按步骤语义选取。
```html
<div style="display:flex;gap:1rem;">
    <div class="step-card step-card-1">   <!-- 浅蓝渐变 -->
        <div class="step-icon"><!-- 从 icons.md 选图标 SVG --></div>
        <div class="step-num">STEP 01</div>
        <div class="step-title">启动阶段</div>
        <div class="step-desc">描述文字</div>
    </div>
    <div class="step-card step-card-2">   <!-- 浅靛渐变 -->...</div>
    <div class="step-card step-card-3">   <!-- 浅紫渐变 -->...</div>
    <div class="step-card step-card-4">   <!-- 浅橙渐变 -->...</div>
</div>
```

### 支持卡 `support-card`
带图标的功能/服务卡，适合展示支撑体系。图标**必须**使用 SVG（从 `icons.md` 选取），不用 emoji。
```html
<div style="display:flex;gap:1rem;">
    <div class="support-card">
        <div class="support-icon"><!-- 从 icons.md 选图标 SVG --></div>
        <div class="support-title">安全合规</div>
        <ul class="support-items">
            <li>数据脱敏规范</li>
            <li>工具准入清单</li>
        </ul>
    </div>
</div>
```

### 角色卡 `role-card`
展示职能角色的演进/变化。图标**必须**使用 SVG（从 `icons.md` 选取），不用 emoji。
```html
<div style="display:flex;gap:1rem;">
    <div class="role-card">
        <div class="role-icon"><!-- 从 icons.md 选图标 SVG --></div>
        <div class="role-name">研发工程师</div>
        <div class="role-arrow">传统 → AI增强</div>
        <div class="role-evolution">AI 辅助代码审查、单测生成、文档撰写</div>
    </div>
</div>
```

---

## 目录 / 议程

### 议程条目 `agenda-item`
带编号、标签的议程列表，适合目录页。
```html
<div style="display:flex;flex-direction:column;gap:0.6rem;max-width:72%;margin:0 auto;">
    <div class="agenda-item reveal">
        <span class="agenda-num">01</span>
        <span class="agenda-text">为什么要做这件事</span>
        <span class="agenda-tag" style="background:#FFF1F0;color:#E8380D;">背景</span>
    </div>
    <div class="agenda-item reveal">
        <span class="agenda-num">02</span>
        <span class="agenda-text">现状与问题</span>
        <span class="agenda-tag" style="background:#E6F4FF;color:#1677FF;">分析</span>
    </div>
</div>
```

---

## 文字组件

### 高亮引用框 `highlight-box`
重要结论或引用，左蓝竖线背景。默认全宽拉伸，如需限制宽度必须同时加 `margin: 0 auto` 居中，否则会左对齐偏歪。
```html
<!-- 红色警示变体 -->
<div class="highlight-box red-box reveal">危险或紧急提示</div>

<!-- 限制宽度时必须同时加 margin: 0 auto，否则会左对齐偏歪 -->
<div class="highlight-box reveal">限制宽度时居中显示。</div>
```

### 标签 `tag`
行内标签，适合分类或标注。
```html
<span class="tag tag-blue">蓝色标签</span>
<span class="tag tag-red">红色标签</span>
<span class="tag tag-green">绿色标签</span>
<span class="tag tag-dark">深色标签</span>
```

### 要点列表 `styled-list`
带蓝色圆点的列表，替代默认 ul/li。
```html
<ul class="styled-list reveal">
    <li>第一个要点，简洁有力的一句话</li>
    <li>第二个要点，<strong>关键词加粗</strong>突出重点</li>
    <li>第三个要点</li>
</ul>
```

### 洞察/观点 `insight`
带圆点的单条观点，适合图表下方的解读。
```html
<div class="insight reveal">
    <span class="insight-dot"></span>
    <span>从数据可以看出，深度用户的效率提升远高于平均水平，关键变量是<strong>工具使用深度</strong>而非工具数量。</span>
</div>
```

---

## 层级 / 评分

### 级别行 `level-row`
展示能力等级或评分维度，支持高亮当前级别。
```html
<div style="display:flex;flex-direction:column;gap:0.4rem;">
    <div class="level-row">
        <span class="level-badge">L1</span>
        <div class="level-main">
            <div class="level-title">工具使用</div>
            <div class="level-desc">会用 Copilot / ChatGPT 完成简单任务</div>
        </div>
        <span class="level-dot"></span>
    </div>
    <div class="level-row active">   <!-- active 高亮 -->
        <span class="level-badge">L2</span>
        <div class="level-main">
            <div class="level-title">场景驱动</div>
            <div class="level-desc">能结合业务场景设计 Prompt，独立完成复杂任务</div>
        </div>
        <span class="level-dot"></span>
    </div>
</div>
```

---

## 流程图

### 流程格 `flow-item`
4列×2行紧凑网格，每格含阶段名 + 副描述。适合 DevOps 流程、工作流拆解等 8 步以内横向矩阵。与 `step-card` 的区别：`step-card` 用于竖向分阶段推进，`flow-item` 用于横向并列的工序/环节铺排。
```html
<div style="display:flex;flex-direction:column;gap:clamp(0.5rem,1vh,0.8rem);">
    <p class="slide-subtitle">AI for DevOps</p>
    <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(0.3rem,0.8vw,0.6rem);" class="reveal">
        <div class="flow-item changed"><div class="flow-label">需求收集</div><div class="flow-sub">快速生成版本</div></div>
        <div class="flow-item changed"><div class="flow-label">系统设计</div><div class="flow-sub">结合上下文设计</div></div>
        <div class="flow-item changed"><div class="flow-label">代码实现</div><div class="flow-sub">Agent 实现，人把关</div></div>
        <div class="flow-item changed"><div class="flow-label">测试</div><div class="flow-sub">代码测试同步生成</div></div>
    </div>
    <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(0.3rem,0.8vw,0.6rem);" class="reveal">
        <div class="flow-item changed"><div class="flow-label">代码审查</div><div class="flow-sub">对抗性校验</div></div>
        <div class="flow-item changed"><div class="flow-label">部署</div><div class="flow-sub">验证后自动上线</div></div>
        <div class="flow-item changed"><div class="flow-label">监测</div><div class="flow-sub">可观测性驱动</div></div>
        <div class="flow-item changed"><div class="flow-label">反馈闭环</div><div class="flow-sub">持续迭代优化</div></div>
    </div>
</div>
```
> `class="flow-item"` 为默认灰色格，`class="flow-item changed"` 为蓝色高亮格（表示已 AI 化），可混用。

### 步骤流箭头 `step-flow-grid`
横向流程，带箭头连接，适合4步以内的流程。
```html
<div class="step-flow-grid reveal">
    <div class="step-card step-card-1">
        <div class="step-num">STEP 01</div>
        <div class="step-title">需求分析</div>
    </div>
    <div class="step-flow-arrow">→</div>
    <div class="step-card step-card-2">
        <div class="step-num">STEP 02</div>
        <div class="step-title">方案设计</div>
    </div>
    <div class="step-flow-arrow">→</div>
    <div class="step-card step-card-3">...</div>
    <div class="step-flow-arrow">→</div>
    <div class="step-card step-card-4">...</div>
</div>
```

---

## 图文混排

### 左图右文
图片区域可替换为 `<img>` 标签或任意 SVG 图表；占位块仅作布局参考。
```html
<div style="display:grid;grid-template-columns:1fr 1fr;gap:2rem;align-items:center;" class="reveal">
    <!-- 图片区 -->
    <div style="border-radius:12px;overflow:hidden;aspect-ratio:4/3;background:var(--primary-pale);display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:var(--small-size);font-weight:600;">
        图片 / 图表 / 截图
    </div>
    <!-- 文字区 -->
    <div style="display:flex;flex-direction:column;gap:0.6rem;">
        <h3 style="font-size:clamp(1rem,1.4vw,1.15rem);font-weight:800;color:var(--text-1);">标题</h3>
        <p style="font-size:var(--body-size);color:var(--text-2);line-height:1.65;">说明文字，2–3 句话，简洁描述图片要传达的关键信息。</p>
        <ul class="mini-list">
            <li>补充要点一</li>
            <li>补充要点二</li>
        </ul>
    </div>
</div>
```

### 浏览器截图框 `browser-mockup`
macOS 风格浏览器窗口：红黄绿三点 + URL 地址栏 + 截图。适合展示网站、后台系统、产品界面截图，比裸图更有上下文感。
```html
<div style="border-radius:10px;overflow:hidden;border:1px solid #E0E0E0;box-shadow:0 4px 16px rgba(0,0,0,0.06);">
    <!-- 标题栏 -->
    <div style="background:#F2F2F2;padding:0.45rem 0.75rem;display:flex;align-items:center;gap:0.55rem;border-bottom:1px solid #DCDCDC;">
        <!-- 红黄绿三点 -->
        <div style="display:flex;gap:5px;flex-shrink:0;">
            <span style="width:10px;height:10px;border-radius:50%;background:#FF5F57;display:inline-block;"></span>
            <span style="width:10px;height:10px;border-radius:50%;background:#FEBC2E;display:inline-block;"></span>
            <span style="width:10px;height:10px;border-radius:50%;background:#28C840;display:inline-block;"></span>
        </div>
        <!-- URL 地址栏 -->
        <div style="flex:1;background:#fff;border:1px solid #DCDCDC;border-radius:6px;padding:0.18em 0.65em;font-size:clamp(0.58rem,0.92vw,0.74rem);color:#111;font-family:monospace;font-weight:800;">your-domain.com</div>
    </div>
    <!-- 截图 -->
    <img src="./screenshot.jpg" alt="界面截图"
         style="width:100%;height:auto;max-height:260px;object-fit:contain;object-position:center top;display:block;background:#fff;">
</div>
```

### 右图左文
```html
<div style="display:grid;grid-template-columns:1fr 1fr;gap:2rem;align-items:center;" class="reveal">
    <!-- 文字区 -->
    <div style="display:flex;flex-direction:column;gap:0.6rem;">
        <h3 style="font-size:clamp(1rem,1.4vw,1.15rem);font-weight:800;color:var(--text-1);">标题</h3>
        <p style="font-size:var(--body-size);color:var(--text-2);line-height:1.65;">文字放左侧，图片放右侧，视觉重心靠右。</p>
        <ul class="mini-list">
            <li>补充要点一</li>
            <li>补充要点二</li>
        </ul>
    </div>
    <!-- 图片区 -->
    <div style="border-radius:12px;overflow:hidden;aspect-ratio:4/3;background:var(--primary-pale);display:flex;align-items:center;justify-content:center;color:var(--primary);font-size:var(--small-size);font-weight:600;">
        图片 / 图表 / 截图
    </div>
</div>
```

---

## 数据可视化（纯 CSS）

### 横向进度条（对比数据）
```html
<div style="display:flex;flex-direction:column;gap:0.5rem;" class="reveal">
    <div style="display:flex;align-items:center;gap:0.6rem;">
        <span style="font-size:var(--small-size);font-weight:900;color:var(--red);min-width:2.5em;">~200</span>
        <div style="flex:1;height:7px;border-radius:3px;background:linear-gradient(90deg,var(--red),#FFCDD2);"></div>
        <span style="font-size:var(--small-size);color:var(--text-2);white-space:nowrap;">标签文字</span>
    </div>
    <div style="display:flex;align-items:center;gap:0.6rem;">
        <span style="font-size:var(--small-size);font-weight:900;color:var(--primary);min-width:2.5em;">~80</span>
        <div style="flex:1;height:7px;border-radius:3px;background:linear-gradient(90deg,var(--primary),#BAE0FF);width:40%;"></div>
        <span style="font-size:var(--small-size);color:var(--text-2);white-space:nowrap;">另一个标签</span>
    </div>
</div>
```

### 标签云（密度代表规模）
```html
<div style="display:flex;flex-wrap:wrap;justify-content:center;gap:0.5rem 0.7rem;" class="reveal">
    <span class="tag tag-blue" style="font-size:clamp(1rem,2vw,1.4rem);font-weight:800;">核心业务</span>
    <span class="tag tag-blue" style="font-size:clamp(0.8rem,1.4vw,1rem);">子业务A</span>
    <span class="tag tag-blue" style="font-size:clamp(0.72rem,1.2vw,0.9rem);">子业务B</span>
</div>
```

---

---

## 表格

### 标准数据表 `slide-table`
带交替行背景，适合最多 5 行 × 4 列的结构化数据。
```html
<div style="overflow:hidden;border-radius:8px;border:1px solid var(--border);width:100%;" class="reveal">
    <table style="width:100%;border-collapse:collapse;font-size:var(--small-size);">
        <thead>
            <tr style="background:var(--primary);color:#fff;">
                <th style="padding:0.5rem 0.9rem;text-align:left;font-weight:600;">维度</th>
                <th style="padding:0.5rem 0.9rem;text-align:left;font-weight:600;">现状</th>
                <th style="padding:0.5rem 0.9rem;text-align:right;font-weight:600;">目标</th>
                <th style="padding:0.5rem 0.9rem;text-align:right;font-weight:600;">差距</th>
            </tr>
        </thead>
        <tbody>
            <tr style="background:#fff;border-bottom:1px solid var(--border);">
                <td style="padding:0.45rem 0.9rem;font-weight:600;color:var(--text-1);">指标 A</td>
                <td style="padding:0.45rem 0.9rem;color:var(--text-2);">数据1</td>
                <td style="padding:0.45rem 0.9rem;text-align:right;color:var(--text-2);">数据2</td>
                <td style="padding:0.45rem 0.9rem;text-align:right;font-weight:700;color:var(--red);">▲ 差距值</td>
            </tr>
            <tr style="background:var(--bg-page);border-bottom:1px solid var(--border);">
                <td style="padding:0.45rem 0.9rem;font-weight:600;color:var(--text-1);">指标 B</td>
                <td style="padding:0.45rem 0.9rem;color:var(--text-2);">数据3</td>
                <td style="padding:0.45rem 0.9rem;text-align:right;color:var(--text-2);">数据4</td>
                <td style="padding:0.45rem 0.9rem;text-align:right;font-weight:700;color:var(--green);">▲ 已达标</td>
            </tr>
            <tr style="background:#fff;">
                <td style="padding:0.45rem 0.9rem;font-weight:600;color:var(--text-1);">指标 C</td>
                <td style="padding:0.45rem 0.9rem;color:var(--text-2);">数据5</td>
                <td style="padding:0.45rem 0.9rem;text-align:right;color:var(--text-2);">数据6</td>
                <td style="padding:0.45rem 0.9rem;text-align:right;font-weight:700;color:var(--text-3);">持平</td>
            </tr>
        </tbody>
    </table>
</div>
```

---

## 数据可视化（SVG）

SVG 图表无需外部库，`fill`/`stroke` 使用 CSS 变量自动跟随主题色。使用时按实际数据调整矩形高度和折线坐标。

### 竖向柱状图
```html
<svg viewBox="0 0 520 240" style="width:100%;max-width:520px;height:auto;display:block;margin:0 auto;" class="reveal">
    <!-- 网格线 -->
    <line x1="60" y1="20" x2="60" y2="190" stroke="var(--border,#E4E8EF)" stroke-width="1.5"/>
    <line x1="60" y1="190" x2="500" y2="190" stroke="var(--border,#E4E8EF)" stroke-width="1.5"/>
    <line x1="60" y1="130" x2="500" y2="130" stroke="var(--border,#E4E8EF)" stroke-width="1" stroke-dasharray="4,3"/>
    <line x1="60" y1="70"  x2="500" y2="70"  stroke="var(--border,#E4E8EF)" stroke-width="1" stroke-dasharray="4,3"/>
    <!-- Y轴刻度 -->
    <text x="52" y="194" text-anchor="end" style="fill:var(--text-3,#8C8C8C);font-size:11px;">0</text>
    <text x="52" y="134" text-anchor="end" style="fill:var(--text-3,#8C8C8C);font-size:11px;">50%</text>
    <text x="52" y="74"  text-anchor="end" style="fill:var(--text-3,#8C8C8C);font-size:11px;">100%</text>
    <!-- 柱子：高度 = (值/最大值) × 120，y = 190 - 高度 -->
    <rect x="90"  y="70"  width="55" height="120" style="fill:var(--primary,#1677FF);" rx="3" opacity="0.9"/>
    <text x="117" y="65"  text-anchor="middle" style="fill:var(--text-1,#141414);font-size:12px;font-weight:700;">100%</text>
    <text x="117" y="208" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Q1</text>

    <rect x="200" y="100" width="55" height="90"  style="fill:var(--primary,#1677FF);" rx="3" opacity="0.75"/>
    <text x="227" y="95"  text-anchor="middle" style="fill:var(--text-1,#141414);font-size:12px;font-weight:700;">75%</text>
    <text x="227" y="208" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Q2</text>

    <rect x="310" y="88"  width="55" height="102" style="fill:var(--primary,#1677FF);" rx="3" opacity="0.85"/>
    <text x="337" y="83"  text-anchor="middle" style="fill:var(--text-1,#141414);font-size:12px;font-weight:700;">85%</text>
    <text x="337" y="208" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Q3</text>

    <rect x="420" y="50"  width="55" height="140" style="fill:var(--primary-dark,#0950D9);" rx="3" opacity="0.9"/>
    <text x="447" y="45"  text-anchor="middle" style="fill:var(--text-1,#141414);font-size:12px;font-weight:700;">116%</text>
    <text x="447" y="208" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Q4</text>
</svg>
```

### 折线图（带面积）
```html
<svg viewBox="0 0 520 220" style="width:100%;max-width:520px;height:auto;display:block;margin:0 auto;" class="reveal">
    <defs>
        <linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%" stop-color="#1677FF" stop-opacity="0.18"/>
            <stop offset="100%" stop-color="#1677FF" stop-opacity="0"/>
        </linearGradient>
    </defs>
    <!-- 网格线 -->
    <line x1="60" y1="20" x2="60" y2="170" stroke="var(--border,#E4E8EF)" stroke-width="1.5"/>
    <line x1="60" y1="170" x2="500" y2="170" stroke="var(--border,#E4E8EF)" stroke-width="1.5"/>
    <line x1="60" y1="110" x2="500" y2="110" stroke="var(--border,#E4E8EF)" stroke-width="1" stroke-dasharray="4,3"/>
    <line x1="60" y1="50"  x2="500" y2="50"  stroke="var(--border,#E4E8EF)" stroke-width="1" stroke-dasharray="4,3"/>
    <!-- Y轴刻度 -->
    <text x="52" y="174" text-anchor="end" style="fill:var(--text-3,#8C8C8C);font-size:11px;">0</text>
    <text x="52" y="114" text-anchor="end" style="fill:var(--text-3,#8C8C8C);font-size:11px;">50</text>
    <text x="52" y="54"  text-anchor="end" style="fill:var(--text-3,#8C8C8C);font-size:11px;">100</text>
    <!-- 面积 -->
    <path d="M100,140 L200,105 L300,75 L400,50 L480,62 L480,170 L100,170 Z" fill="url(#areaGrad)"/>
    <!-- 折线 -->
    <polyline points="100,140 200,105 300,75 400,50 480,62"
              fill="none" style="stroke:var(--primary,#1677FF);" stroke-width="2.5"
              stroke-linejoin="round" stroke-linecap="round"/>
    <!-- 数据点 -->
    <circle cx="100" cy="140" r="4" style="fill:var(--primary,#1677FF);" stroke="#fff" stroke-width="2"/>
    <circle cx="200" cy="105" r="4" style="fill:var(--primary,#1677FF);" stroke="#fff" stroke-width="2"/>
    <circle cx="300" cy="75"  r="4" style="fill:var(--primary,#1677FF);" stroke="#fff" stroke-width="2"/>
    <circle cx="400" cy="50"  r="4" style="fill:var(--primary-dark,#0950D9);" stroke="#fff" stroke-width="2"/>
    <circle cx="480" cy="62"  r="4" style="fill:var(--primary,#1677FF);" stroke="#fff" stroke-width="2"/>
    <!-- X轴标签 -->
    <text x="100" y="190" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Jan</text>
    <text x="200" y="190" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Feb</text>
    <text x="300" y="190" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Mar</text>
    <text x="400" y="190" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">Apr</text>
    <text x="480" y="190" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:11px;">May</text>
</svg>
```

### 环形图（带图例）
```html
<!-- 总周长 ≈ 440（r=70）；各段 dasharray 第一值 = 占比 × 440 -->
<div style="display:flex;align-items:center;justify-content:center;gap:2rem;" class="reveal">
    <svg viewBox="0 0 200 200" style="width:clamp(120px,18vw,180px);flex-shrink:0;">
        <!-- 背景轨道 -->
        <circle cx="100" cy="100" r="70" fill="none" stroke="var(--bg-page,#F5F7FA)" stroke-width="30"/>
        <!-- 分段1：45% = 198 -->
        <circle cx="100" cy="100" r="70" fill="none" style="stroke:var(--primary,#1677FF);"
                stroke-width="30" stroke-dasharray="198 242" stroke-dashoffset="0"
                transform="rotate(-90 100 100)"/>
        <!-- 分段2：30% = 132，偏移 -198 -->
        <circle cx="100" cy="100" r="70" fill="none" style="stroke:var(--primary-light,#4096FF);"
                stroke-width="30" stroke-dasharray="132 308" stroke-dashoffset="-198"
                transform="rotate(-90 100 100)"/>
        <!-- 分段3：25% = 110 -->
        <circle cx="100" cy="100" r="70" fill="none" style="stroke:var(--primary-pale,#E6F4FF);"
                stroke-width="30" stroke-dasharray="110 330" stroke-dashoffset="-330"
                transform="rotate(-90 100 100)"/>
        <!-- 中心文字 -->
        <text x="100" y="96"  text-anchor="middle" style="fill:var(--text-1,#141414);font-size:18px;font-weight:900;">75%</text>
        <text x="100" y="113" text-anchor="middle" style="fill:var(--text-3,#8C8C8C);font-size:10px;">核心指标</text>
    </svg>
    <!-- 图例 -->
    <div style="display:flex;flex-direction:column;gap:0.5rem;">
        <div style="display:flex;align-items:center;gap:0.5rem;">
            <span style="width:12px;height:12px;border-radius:3px;background:var(--primary,#1677FF);flex-shrink:0;"></span>
            <span style="font-size:var(--small-size);color:var(--text-2);">类别 A &nbsp;<strong style="color:var(--text-1);">45%</strong></span>
        </div>
        <div style="display:flex;align-items:center;gap:0.5rem;">
            <span style="width:12px;height:12px;border-radius:3px;background:var(--primary-light,#4096FF);flex-shrink:0;"></span>
            <span style="font-size:var(--small-size);color:var(--text-2);">类别 B &nbsp;<strong style="color:var(--text-1);">30%</strong></span>
        </div>
        <div style="display:flex;align-items:center;gap:0.5rem;">
            <span style="width:12px;height:12px;border-radius:3px;background:var(--primary-pale,#E6F4FF);flex-shrink:0;"></span>
            <span style="font-size:var(--small-size);color:var(--text-2);">类别 C &nbsp;<strong style="color:var(--text-1);">25%</strong></span>
        </div>
    </div>
</div>
```

---

## 页面骨架

除了单个组件，当前模板里已经沉淀出几类更稳定的“整页布局骨架”。生成时优先复用这些骨架，而不是每页都重新拼布局。

### 月份路线图 `roadmap-col`
4列月份路线图，顶部英文月份 + 大字中文月份作为列头，下方卡片顶部带彩色渐变色条标识阶段。适合季度/月度计划、项目推进节奏。
```html
<div style="display:flex;flex-direction:column;gap:clamp(0.8rem,1.6vh,1.1rem);">
    <!-- 月份列头 -->
    <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(0.7rem,1.3vw,1rem);align-items:end;">
        <div style="padding:0 0.4rem 0.5rem;border-bottom:1px solid rgba(22,119,255,0.12);min-height:78px;display:flex;align-items:flex-end;">
            <div>
                <div style="font-size:clamp(0.66rem,0.95vw,0.8rem);font-weight:800;letter-spacing:0.08em;color:#7B8BA5;text-transform:uppercase;margin-bottom:0.28rem;">March</div>
                <div style="font-size:clamp(1.65rem,3.1vw,2.3rem);font-weight:900;color:#22324A;line-height:1;">3月</div>
            </div>
        </div>
        <!-- 重复 4月/April、5月/May、6月/June -->
    </div>

    <div style="font-size:var(--small-size);color:#7B8BA5;font-weight:800;letter-spacing:0.08em;text-transform:uppercase;">关键事项</div>

    <!-- 内容卡片列（顶部色条区分阶段） -->
    <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:clamp(0.6rem,1.2vw,0.9rem);align-items:start;">
        <div style="background:linear-gradient(180deg,#FEFEFF 0%,#F6FAFF 100%);border:1px solid rgba(22,119,255,0.10);border-radius:22px;padding:clamp(0.85rem,1.5vw,1.1rem);box-shadow:0 10px 24px rgba(22,119,255,0.05);position:relative;overflow:hidden;">
            <!-- 顶部色条：每个阶段换一组渐变色 -->
            <div style="position:absolute;left:0;top:0;width:100%;height:4px;background:linear-gradient(90deg,#1677FF 0%,#69B1FF 100%);"></div>
            <div style="display:flex;align-items:center;justify-content:space-between;gap:0.7rem;margin-bottom:0.7rem;">
                <div style="font-size:clamp(1.08rem,1.9vw,1.4rem);font-weight:900;color:#22324A;">阶段名称</div>
                <!-- 阶段 pill 标签 -->
                <span style="display:inline-flex;align-items:center;padding:0.28em 0.72em;border-radius:999px;background:rgba(22,119,255,0.09);color:#1766D1;font-size:clamp(0.58rem,0.85vw,0.7rem);font-weight:800;">状态描述</span>
            </div>
            <div style="display:flex;flex-direction:column;gap:0.5rem;">
                <div style="font-size:clamp(0.68rem,1.05vw,0.82rem);color:var(--text-3);padding:0.3em 0;border-bottom:1px solid rgba(0,0,0,0.04);line-height:1.5;">事项一</div>
                <div style="font-size:clamp(0.68rem,1.05vw,0.82rem);color:var(--text-3);padding:0.3em 0;border-bottom:1px solid rgba(0,0,0,0.04);line-height:1.5;">事项二</div>
                <div style="font-size:clamp(0.68rem,1.05vw,0.82rem);color:var(--text-3);padding:0.3em 0;line-height:1.5;">事项三</div>
            </div>
        </div>
        <!-- 重复其他阶段卡片，色条颜色建议：
             阶段1: #1677FF → #69B1FF
             阶段2: #2F54EB → #85A5FF
             阶段3: #13C2C2 → #5CDBD3
             阶段4: #FA8C16 → #FFC069 -->
    </div>
</div>
```

### 洞察数据面板 `insight-panel`
一句核心判断 + 内嵌 2×2 小数字格 + 补充说明。适合"数据结论页"中用一个大卡片承载观点+数据，比纯 `stat-block` 多了结论句，比纯文字段落更有视觉层次。
```html
<div style="background:linear-gradient(135deg,#F7FAFF 0%,#EEF5FF 52%,#FFFFFF 100%);border:1px solid rgba(22,119,255,0.12);border-radius:20px;padding:clamp(0.9rem,1.5vw,1.15rem);box-shadow:0 12px 30px rgba(22,119,255,0.06);display:flex;flex-direction:column;gap:0.65rem;position:relative;overflow:hidden;">
    <!-- 装饰性径向渐变光圈（可选） -->
    <div style="position:absolute;right:-0.35rem;top:-0.4rem;width:3rem;height:3rem;border-radius:50%;background:radial-gradient(circle,rgba(22,119,255,0.16) 0%,rgba(22,119,255,0) 72%);"></div>
    <!-- 标签 + 时间范围 -->
    <div style="display:flex;align-items:center;justify-content:space-between;gap:0.7rem;">
        <span class="tag tag-blue">阶段观察</span>
        <div style="font-size:clamp(0.62rem,0.9vw,0.72rem);font-weight:800;letter-spacing:0.08em;color:#7B8BA5;text-transform:uppercase;">Q1 2026</div>
    </div>
    <!-- 核心判断句 -->
    <div style="font-size:clamp(1.28rem,2vw,1.7rem);font-weight:900;color:#22324A;line-height:1.18;max-width:92%;">这里写一句有力的核心判断，不超过 20 字</div>
    <!-- 内嵌 2×2 小数字格 -->
    <div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0.55rem;">
        <div style="background:rgba(255,255,255,0.86);border:1px solid rgba(22,119,255,0.08);border-radius:14px;padding:0.58rem 0.68rem;">
            <div style="font-size:clamp(1.45rem,2.4vw,2rem);font-weight:900;color:#1677FF;line-height:1;">98%</div>
            <div style="font-size:clamp(0.58rem,0.86vw,0.68rem);color:#5B6B82;margin-top:0.14rem;">指标名称</div>
        </div>
        <div style="background:rgba(255,255,255,0.86);border:1px solid rgba(22,119,255,0.08);border-radius:14px;padding:0.58rem 0.68rem;">
            <div style="font-size:clamp(1.45rem,2.4vw,2rem);font-weight:900;color:#1677FF;line-height:1;">200%</div>
            <div style="font-size:clamp(0.58rem,0.86vw,0.68rem);color:#5B6B82;margin-top:0.14rem;">另一指标</div>
        </div>
    </div>
    <!-- 补充说明 -->
    <div style="font-size:clamp(0.62rem,0.92vw,0.73rem);line-height:1.55;color:#4F5F77;">下一步重点或补充说明，1-2 句话。</div>
</div>
```

### 统一标题区 `slide-header center-stack`
所有内容页的标题和副标题应固定在同一位置，不随正文内容浮动。正文区域统一放到 `slide-body`。

```html
<section class="slide slide-white">
    <div class="slide-header center-stack">
        <span class="header-mark"></span>
        <h2 class="header-title">简短标题</h2>
        <p class="header-sub">一句副标题，解释这一页要回答什么问题。</p>
    </div>
    <div class="slide-body" style="justify-content:center;">
        <!-- 正文内容 -->
    </div>
</section>
```

### 开场钩子页
适合“Opening Hook + 2×2 信息面板 + 背景底纹”的第一页正文。

```html
<section class="slide slide-white">
    <div class="slide-header center-stack">
        <h2 class="header-title">整页骨架</h2>
        <p class="header-sub">适合开场问题、外部变化或高势能背景页。</p>
    </div>
    <div class="slide-body" style="justify-content:center;padding-top:clamp(0.8rem,2vh,1.4rem);">
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:clamp(0.8rem,1.8vw,1.4rem);width:100%;" class="reveal">
            <div class="info-card">...</div>
            <div class="info-card">...</div>
            <div class="info-card">...</div>
            <div class="info-card">...</div>
        </div>
    </div>
</section>
```

### 分组目录页
适合“为什么 / 做什么 / 怎么做”这类三段式汇报。比纯平铺议程更适合管理层沟通。

```html
<section class="slide slide-white">
    <div class="slide-header center-stack">
        <h2 class="header-title">分组目录</h2>
        <p class="header-sub">目录按阶段聚类，而不是简单平铺。</p>
    </div>
    <div class="slide-body" style="justify-content:center;gap:clamp(0.55rem,1.2vh,0.9rem);max-width:72%;margin:0 auto;">
        <div style="font-size:clamp(0.6rem,0.9vw,0.75rem);font-weight:700;color:var(--text-3);letter-spacing:0.1em;text-transform:uppercase;">为什么</div>
        <div class="agenda-item reveal">...</div>
        <div style="border-top:1.5px solid rgba(0,0,0,0.06);"></div>
        <div style="font-size:clamp(0.6rem,0.9vw,0.75rem);font-weight:700;color:var(--text-3);letter-spacing:0.1em;text-transform:uppercase;">做什么</div>
        <div class="agenda-item reveal">...</div>
        <div style="border-top:1.5px solid rgba(0,0,0,0.06);"></div>
        <div style="font-size:clamp(0.6rem,0.9vw,0.75rem);font-weight:700;color:var(--text-3);letter-spacing:0.1em;text-transform:uppercase;">怎么做</div>
        <div class="agenda-item reveal">...</div>
    </div>
</section>
```

### 双阶段桥接流程
比单行 `step-flow-grid` 更适合表达“先规划，再执行”的两段式推进逻辑。

```html
<section class="slide slide-white">
    <div class="slide-header center-stack">
        <h2 class="header-title">双阶段流程</h2>
        <p class="header-sub">适合表达先搭框架、再落地执行的结构。</p>
    </div>
    <div class="slide-body" style="justify-content:center;max-width:85%;margin:0 auto;">
        <div class="step-flow-top reveal">
            <div class="step-card step-card-1">...</div>
            <div class="step-flow-arrow">→</div>
            <div class="step-card step-card-2">...</div>
        </div>
        <div class="step-flow-bridge reveal"><span>进入执行层</span></div>
        <div class="step-flow-bottom reveal">
            <div class="step-card step-card-3">...</div>
            <div class="step-card step-card-4">...</div>
        </div>
    </div>
</section>
```

### 横向支撑板 `support-board`
适合讲“专项体系”“横向支撑”“保障机制”。信息密度高，但比普通三列卡片更规整。

```html
<div class="support-board reveal">
    <div class="support-board-title">专项示例</div>
    <div class="support-board-main">横向支撑板布局</div>
    <div class="support-board-grid">
        <div class="support-board-col" style="border-left:4px solid var(--primary);">
            <div class="support-board-col-title">🔧 基础保障</div>
            <div class="support-board-items">
                <div class="support-mini-card">
                    <div class="support-mini-card-title">模板引擎 <span class="support-owner">平台</span></div>
                    <div class="support-mini-card-body">说明文字。</div>
                </div>
                <div class="support-mini-card">...</div>
            </div>
        </div>
        <div class="support-board-col">...</div>
        <div class="support-board-col">...</div>
    </div>
</div>
```

### 流转看板 `demand-flow-board`
适合讲跨角色、跨阶段的协同链路。它不是标准图表，更像“过程地图”。

```html
<div class="demand-flow reveal">
    <div class="demand-flow-board">
        <div class="demand-flow-stage-head">
            <span>提出<span class="demand-flow-stage-role">业务方</span></span>
            <span>拆解<span class="demand-flow-stage-role">策划</span></span>
            <span>生成<span class="demand-flow-stage-role">系统</span></span>
            <span>审校<span class="demand-flow-stage-role">专家</span></span>
            <span>交付<span class="demand-flow-stage-role">演讲者</span></span>
        </div>
        <div class="demand-flow-stage-grid">
            <span></span><span></span><span></span><span></span><span></span>
        </div>
        <svg class="demand-flow-lines" viewBox="0 0 1000 240" preserveAspectRatio="none">
            <path d="M60,40 C150,40 170,90 260,90 S370,140 460,140 590,80 700,80 820,150 940,150"
                  fill="none" stroke="rgba(22,119,255,0.18)" stroke-width="6" stroke-linecap="round"/>
        </svg>
        <div class="demand-flow-track">
            <span class="demand-dot" style="left:8%;top:18%;width:12px;height:12px;background:var(--primary);"></span>
            <span class="demand-dot" style="left:25%;top:38%;width:10px;height:10px;background:var(--primary-light);"></span>
        </div>
    </div>
</div>
```

---

## 图标选取规则（info-card / step-card）

图标库见 `icons.md`（287 个 Feather Icons，按类别分组）。生成 `info-card` 或 `step-card` 时，按以下步骤选图标：

1. **按标题关键词匹配**：从下方映射表找最接近的图标名，再到 `icons.md` Grep 该名称取完整 SVG
2. **匹配不到时**：到 `icons.md` 对应类别下目视挑选语义最近的
3. **同一页的图标**：优先在同一类别内选，保持视觉一致性

**常用关键词 → 图标名映射：**

| 关键词 | 图标名 |
|---|---|
| 安全、防护、合规、权限 | `shield` |
| 数据、存储、数据库 | `database` |
| 增长、提升、上升、趋势 | `trending-up` |
| 下降、降低、减少 | `trending-down` |
| 统计、报表、柱状图 | `bar-chart-2` |
| 占比、分布、饼图 | `pie-chart` |
| 用户、用户量、DAU | `users` |
| 团队、人员、组织 | `users` |
| 速度、性能、效率 | `zap` |
| 配置、系统、设置 | `settings` |
| 代码、开发、研发 | `code` |
| 时间、周期、里程碑 | `clock` |
| 日历、排期、计划 | `calendar` |
| 目标、OKR、KPI | `target` |
| 成本、费用、收益 | `dollar-sign` |
| 质量、完成、达标 | `check-circle` |
| 风险、问题、告警 | `alert-triangle` |
| 网络、连接、集成 | `link-2` |
| 创新、想法、方案 | `zap` |
| 流程、步骤、推进 | `arrow-right` |
| 启动、上线、发布 | `play-circle` |
| 分析、调研、洞察 | `search` |
| 协作、沟通、对齐 | `message-circle` |
| 架构、分层、体系 | `layers` |
| 文档、记录、报告 | `file-text` |
| 邮件、通知、消息 | `mail` |
| 监控、观测、巡检 | `activity` |
| 服务器、基础设施 | `server` |
| 锁、加密、认证 | `lock` |
| 标签、分类、打标 | `tag` |
| 版本、分支、迭代 | `git-branch` |
| 下载、导出、交付 | `download` |
| 上传、导入、接入 | `upload` |
| 刷新、重试、同步 | `refresh-cw` |
| 搜索、过滤、筛选 | `filter` |

---

## 设计效果规范

以下效果可叠加在任意组件上，提升视觉层次感。统一使用已有 CSS 变量，不引入新颜色。

### 渐变浅色卡片背景
比纯白背景更有层次，适合重点卡片、数据面板。
```html
style="background:linear-gradient(180deg,#FEFEFF 0%,#F6FAFF 100%);
       border:1px solid rgba(22,119,255,0.10);
       border-radius:18px;
       box-shadow:0 8px 24px rgba(22,119,255,0.05);"
```

### 顶部色条（阶段/状态标识）
卡片顶部 4px 渐变色条，用颜色区分阶段。放在 `position:relative;overflow:hidden` 的父容器内。
```html
<!-- 蓝色（启动） -->
<div style="position:absolute;left:0;top:0;width:100%;height:4px;background:linear-gradient(90deg,#1677FF 0%,#69B1FF 100%);"></div>
<!-- 靛蓝（验证） -->
<div style="position:absolute;left:0;top:0;width:100%;height:4px;background:linear-gradient(90deg,#2F54EB 0%,#85A5FF 100%);"></div>
<!-- 青绿（迭代） -->
<div style="position:absolute;left:0;top:0;width:100%;height:4px;background:linear-gradient(90deg,#13C2C2 0%,#5CDBD3 100%);"></div>
<!-- 橙色（扩散） -->
<div style="position:absolute;left:0;top:0;width:100%;height:4px;background:linear-gradient(90deg,#FA8C16 0%,#FFC069 100%);"></div>
```

### 阶段 pill 标签
放在卡片标题右侧，用 `border-radius:999px` 实现胶囊形。颜色跟顶部色条配套。
```html
<!-- 蓝色 -->
<span style="display:inline-flex;align-items:center;padding:0.28em 0.72em;border-radius:999px;background:rgba(22,119,255,0.09);color:#1766D1;font-size:clamp(0.58rem,0.85vw,0.7rem);font-weight:800;">启动准备</span>
<!-- 橙色 -->
<span style="display:inline-flex;align-items:center;padding:0.28em 0.72em;border-radius:999px;background:rgba(250,140,22,0.10);color:#D46B08;font-size:clamp(0.58rem,0.85vw,0.7rem);font-weight:800;">规模推广</span>
```

### 径向渐变装饰光圈
放在卡片右上角，制造光晕感，不干扰文字。
```html
<div style="position:absolute;right:-0.35rem;top:-0.4rem;width:3rem;height:3rem;border-radius:50%;
            background:radial-gradient(circle,rgba(22,119,255,0.16) 0%,rgba(22,119,255,0) 72%);"></div>
```

### 彩色微型数字格
嵌入卡片内部的小型数据格，适合不需要单独占一格的辅助数据。
```html
<div style="background:rgba(22,119,255,0.05);border-radius:12px;padding:0.5rem 0.6rem;">
    <div style="font-size:clamp(0.96rem,1.45vw,1.12rem);font-weight:900;color:#1677FF;line-height:1;">35+</div>
    <div style="font-size:clamp(0.58rem,0.84vw,0.67rem);color:#5B6B82;margin-top:0.12rem;">指标名称</div>
</div>
<!-- 绿色版 -->
<div style="background:rgba(82,196,26,0.07);border-radius:12px;padding:0.5rem 0.6rem;">
    <div style="font-size:clamp(0.96rem,1.45vw,1.12rem);font-weight:900;color:#389E0D;line-height:1;">6+</div>
    <div style="font-size:clamp(0.58rem,0.84vw,0.67rem);color:#5B6B82;margin-top:0.12rem;">另一指标</div>
</div>
```

---

## 关键用法提示

1. **所有 clamp 字号**：不要硬编码 px，统一用 `var(--body-size)`、`var(--small-size)`、`var(--h3-size)` 等变量。
2. **间距**：用 `var(--content-gap)` 和 `var(--element-gap)`，或 `clamp(N, Nvw, N)` 格式。
3. **动画**：给外层容器加 `class="reveal"`，子元素会按顺序依次出现（最多 6 个子元素有自动 delay）。
4. **内容页宽度**：`style="max-width:85%;margin:0 auto;"` 控制内容区宽度，防止内容贴边。
5. **颜色变量**：`var(--primary)` 蓝、`var(--red)` 红、`var(--green)` 绿、`var(--orange)` 橙、`var(--text-1/2/3)` 深中浅文字、`var(--bg-page)` 浅灰背景。


path: examples/alibaba-ai-rollout.md
content: 阿里巴巴内部 AI 落地推进方案

受众：跨部门负责人
页数：8-10 页
署名：项目办公室｜组织效能团队
日期：2026-04-12

目标：做一份偏行动方案型的汇报，不要写成长论文，尽量用判断句、对比、步骤卡来组织内容。

大纲：

1. 为什么现在必须推进
- 行业已经进入 AI 工具普及期，观望成本高于试点成本。
- 真正拉开差距的不是是否接入模型，而是是否完成组织级工作流重构。

2. 当前现状
- 研发、运营、客服三个条线都已经自发探索，但重复试错严重。
- 团队之间提示词、模板、评估标准没有共享。
- 管理层关注 ROI，但一线更关注是否省时、省心、可控。

3. 推进原则
- 先高频，再复杂。
- 先辅助，再替代。
- 先标准化，再规模化。

4. 三阶段路径
- 第一阶段：统一工具入口与安全规范。
- 第二阶段：沉淀可复制场景，形成模板库。
- 第三阶段：把 AI 深度嵌入关键业务流程。

5. 预期收益
- 降低重复劳动。
- 缩短交付周期。
- 提高跨团队知识复用效率。

6. 风险与应对
- 输出质量波动：建立 review 和抽检机制。
- 数据安全顾虑：场景分级、脱敏、白名单。
- 业务团队抗拒：用小范围试点打样。


path: examples/alibaba-jack-ma.md
content: 清华经管学院顾问委员会委员、阿里巴巴集团主席马云

在清华经管学院2014毕业典礼上的演讲全文

2014年6月29日

　　各位老师，各位同学，大家好！

　　特别荣幸能够来参加这个毕业典礼。坐在下面我有点莫名其妙的感动，我突然记不得我的毕业典礼是怎么个情况。但从看风水和面相来看，我坐在下面一看，不得了，今天走过的人里面有很多人将来会成为中国政治、经济、文化中的灿烂明星。好好把握这个机会，要相信我看人的眼光。

　　刚才“马总”讲的我特别同意，今天很多人都不满意头上那顶帽子，没有一顶帽子是适合你的。因为这个帽子是学校给你的。我毕业26年，已经把自己的帽子拿掉了，没有一顶帽子是适合我的。也许在座的各位也一样，每一天在探求适合自己的那顶帽子，打造适合自己的帽子。

　　首先恭喜大家，祝福大家。这是中国最了不起的一所大学之一，尽管在我心里面，中国最好的大学是杭州师范大学。学校的知识总是不够用，但是社会上的知识是取之不尽的。杭师大给我的是学习的能力，获取知识的能力。清华很好，但是清华教的知识永远是不够用的，而你们今天所得到这个能力是取得知识的能力。我看到今天那么多阳光灿烂的笑脸，30年后不忘初心，依旧是这样的笑脸，这才是成功。

　　我今天在这谈一下我的感受和体验。高考我并不算很成功，考了几年，我数学第一年是1分那是真的，第二年考19分，第三年考了89分，但我从来没放弃过。我给大家一个提醒，一个建议，提醒是今天你们获得中国最荣誉的毕业证书，但是那只是一张纸，只证明这四年、六年或者八年，你父母为你付了很多的学费。这是一张学费的通知单而已，告诉你付了那么多学费，花了那么多时间做了很多的模拟考，但这仅仅是模拟考而已。我也给大家一个建议，如果你们毕业于清华大学，请大家用欣赏眼光看看杭师大的同学，如果你毕业于杭师大，请用欣赏的眼光看看自己，因为这社会上永远充满变化，永远充满着各种奇迹。

　　人生最后不管今天多么成功，你最后死的时候才能够看见你到底赢了还是亏了。所以我觉得我们刚刚开始起步。我也相信今天毕业以后，在座很多人都很担心，各种各样的担心，担心我是学经管的，毕业以后能当老板吗？能找到一个好老板吗？能够找到好公司吗？其实这些担心都有，每天都有。我刚创业的时候天天担心能不能活下来，到后来我担心这个公司会不会长大，到今天长大了我担心它会倒下，现在的担心比以前多多了。我们每时每刻处于这份担心中。担心很正常，不担心才不正常。所以我想给大家的建议，也是真实的感受：这30年来，我天天在担心，但是我只是担心自己不够努力，我担心自己没看清楚灾难，我担心自己没把握好机遇。但有一点不用担心，你们一定会遇到眼泪、冤枉、委屈、倒霉等各种事件，一定会碰上，这个不用担心，你碰到这个，就应早知道会来的。

　　这是一个纠结的时代。在座所有的人今天毕业于一个纠结的时代。这个时代看起来充满着怀疑，充满着各种的不信任。学校的老师对学生不信任，学生对老师不信任，媒体对大众不信任，大众对媒体不信任，甚至有各种的担忧，老百姓对政府也有各种的不信任。这世界看起来缺乏各种各样的机会，但这世界看起来又有各种各样的机会。这世界看起来年轻人似乎是可以无所不能，什么事情都可以做，但看起来年轻人什么事情又都做不了。所以我觉得这是一个纠结的时代，很恭喜大家来到了一个很了不起的纠结时代，因为纠结是一种变革，因为我们正在进入一个变革非常快速的时代。如果没有变革就不会有阿里巴巴的今天，阿里巴巴、马云有今天就是因为之前30年中国的变革。

但是我想跟大家讲我心里的感受，未来30年中国的变革会更大，机会更大。从我这个行业来讲，世界正从IT（信息技术）在走向DT（数据技术）。这两个字的差异背后，是思想、文化、社会方方面面都发生很大的差异。绝大部分的人今天站在IT的角度看待世界。什么是IT？IT是以我为主，方便我管理。而DT则是以别人为主，强化别人，支持别人，DT思想是只有别人成功，你才会成功。这是一个巨大的思想转变，由这个思想转变产生技术的转变。所以，我想跟大家讲，所有变革的时代都是年轻人的时代。当然，麻烦也会更多，但今天我看到那么多人以后，我在想70%、80%要成为阿里巴巴的员工就好了，我就不用那么担心了，真的。未来30年我想跟随大家，你们会改变这个世界，你们会把握这个机会。纠结、变革都是年轻人的机遇，也是这个时代的机遇。

　　不管你怎么看，我们经常说生意越来越难做。其实生意从来就没有好做的。年轻人纠结今天IT行业都由阿里巴巴、腾讯、百度搞去了。我们刚出来时也觉得机会给IBM、思科、微软拿走了。但是，你要相信，30年以后的中国企业一定比今天好，一定比今天大，30年后富人一定比今天多，30年以后的文化一定比今天丰富多彩，30年以后的年轻人一定超越我们。这就是世界的变化。我爷爷说我爸不如他，我爸说我不如他。我觉得我爸比我爷爷厉害，我比我爸厉害，你们会比我们厉害。

　　在变革的时代，我也特别想给大家分享一下我自己的经历，之前30年我是坚持三样东西，我也希望大家去反思和思考这三样对你是否有用，就是三个坚持。第一永远坚持理想主义，第二要坚持担当精神，第三要坚持乐观的正能量。

　　我相信未来，我相信别人超过自己。其实在阿里巴巴，我数学不好，管理也没学过，会计也不懂，连预算报表、财务报表，到今天为止，我也看不懂。这是真话，我并没有觉得这是丢人的。承认自己不懂并不丢人，不懂装懂很丢人。我到今天为止没到淘宝上购过一件物，我没用过支付宝，因为我不知道该怎么用。但我耳朵竖起来，我老是在听支付宝到底好还是不好。因为如果我用多了，就会捍卫自己的产品。但是我不用，我永远担忧自己，因为只有担忧，让我晚上睡不着觉，只有我睡不着觉，公司才睡得着觉。我们看了《中国合伙人》，这个电影很好，但是这个电影有很大的问题，男主人公老哭。其实创业者是不哭的，是让别人哭。所以我们永远相信未来，相信年轻人，相信别人，我如果不相信别人，阿里巴巴的程序写不出来；我不相信别人，今天市场不会做得这么大。我只是告诉大家什么是我们要坚持的。

　　第二个要有担当精神。支付宝今天存在巨大的争议，其实在2004年准备做支付宝，做阿里金融的时候，我知道有一天会碰到这样的麻烦，我也纠结过。后来在达沃斯会议上听很多的政治家、企业家在谈论，什么是担当。你觉得是对的，对社会发展有利，你真相信，就勇敢地担当起来去做。我记得那次会议以后，我在达沃斯打电话给公司说，立刻、现在、马上去做，如果出问题我愿意去解决。去年年初，在阿里金融内部的会议上，我跟所有的同事讲，如果我们对中国金融改革有激活，有创新，如果基于这个，有人要付出代价，我来。我相信大家如果真的带着完善这个社会的希望，激活金融，服务实业，稳妥创新，我们一定越走越好，因为社会总会越来越清晰。

　　在今天的社会缺乏理想主义，缺乏担当的时候，更需要理想主义，更需要担当。不仅仅是你需要，不仅仅是社会需要，社会最缺的东西是最稀缺的资源，做那些别人不愿意做的、但最需要的事情，才有成就。有人说这个社会非常大，每天淘宝有几千万笔交易在进行，几千万人把自己的包裹送给一个完全不认识的人，交给不认识的快递员，辗转反复几千公里送给另外一个人，这是以前不可想象的。这是我们今天年轻人在以不同的方法，在以技术的方法表达“信任”真正存在。

　　第三个我希望大家坚持正能量，乐观地看待问题。我是犯过无数错误的人，今天阿里在前面15年内至少有100多次灭顶之灾，但都过来了。可以这么讲，今天再来一遍，我们今天的人比那时候的多，我们今天的人知识和能力比那时候强，但是重新再走一遍的话，我们一定走不出来。但是我们怎么走出来的？我们坚持乐观，我们相信这个世界你不成功有人会成功，我相信阿里巴巴、淘宝能做得出来的，一定有人也做得出来，我们相信有人花更多的时间在学习这些东西，只是看我们是否够运气。所以我后来给自己的座右铭，也是给所有年轻人，给我同事的座右铭：“今天很残酷，明天更残酷，后天很美好，但是绝大部分人死在明天晚上”。这就是残酷的生活。所以你今天必须很努力，才能面对明天的残酷，明天你必须很努力，才有可能看到后天的太阳。但是绝大部分人是看不到太阳的。你光努力还不够，还有运气，运气从哪里来？运气就是在自己好的时候多想想别人，自己不好的时候多检查检查自己。我相信会走过来。

　　今天我看到大家的微笑。这世界上最有力量的武器是用微笑化解所有的问题。我永远面带笑容，尽管我内伤很重。在中国这样的市场环境下诞生，阿里巴巴是一个偶然，也是一个必然。因为市场机制，因为一帮年轻人相信我，我们在市场上能够做出这样的东西来。

　　在座的每一个人，你们都经历了无数的挑战。我跟公司同事讲，很多人说没有机会，我们从来就没赢过。我说，你赢过，在你出生之前，赢的是几亿颗精子和卵子赛跑出来的，来到这个世界，你就成功了。来到这个世界，你们又经过无数的考试进入了清华大学，获得了今天的毕业证书，你们已经有良好的起步，良好的机会，有很好的基础。但未必有基础的人都会赢，未必今天跑得快的人还是能走得很快。这世界就像足球一样，是圆的。我没有想过杭州师范大学的人可以当清华经管学院顾问委员会委员，感谢钱院长给我的信任。世界是圆的，所以大家记住，今天你最好，未必明天最好；今天你最差，社会给了你很多的机会，只要你把握，只要努力，总会有机会。

　　最后给大家一个建议：永远相信你的对手不在你边上，在你边上的都是你的榜样，哪怕这个人你特别讨厌。很多年以前我说，我用望远镜都没有找到过对手，人家说你好骄傲。其实他们没有听我下一句：我望远镜找的不是对手，找的是榜样。你的对手可能在以色列，可能在你不知道什么地方，他比你更用功。你今天获得了清华的毕业证书，不学习了，不读书了，因为你觉得我毕业于清华，而那个人毕业于杭师大。但他不断在学习，他不断在努力，不断在进取。所以这一点是我希望给大家讲的，战胜自己就是真正的英雄。

　　我想，我们人类今天共同面临巨大的挑战，就是知识和教育跟不上技术的发展。但这正是我们的机会。哪里有抱怨，哪里就是机会。中国电子商务发展得这么好，跟阿里巴巴其实没什么关系，是中国原来经济的基础设施太差，我们相信了这件事情，走了十年而已。今天中国的电子商务超越了美国电子商务的总和，原因不是因为美国不努力，而是美国昨天的基础太好。美国没有互联网金融，是因为美国的金融环境实在太好，根本插不进去。中国的金融环境不太好，才给我们机会。所有昨天不好的事情都是你的机会，别人在抱怨的时候，才使你看到机会所在。

　　阿里有一样东西也是我想给大家分享的，我花30年走到今天，不是3年。我们明白一个道理，什么是战略，就是做未来最重要的事情。坚持理想，坚持正能量，坚持乐观，坚持脚踏实地。我们从来就没有做成过一件今天做明天成功的事情，或者今年做明年成功的事情。因为这样的机会永远轮不到我。今天，你们最大的资本是年轻。因为年轻，你可以花十年时间打败阿里巴巴，打败淘宝。如果你有这个想法，也许只要五年；但如果你希望明年就打败，那你可能一辈子都打败不了。

　　谢谢大家！

path: examples/antgroup-eric.md
content: 蚂蚁集团董事长兼CEO井贤栋，2021世界互联网大会企业家高峰论坛

尊敬的各位领导，各位嘉宾：

下午好。

很高兴参加世界互联网大会「企业家高峰论坛」，和大家一起探讨数字化的未来。

过去一年多来，中国科技抗疫的实践和成果令世界瞩目。这期间加速培育的数字化活力，已经成为经济可持续增长的新动能，也让我们这些从业者必须思考：什么样的数字化是社会需要的？哪些数字化领域应该是我们重点去完善的？数字化带来的好处显而易见，数字化带来的问题又该如何解决？

受益于时代的发展，蚂蚁集团得以参与到全社会的数字化实践，我想谈谈我理解的社会需要的数字化：

第一，数字化发展的根本目标是让老百姓受益，而不是个别企业的数字化。让每一个有需要的个体和小微企业实现数字化，才是真正的数字化。各地政府的数字化办事让群众少跑腿、电子商务和移动支付让百姓生活更便捷，等等这一切正让人们生活越来越好。

第二，开放共享是数字化发展的基本精神。近期国家对互联网发展提出了一系列政策指引和要求，核心之一就是要构建开放共享的数字生态，实现要素的流动循环、竞争的良性有序、创新的持续活跃和数字鸿沟的有效弥合。这一决策部署非常及时和重要，也提醒我们这些从业者不要被竞争扰乱，而是回到互联网最初的本质上来。

第三，绝不能因为数字化牺牲安全和隐私保护。有人认为，数字化时代没有隐私。而我们相信，数字化发展的前提是必须保护好数据安全和个人隐私，这也是全球的共识！国家已经制定了相对健全的法律法规，需要从业者心存敬畏，不折不扣地遵照执行，同时，也需要加强技术攻坚，在隐私计算等核心技术领域寻求突破。

各位领导，各位嘉宾，国家十四五规划明确提出“加快数字化发展，建设数字中国”，而发展数字经济，推动高质量发展，关键是充分调动企业数字化转型的内生动力，尤其是充分释放中小企业、中小机构的活力。我们认为，有四个方面极其关键：

1、服务业小微商家的数字化经营

中国有几千万的小微商家，但真正迈上数字化经营的小微商家，还只是很少的一部分。疫情期间，我们发现，凡是具备一定数字化经营能力的小微商家，往往受冲击较小，也比较能快速地走上复苏的轨道。

移动支付只是部分解决了小微商家经营便利性的问题，但相对于数字经济的未来而言，小微商家的需求还远未满足。目前包括蚂蚁集团在内的很多从业者正探索IOT、小程序和商业SaaS等综合方案，力争帮助每一个小商家实现数字化经营、营销和管理，从而帮助他们降本、提效、增收。

2、中小金融机构的数字化升级

相对于大型金融机构在数字化上的投入，中小金融机构则面临着资源和技术等方面的巨大挑战。帮助中小金融机构升级，对于构建更安全，更高效，更健康的金融生态分外重要。从目前了解的情况来看，中小金融机构急需提升风险管理水平和数字化经营等科技能力，蚂蚁集团正积极参与这个领域的技术升级过程，并跟更多同行一起，做好技术服务商的角色。

3、产业链的数字化协作和价值创造

数字化越往纵深发展，通过产业协作创造全新的价值就越关键。如果说单个产业的数字化是建楼，产业协作的数字化就是修高速公路——今天的数字化发展已经到了“高速公路”的阶段，区块链技术则为产业协作提供了信任的基础。过去几年，区块链在版权保护、农产品溯源以及供应链金融等领域进行了一些探索，未来，云计算、区块链、物联网和人工智能等技术，会为产业互联网探索更高效更可信的技术解决方案，促进更多产业协作和价值创造。

4、全球跨境服务的数字化

过去两年，传统外贸行业加速向线上转型，大量创业者、小微企业借助数字技术的力量积极经营海外市场，成为“微型跨国企业”。这些跨境贸易的新主角，一方面为国家“双循环”注入了新动能，另一方面，他们的订单呈现小额、碎片化特征，也带来了一系列新的挑战。蚂蚁集团与众多同行一起，通过海内外创新布局，探索了一套服务小微企业跨境收付款和数字营销的解决方案，但这个领域空间仍非常巨大。

这些重点领域的数字化，对于激活千万中小商家和中小机构的创新活力，焕发经济增长的新动能，是正向有益的，但同时也是一条漫长的道路，需要耐住性子，需要不计得失，更需要守住底线的前提下做好创新与突破！

数字化的发展的过程中也同时提醒我们，必须要解决好数字包容性。今年开始，蚂蚁在全国30多个城市开展了3000多场蓝马甲公益行动，深入社区去帮助老年人做好数字化的辅导和服务，他们很欢迎，我们还是觉得很惭愧，类似的工作应该早点做、持续做。明年我们会继续深入更多城市，这是我们需要担当的社会责任和义务。

今年，国家提出了以高质量发展推动共同富裕的战略规划，数字化恰恰是实现共同富裕的重要方式之一。我们今天相聚乌镇，更是多了一份历史责任感和时不我待的迫切感。除了不断攻克数字化核心技术的难题，我们这些从业者需要以更加开放共享的姿态，共同携手参与中国数字化建设的进程，为推进普惠，包容和可持续的发展贡献一份力量！

path: examples/antgroup-quarterly-review.md
content: 蚂蚁集团 2026 Q1 季度复盘汇报

受众：管理层
页数：6-8 页
署名：吴飞飞｜体验技术负责人
日期：2026-04-12

请输出一份适合管理层会上的演示稿，风格简洁、判断清晰、单页不拥挤。

核心内容：

1. 本季度总体判断
- 支付主链路稳定，基础盘健康。
- AI 提效开始从局部试点走向团队级复用。
- 真正的瓶颈不是工具数量，而是业务接入深度和规范化程度。

2. 关键结果
- 核心支付链路成功率维持在 99.97%。
- 高优故障数同比下降 38%。
- AI 辅助研发覆盖率从 21% 提升到 57%。
- 需求交付平均周期缩短 18%。

3. 当前问题
- 不同团队对 AI 工具的使用深度差异明显。
- 复盘、文档、测试等高频动作仍有大量手工重复劳动。
- 一线团队有热情，但缺标准、缺模板、缺可复用最佳实践。

4. 下一阶段动作
- 建立统一提示词和页面模板资产。
- 把高频场景沉淀为标准工作流。
- 用质量红线约束 AI 生成内容，先守底线再扩覆盖。

5. 管理层需要支持
- 明确跨团队统一推进窗口。
- 给出试点团队资源倾斜。
- 将 AI 提效纳入季度经营复盘。


path: examples/before-after.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-Writer Before / After</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    background: #0f1117;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    font-family: -apple-system, "PingFang SC", "Helvetica Neue", sans-serif;
    padding: 48px;
  }

  .container {
    display: flex;
    align-items: center;
    gap: 32px;
    width: 100%;
    max-width: 1400px;
  }

  /* ── LEFT: Claude Code window ── */
  .panel-before {
    flex: 1;
    background: #1a1a1a;
    border-radius: 16px;
    overflow: hidden;
    border: 1px solid rgba(255,255,255,0.1);
    box-shadow: 0 24px 64px rgba(0,0,0,0.5);
    display: flex;
    flex-direction: column;
  }

  .panel-titlebar {
    background: #242424;
    padding: 12px 16px;
    display: flex;
    align-items: center;
    gap: 8px;
    border-bottom: 1px solid rgba(255,255,255,0.06);
    flex-shrink: 0;
  }

  .dot { width: 12px; height: 12px; border-radius: 50%; }
  .dot-red   { background: #ff5f57; }
  .dot-yellow{ background: #febc2e; }
  .dot-green { background: #28c840; }

  .titlebar-label {
    margin-left: 8px;
    font-size: 12px;
    color: rgba(255,255,255,0.4);
    font-family: "SF Mono", "Fira Code", monospace;
  }

  /* terminal install section */
  .terminal-section {
    background: #111;
    padding: 16px 20px;
    border-bottom: 1px solid rgba(255,255,255,0.06);
    font-family: "SF Mono", "Fira Code", monospace;
    font-size: 12.5px;
    line-height: 1.9;
  }

  .term-comment { color: rgba(255,255,255,0.28); }
  .term-prompt  { color: #4c8ef7; }
  .term-text    { color: rgba(255,255,255,0.7); }

  /* Claude Code chat UI */
  .chat-section {
    padding: 20px 20px 24px;
    display: flex;
    flex-direction: column;
    gap: 16px;
  }

  .chat-row {
    display: flex;
    gap: 12px;
    align-items: flex-start;
  }

  .avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    font-weight: 700;
    flex-shrink: 0;
    margin-top: 1px;
  }

  .avatar-user {
    background: #2d2d2d;
    color: rgba(255,255,255,0.6);
    border: 1px solid rgba(255,255,255,0.1);
  }

  .avatar-claude {
    background: #c17f3a;
    color: #fff;
    font-size: 10px;
  }

  .chat-bubble-user {
    background: #2a2a2a;
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 12px;
    padding: 10px 14px;
    font-size: 13.5px;
    color: rgba(255,255,255,0.85);
    line-height: 1.6;
  }

  .chat-bubble-user .slash-cmd {
    color: #a78bfa;
    font-weight: 600;
    font-family: "SF Mono", "Fira Code", monospace;
  }

  .chat-bubble-claude {
    font-size: 12.5px;
    color: rgba(255,255,255,0.55);
    line-height: 1.7;
    padding-top: 2px;
  }

  .chat-bubble-claude .progress {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
  }

  .spinner {
    width: 12px;
    height: 12px;
    border: 2px solid rgba(255,255,255,0.15);
    border-top-color: #4c8ef7;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
    flex-shrink: 0;
  }

  @keyframes spin { to { transform: rotate(360deg); } }

  .cursor {
    display: inline-block;
    width: 2px;
    height: 13px;
    background: rgba(255,255,255,0.6);
    margin-left: 1px;
    vertical-align: middle;
    animation: blink 1s step-end infinite;
  }

  @keyframes blink { 50% { opacity: 0; } }

  /* ── MIDDLE: arrow ── */
  .arrow-area {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    flex-shrink: 0;
  }

  .arrow-brand {
    font-size: 13px;
    font-weight: 700;
    color: #fff;
    letter-spacing: 0.02em;
    white-space: nowrap;
  }

  .arrow-brand span { color: #4c8ef7; }

  .arrow-svg {
    width: 56px;
  }

  .arrow-sub {
    font-size: 11px;
    color: rgba(255,255,255,0.3);
    text-align: center;
    line-height: 1.5;
    white-space: nowrap;
  }

  /* ── RIGHT: multi-slide stack ── */
  .panel-after {
    flex: 1.6;
    position: relative;
    height: 320px;
  }

  .slide-stack {
    position: relative;
    width: 100%;
    height: 100%;
  }

  .slide-card {
    position: absolute;
    border-radius: 12px;
    overflow: hidden;
    border: 1px solid rgba(255,255,255,0.12);
    box-shadow: 0 16px 48px rgba(0,0,0,0.6);
  }

  .slide-card img { width: 100%; display: block; }

  .slide-card:nth-child(1) {
    width: 78%;
    top: 0;
    left: 11%;
    z-index: 3;
    box-shadow: 0 20px 60px rgba(0,0,0,0.7), 0 0 0 1px rgba(76,142,247,0.25);
  }

  .slide-card:nth-child(2) {
    width: 70%;
    top: 20px;
    left: 0;
    z-index: 2;
    opacity: 0.75;
    transform: rotate(-4deg);
  }

  .slide-card:nth-child(3) {
    width: 70%;
    top: 20px;
    right: 0;
    z-index: 2;
    opacity: 0.75;
    transform: rotate(4deg);
  }

  .slide-card:nth-child(4) {
    width: 62%;
    top: 38px;
    left: 50%;
    transform: translateX(-50%) rotate(0deg);
    z-index: 1;
    opacity: 0.45;
  }

  .after-badge {
    position: absolute;
    bottom: -8px;
    right: 10%;
    background: rgba(20,20,20,0.85);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255,255,255,0.12);
    border-radius: 20px;
    padding: 5px 12px;
    font-size: 11px;
    color: rgba(255,255,255,0.55);
    font-family: "SF Mono", monospace;
    z-index: 10;
  }
</style>
</head>
<body>
<div class="container">

  <!-- LEFT: Claude Code window -->
  <div class="panel-before">
    <div class="panel-titlebar">
      <div class="dot dot-red"></div>
      <div class="dot dot-yellow"></div>
      <div class="dot dot-green"></div>
      <span class="titlebar-label">Claude Code</span>
    </div>

    <!-- terminal: install -->
    <div class="terminal-section">
      <div class="term-comment"># 安装 Slide-Writer skill</div>
      <div>
        <span class="term-prompt">$</span>
        <span class="term-text"> git clone https://github.com/FeeiCN/slide-writer.git ~/.claude/skills/slide-writer</span>
      </div>
    </div>

    <!-- chat: user message -->
    <div class="chat-section">
      <div class="chat-row">
        <div class="avatar avatar-user">你</div>
        <div class="chat-bubble-user">
          <span class="slash-cmd">/slide-writer</span> 帮我生成一个 Agent 的几种协作模式研究 为主题的演讲 PPT。
        </div>
      </div>

      <!-- chat: claude response -->
      <div class="chat-row">
        <div class="avatar avatar-claude">C</div>
        <div class="chat-bubble-claude">
          <div class="progress">
            <div class="spinner"></div>
            <span>正在生成演示文稿…</span>
          </div>
          <div>Phase 0 · 检测主题 → 蚂蚁集团</div>
          <div>Phase 2 · 规划结构 → 10 页</div>
          <div>Phase 3 · 生成 HTML<span class="cursor"></span></div>
        </div>
      </div>
    </div>
  </div>

  <!-- MIDDLE: arrow -->
  <div class="arrow-area">
    <div class="arrow-brand">Slide<span>Writer</span></div>
    <svg class="arrow-svg" viewBox="0 0 56 24" fill="none" xmlns="http://www.w3.org/2000/svg">
      <line x1="0" y1="12" x2="44" y2="12" stroke="#4c8ef7" stroke-width="2" stroke-linecap="round"/>
      <polyline points="36,4 48,12 36,20" stroke="#4c8ef7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
    </svg>
    <div class="arrow-sub">自动生成<br>企业级 PPT</div>
  </div>

  <!-- RIGHT: multi-slide stack -->
  <div class="panel-after">
    <div class="slide-stack">
      <div class="slide-card"><img src="examples/slide-writer.png" alt="Slide 1"></div>
      <div class="slide-card"><img src="examples/test-antgroup-eric.png" alt="Slide 2"></div>
      <div class="slide-card"><img src="examples/test-alibaba-jack-ma.png" alt="Slide 3"></div>
      <div class="slide-card"><img src="examples/test-tencent-pony-ma.png" alt="Slide 4"></div>
    </div>
    <div class="after-badge">单文件 HTML · 零依赖</div>
  </div>

</div>
</body>
</html>


path: examples/revise-existing-deck.md
content: 请直接修改现有 HTML 演示稿，不要重做整份 deck。

修改目标：

1. 把整份稿子的语气改得更像管理层汇报，少一些解释性废话，多一些结论句。
2. 目录页压缩成 5 个议程，不要超过 5 行。
3. 至少增加 1 页章节过渡页，让节奏更清楚。
4. 把最长的一页拆成两页，避免内容过满。
5. 如果某一页只有连续大段文字，改成卡片、对比或数字块结构。
6. 保留现有主题和 logo，不要改品牌风格。
7. 保留进度条、导航点、全屏、页脚和键盘翻页。


path: examples/tencent-pony-ma.md
content: 马化腾2026年（腾讯2025年度员工大会）发言全文

（2026年1月26日 腾讯内部员工大会）
各位同事，大家好。

先和大家回顾这几年我们走过的路，2021到2023年，行业寒冬来袭，我们果断提出聚焦主业、降本增效、减脂增肌，一步步稳扎稳打实现触底反弹，营收和利润重回增长轨道，更重要的是，我们把更多资源集中到了核心能力的打磨上，为后续的发展筑牢了根基。

2025年是AI大年，ChatGPT和DeepSeek改写了行业发展进程，整个行业都在加速奔跑，但大家要清楚，每个企业的基因不同、体质不同，腾讯的风格从来不是跟风追热点，而是稳扎稳打做长期事。过往几十年我们在社交、内容、云服务等领域的长青业务，给了我们在AI领域长期投入的底气，我们的AI战略，核心从来不是比谁跑得快，而是守住产品长期竞争力，把用户体验放在第一位，现在要做的，就是在各个业务板块、各个平台上，仔细琢磨、慢慢沉淀，持续点亮新技能，夯实我们的核心壁垒。

过去一年，我们在AI上做了不少扎实的调整，一方面补齐基础设施短板，另一方面大力吸引业界原生AI人才，像姚顺雨博士加入后，我们重构了AI研发团队，成立AI Infra部、AI Data部等核心部门，通过年轻化力量注入，让我们的AI研发更有活力。接下来，我们要把大模型和AI产品一体化统筹，用交叉派驻、Co-design的逻辑做产品和组织设计，TEG和CSIG是AI化、智能化的主力军，要扛起重任，推动技术和业务深度融合。

说到AI应用，元宝的成长大家有目共睹，从TEG的技术试验田，到转入CSIG成为AI应用先锋，日均迭代一个版本的速度，让用户规模快速冲进国内AI应用前三，现在元宝的能力已经接入QQ音乐、腾讯会议、微信公众号、视频号评论区等几十款核心应用，真正实现了AI服务无处不在。

这里和大家同步两个好消息，一是元宝春节期间会拿出10亿现金红包回馈用户，我们把节省下来的营销费用，实实在在给到用户，就是想让大家重温当年抢红包的快乐，更希望能重现2015年微信红包的高光时刻，借春节这个超级社交场景，让更多人感受到腾讯AI的价值；二是元宝即将上线全新社交功能“元宝派”，这个玩法灵感来自腾讯会议群聊，核心是在AI助手中叠加社交属性，打造专属社交空间，后续我们会开放QQ音乐、腾讯视频海量内容，大家在元宝派里能和好友一起看视频、一起听音乐，还能和元宝实时互动聊天，未来还会探索更多通信功能，这是我们结合自身社交和关系链优势的全新尝试，希望大家都去体验元宝2.55版本，多提建议、一起Debug，把这个产品打磨得更好。

再和大家复盘各事业群的2025年，也明确2026年的发力方向。
CSIG这边，去年最大的突破是云业务实现规模化盈利，这是里程碑式的成果，同时把QQ浏览器、腾讯会议、元宝等AI相关产品深度整合，协同效应逐步显现，接下来要继续深耕政企服务和C端场景，让AI赋能云服务更上一层楼。
WXG不用多说，始终是腾讯的中流砥柱，大家对微信寄予厚望，视频号、微信电商都有自己的成长节奏和逻辑，不用急于求成，核心是结合自身特点，稳妥推进AI智能化升级，我们坚持去中心化，不搞强制的AI全家桶，始终兼顾用户需求和隐私安全，只做底层连接，让创业者能在微信生态里打造自己的专属入口，这样的生态才更健康、更有生命力。
IEG去年的表现可以说强得可怕，全球游戏市场仅增长3%，我们实现了23%的增速，跑赢大盘20个百分点，海外业务营收突破100亿美元，占游戏业务30%，这背后是团队坚持做长青游戏，不盲目上新，而是在《王者荣耀》《和平精英》等经典产品里持续创新，同时赋能海外投资企业补齐能力，2026年要继续坚守内容初心，在精品化和全球化上再发力。
PCG今年的调整和探索值得肯定，QQ回归初心，让QQ秀、QQ农场等经典产品回归，还打通AI能力让用户自定义生成图像，精准触达老用户需求；腾讯新闻坚守高品质资讯，用AI帮用户提升信息浏览效率；应用宝实现安卓APP在Windows系统无缝运行，得到微软认可，打开了新场景；腾讯视频借助AI多模态能力，联动阅文推动自有版权影视化，不过去年爆款偏少，这和内容投入周期有关，大家要多些耐心，既要坚守长视频品质，也要布局广告驱动型产品，应对行业变化。

广告业务在AI加持下增速亮眼，但目前收入占比仍低于行业平均水平，未来商业化空间巨大，要继续深挖AI赋能潜力，做好精准投放和价值挖掘；FiT金融科技业务，始终把安全放在第一位，不追求发展速度，守住合规底线，稳步推进业务创新，守护用户资金安全。

最后想和大家说，未来人的时间只会越来越宝贵，帮用户节省时间、提升效率，是我们所有产品的核心发力点，这也是AI能给我们带来的核心价值。2026年又是拼搏的一年，希望大家立足自身岗位，发挥腾讯的基因优势，不断进化、持续点亮新技能，相信只要我们步调一致、沉心做事，腾讯的未来一定光明可期！

谢谢大家

path: scripts/apply-template-branding.py
content: #!/usr/bin/env python3
"""
apply-template-branding.py — 一次性把标题、主题样式、logo 写回 HTML 模板。

用法：
    python3 scripts/apply-template-branding.py <html-file> --title <title>
    python3 scripts/apply-template-branding.py <html-file> --title <title> --speaker <speaker>
    python3 scripts/apply-template-branding.py <html-file> --title <title> --speaker <speaker> --theme-id <theme-id>
    python3 scripts/apply-template-branding.py <html-file> --title <title> --theme-id <theme-id>
    python3 scripts/apply-template-branding.py <html-file> --title <title> --theme-id <theme-id> \
        [--primary-id <primary-id> --primary-alt <primary-alt>] \
        [--secondary-id <secondary-id> --secondary-alt <secondary-alt>]

默认：
    - theme-id = ant-group
    - logo 跟随 theme-id 自动推断
"""

from __future__ import annotations

import argparse
import html
import re
import sys
from pathlib import Path


ROOT_DIR = Path(__file__).resolve().parent.parent
LOGO_DIR = ROOT_DIR / "themes" / "logos"
TITLE_RE = re.compile(r"<title>.*?</title>", re.DOTALL)
THEME_STYLE_RE = re.compile(r"<!-- %%THEME_STYLE%% -->")
LOGO_BLOCK_RE = re.compile(
    r'<div id="globalLogoGroup" class="logo-group-(?:single|dual)"[^>]*>.*?</div>',
    re.DOTALL,
)
DEFAULT_THEME_LOGOS: dict[str, dict[str, str]] = {
    "ant-group": {
        "primary_id": "ant-group",
        "primary_alt": "蚂蚁集团",
        "secondary_id": "alipay",
        "secondary_alt": "支付宝",
    },
    "bytedance": {
        "primary_id": "bytedance",
        "primary_alt": "字节跳动",
    },
    "meituan": {
        "primary_id": "meituan",
        "primary_alt": "美团",
    },
    "jd": {
        "primary_id": "jd",
        "primary_alt": "京东",
    },
    "baidu": {
        "primary_id": "baidu",
        "primary_alt": "百度",
    },
    "huawei": {
        "primary_id": "huawei",
        "primary_alt": "华为",
    },
    "xiaomi": {
        "primary_id": "xiaomi",
        "primary_alt": "小米",
    },
    "netease": {
        "primary_id": "netease",
        "primary_alt": "网易",
    },
    "didi": {
        "primary_id": "didi",
        "primary_alt": "滴滴",
    },
    "microsoft": {
        "primary_id": "microsoft",
        "primary_alt": "Microsoft",
    },
    "google": {
        "primary_id": "google",
        "primary_alt": "Google",
    },
    "apple": {
        "primary_id": "apple",
        "primary_alt": "Apple",
    },
    "tencent": {
        "primary_id": "tencent",
        "primary_alt": "腾讯",
    },
    "alibaba": {
        "primary_id": "alibaba",
        "primary_alt": "阿里巴巴",
    },
}


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Apply title, theme style and logo branding to a local HTML file."
    )
    parser.add_argument("html_file", help="要替换 branding 的 HTML 文件")
    parser.add_argument("--title", required=True, help="演讲主题")
    parser.add_argument("--speaker", help="演讲者")
    parser.add_argument(
        "--theme-id",
        default="ant-group",
        help="主题 ID，对应 themes/<theme-id>.md，默认 ant-group",
    )
    parser.add_argument("--primary-id", help="主 logo 的品牌 ID，例如 ant-group")
    parser.add_argument("--primary-alt", help="主 logo 的 alt 文案，例如 蚂蚁集团")
    parser.add_argument("--secondary-id", help="副 logo 的品牌 ID，例如 alipay")
    parser.add_argument("--secondary-alt", help="副 logo 的 alt 文案，例如 支付宝")
    args = parser.parse_args()

    defaults = DEFAULT_THEME_LOGOS.get(args.theme_id, {"primary_id": args.theme_id})

    if not args.primary_id:
        args.primary_id = defaults["primary_id"]
        args.primary_alt = args.primary_alt or defaults.get("primary_alt") or args.primary_id
        if args.secondary_id is None:
            args.secondary_id = defaults.get("secondary_id")
        if args.secondary_alt is None:
            args.secondary_alt = defaults.get("secondary_alt")
    elif not args.primary_alt:
        args.primary_alt = args.primary_id

    return args


def build_title(title: str, speaker: str | None) -> str:
    return f"{title} — {speaker}" if speaker else title


def load_theme_style(theme_id: str) -> str:
    if theme_id == "ant-group":
        return "<!-- %%THEME_STYLE%% -->"

    theme_path = ROOT_DIR / "themes" / f"{theme_id}.md"
    if not theme_path.exists():
        raise FileNotFoundError(f"theme file not found: {theme_path}")

    content = theme_path.read_text(encoding="utf-8")
    match = re.search(r"## CSS\s+```css\n(.*?)\n```", content, re.DOTALL)
    if not match:
        raise ValueError(f"CSS block not found in theme file: {theme_path}")

    css = match.group(1).strip()
    return f"<style>\n{css}\n</style>"


def read_logo_data_uri(brand_id: str, variant: str) -> str | None:
    path = LOGO_DIR / f"{brand_id}-{variant}.txt"
    if not path.exists():
        return None
    return path.read_text(encoding="utf-8").strip() or None


def render_logo_img(css_class: str, src: str, alt: str, style: str | None = None) -> str:
    style_attr = f' style="{html.escape(style, quote=True)}"' if style else ""
    return (
        f'    <img class="{css_class}" src="{html.escape(src, quote=True)}" '
        f'alt="{html.escape(alt, quote=True)}"{style_attr}>'
    )


def render_logo_pair(brand_id: str, alt: str) -> list[str] | None:
    color_src = read_logo_data_uri(brand_id, "color")
    if not color_src:
        return None

    white_src = read_logo_data_uri(brand_id, "white")
    lines = [render_logo_img("logo-light", color_src, alt)]
    if white_src:
        lines.append(render_logo_img("logo-dark", white_src, alt))
    else:
        lines.append(
            render_logo_img(
                "logo-dark",
                color_src,
                alt,
                style="filter:brightness(0) invert(1);",
            )
        )
    return lines


def render_hidden_logo_group() -> str:
    return '<div id="globalLogoGroup" class="logo-group-single" style="display:none;"></div>'


def build_logo_block(
    primary_id: str,
    primary_alt: str,
    secondary_id: str | None = None,
    secondary_alt: str | None = None,
) -> tuple[str, list[str]]:
    warnings: list[str] = []

    primary_pair = render_logo_pair(primary_id, primary_alt)
    if not primary_pair:
        warnings.append(
            f"WARNING: missing required color logo: themes/logos/{primary_id}-color.txt; logo block hidden"
        )
        return render_hidden_logo_group(), warnings

    if secondary_id:
        secondary_pair = render_logo_pair(secondary_id, secondary_alt or secondary_id)
        if secondary_pair:
            lines = ['<div id="globalLogoGroup" class="logo-group-dual">']
            lines.extend(primary_pair)
            lines.append('    <span class="logo-divider"></span>')
            lines.extend(secondary_pair)
            lines.append("</div>")
            return "\n".join(lines), warnings

        warnings.append(
            f"WARNING: missing secondary color logo: themes/logos/{secondary_id}-color.txt; fallback to primary only"
        )

    lines = ['<div id="globalLogoGroup" class="logo-group-single">']
    lines.extend(primary_pair)
    lines.append("</div>")
    return "\n".join(lines), warnings


def main() -> int:
    args = parse_args()
    html_path = Path(args.html_file)
    if not html_path.is_absolute():
        html_path = ROOT_DIR / html_path
    if not html_path.exists():
        print(f"ERROR: file not found: {html_path}", file=sys.stderr)
        return 1

    html_text = html_path.read_text(encoding="utf-8")

    if not TITLE_RE.search(html_text):
        print("ERROR: <title> tag not found", file=sys.stderr)
        return 1
    if not THEME_STYLE_RE.search(html_text):
        print("ERROR: <!-- %%THEME_STYLE%% --> placeholder not found", file=sys.stderr)
        return 1

    final_title = build_title(args.title, args.speaker)
    theme_style = load_theme_style(args.theme_id)
    logo_block, warnings = build_logo_block(
        primary_id=args.primary_id,
        primary_alt=args.primary_alt,
        secondary_id=args.secondary_id,
        secondary_alt=args.secondary_alt,
    )

    updated = TITLE_RE.sub(f"<title>{final_title}</title>", html_text, count=1)
    updated = THEME_STYLE_RE.sub(theme_style, updated, count=1)
    if not LOGO_BLOCK_RE.search(updated):
        print("ERROR: #globalLogoGroup block not found", file=sys.stderr)
        return 1
    updated = LOGO_BLOCK_RE.sub(logo_block, updated, count=1)
    html_path.write_text(updated, encoding="utf-8")

    print(f"Updated branding in {html_path}")
    print(f"title: {final_title}")
    print(f"theme: {args.theme_id}")
    print(f"primary logo: {args.primary_id}")
    if args.secondary_id:
        print(f"secondary logo: {args.secondary_id}")
    for warning in warnings:
        print(warning, file=sys.stderr)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


path: scripts/inline-images.py
content: #!/usr/bin/env python3
"""
inline-images.py — 将 HTML 文件中所有外部图片引用转为 base64 data URI，使文件零依赖。

用法：
    python3 scripts/inline-images.py <html_file>
"""

import re, base64, os, sys, mimetypes


def inline_images(html_file):
    html_dir = os.path.dirname(os.path.abspath(html_file))
    with open(html_file, 'r', encoding='utf-8') as f:
        html = f.read()

    refs = sorted(set(re.findall(r'src="([^"]+)"', html)))
    inlined, failed = 0, []

    for ref in refs:
        if ref.startswith('data:'):
            continue
        full_path = os.path.join(html_dir, ref)
        if not os.path.exists(full_path):
            failed.append(ref)
            continue
        mime = mimetypes.guess_type(full_path)[0] or 'image/png'
        with open(full_path, 'rb') as img:
            b64 = base64.b64encode(img.read()).decode()
        html = html.replace(f'src="{ref}"', f'src="data:{mime};base64,{b64}"')
        inlined += 1

    with open(html_file, 'w', encoding='utf-8') as f:
        f.write(html)

    if failed:
        print(f"⚠️  以下图片未能内嵌（文件不存在）：{failed}")

    remaining = [s for s in re.findall(r'src="([^"]+)"', html) if not s.startswith('data:')]
    if remaining:
        print(f"⚠️  仍有残留外部引用：{remaining}")
    else:
        size_kb = os.path.getsize(html_file) // 1024
        print(f"✅ 内嵌 {inlined} 张图片，文件大小：{size_kb} KB，无外部依赖。")


if __name__ == '__main__':
    if len(sys.argv) != 2:
        print("用法：python3 scripts/inline-images.py <html_file>")
        sys.exit(1)
    inline_images(sys.argv[1])


path: scripts/preview.sh
content: #!/usr/bin/env bash

set -euo pipefail

PORT="${1:-8000}"
ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"

echo "Serving ${ROOT_DIR} at http://localhost:${PORT}"
cd "$ROOT_DIR"
python3 -m http.server "$PORT"


path: scripts/smoke-test.sh
content: #!/usr/bin/env bash

set -euo pipefail

ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
TARGET="${1:-}"

if [[ -z "$TARGET" ]]; then
    echo "Usage: ./scripts/smoke-test.sh <html-file>"
    exit 1
fi

if [[ "$TARGET" != /* ]]; then
    TARGET="${ROOT_DIR}/${TARGET}"
fi

if [[ ! -f "$TARGET" ]]; then
    echo "ERROR: file not found: $TARGET"
    exit 1
fi

cd "$ROOT_DIR"

echo "Checking ${TARGET#$ROOT_DIR/}"

required_ids=(
    'id="progressBar"'
    'id="navDots"'
    'id="fullscreenBtn"'
    'id="footnote"'
)

required_classes=(
    'class="slide slide-cover"'
    'class="slide slide-section"'
    'class="slide slide-qa"'
)

for pattern in "${required_ids[@]}"; do
    if ! grep -q "$pattern" "$TARGET"; then
        echo "ERROR: missing required element: $pattern"
        exit 1
    fi
done

if ! grep -q 'agenda-item' "$TARGET"; then
    echo "ERROR: missing agenda content for directory slide"
    exit 1
fi

for pattern in "${required_classes[@]}"; do
    if ! grep -q "$pattern" "$TARGET"; then
        echo "ERROR: missing required slide type: $pattern"
        exit 1
    fi
done

if grep -Eq 'src="\./logo-[^"]+\.(png|svg)"' "$TARGET"; then
    echo "ERROR: found legacy root-level logo path; use ./themes/logos/... instead"
    exit 1
fi

for asset in $(grep -Eo 'src="\./[^"]+"' "$TARGET" | sed 's/^src="//;s/"$//' | sort -u); do
    if [[ "$asset" == ./themes/logos/* || "$asset" == ./examples/* ]]; then
        if [[ ! -f "${ROOT_DIR}/${asset#./}" ]]; then
            echo "ERROR: missing local asset: $asset"
            exit 1
        fi
    fi
done

# 检查幻灯片总数（至少 4 页：封面 + 目录 + 章节 + 结尾）
slide_count=$(grep -c 'class="slide ' "$TARGET" || true)
if [[ "$slide_count" -lt 4 ]]; then
    echo "ERROR: too few slides ($slide_count), expected at least 4"
    exit 1
fi
echo "  slide count: $slide_count"

# 检查主题 CSS 变量是否存在覆盖（非默认蚂蚁蓝时应有 --primary 覆盖）
if grep -q 'slide-cover' "$TARGET"; then
    if ! grep -q '\-\-primary:' "$TARGET"; then
        echo "WARNING: no --primary CSS variable found; theme override may be missing"
    fi
fi

# 检查是否引用了 Google Fonts（字体加载失败时有 fallback）
if grep -q 'fonts.googleapis.com' "$TARGET"; then
    if ! grep -q "PingFang\|Hiragino\|Microsoft YaHei\|sans-serif" "$TARGET"; then
        echo "WARNING: Google Fonts referenced but no local font fallback found"
    fi
fi

# 检查文件名是否含中文字符
filename=$(basename "$TARGET")
if echo "$filename" | grep -qP '[\x{4e00}-\x{9fff}]' 2>/dev/null || \
   echo "$filename" | python3 -c "import sys; s=sys.stdin.read(); exit(0 if any(ord(c)>127 for c in s) else 1)" 2>/dev/null; then
    echo "WARNING: filename contains non-ASCII characters: $filename"
fi

echo "Smoke test passed"


path: skills/sonoscli/_meta.json
content: ownerId: kn70pywhg0fyz996kpa8xj89s57yhv26
slug: sonoscli
version: 1.0.0
publishedAt: 1767545381030

path: template.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>%%TITLE%%</title>
<style>
/* =============================================
   MINI TEMPLATE
   Primary reusable template for generation.
   Keep key CSS/JS behavior: progress, nav, fullscreen,
   dark-slide logo switching, reveal, keyboard/touch nav.
   ============================================= */

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

:root {
    --primary: #1677FF;
    --primary-dark: #0950D9;
    --primary-light: #4096FF;
    --primary-pale: #E6F4FF;
    --primary-dim: rgba(22, 119, 255, 0.12);
    --cover-bg: linear-gradient(125deg, #0A3DA8 0%, #1263EA 35%, #2B8FFF 65%, #5AB6FF 100%);
    --section-bg: linear-gradient(135deg, #0A3DA8 0%, #1263EA 50%, #2B8FFF 100%);
    --red: #E8380D;
    --green: #52C41A;
    --orange: #FA8C16;
    --text-1: #141414;
    --text-2: #434343;
    --text-3: #8C8C8C;
    --bg-white: #FFFFFF;
    --bg-page: #F5F7FA;
    --border: #E4E8EF;
    --font: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans CJK SC', 'Source Han Sans SC', sans-serif;
    --title-size: clamp(1.5rem, 5vw, 4rem);
    --h2-size: clamp(1.25rem, 3.5vw, 2.5rem);
    --body-size: clamp(0.75rem, 1.5vw, 1.05rem);
    --small-size: clamp(0.65rem, 1vw, 0.875rem);
    --slide-padding: clamp(1rem, 4vw, 4rem);
    --content-gap: clamp(0.5rem, 2vw, 2rem);
    --element-gap: clamp(0.25rem, 1vw, 1rem);
    --deck-base-width: 1600;
    --deck-safe-width: min(100vw, calc(100dvh * 1.6));
    --deck-safe-side-gap: max(0px, calc((100vw - var(--deck-safe-width)) / 2));
    --z-progress: 999;
    --z-fixed-ui: 998;
    --z-meta: 997;
    --z-overlay: 1000;
}

html, body {
    height: 100%;
    overflow-x: hidden;
    font-family: var(--font);
    scrollbar-width: none;
}

html::-webkit-scrollbar,
body::-webkit-scrollbar {
    display: none;
}

html {
    scroll-snap-type: y mandatory;
    scroll-behavior: smooth;
    background: var(--bg-page);
}

#globalLogoGroup .logo-dark  { display: none; }
body.on-blue #globalLogoGroup .logo-light { display: none; }
body.on-blue #globalLogoGroup .logo-dark  { display: block; }
body.on-blue #globalLogoGroup .logo-divider {
    background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.35) 28%, rgba(255,255,255,0.55) 50%, rgba(255,255,255,0.35) 72%, transparent 100%);
}

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

.slide-white {
    background: linear-gradient(180deg, #FAFBFD 0%, #F3F6FB 100%);
    color: var(--text-1);
}

.slide-cover,
.slide-section,
.slide-qa {
    color: #fff;
    background: var(--cover-bg);
}

.slide-section {
    background: var(--section-bg);
    justify-content: center;
    align-items: center;
    text-align: center;
}

.slide-qa {
    justify-content: center;
    align-items: center;
    text-align: center;
}

.slide-content,
.slide-cover,
.slide-section,
.slide-qa {
    padding: var(--slide-padding);
}

.slide-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    max-height: 100%;
    overflow: hidden;
    gap: var(--content-gap);
    width: min(100%, var(--deck-safe-width));
    margin-left: auto;
    margin-right: auto;
    padding: 0 var(--slide-padding) clamp(0.65rem, 1.5vh, 1.2rem);
}

.slide-body {
    flex: 1;
    padding: clamp(0.45rem, 1.15vh, 0.95rem) var(--slide-padding) clamp(0.65rem, 1.5vh, 1.2rem);
    overflow: hidden;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    gap: clamp(0.35rem, 1vh, 0.85rem);
    width: min(100%, var(--deck-safe-width));
    margin-left: auto;
    margin-right: auto;
}

.slide-body.balance {
    justify-content: center;
}

.slide-content > :not(.slide-header) {
    width: 100%;
}

.cover-top,
.slide-header {
    display: flex;
    align-items: center;
}

.cover-top {
    justify-content: space-between;
}

.cover-arcs {
    position: absolute;
    right: calc(-1 * clamp(4rem, 12vw, 14rem));
    top: 50%;
    transform: translateY(-50%);
    width: clamp(28rem, 65vw, 70rem);
    height: clamp(28rem, 65vw, 70rem);
    pointer-events: none;
    opacity: 0.9;
}

.arc {
    position: absolute;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,0.13);
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

.arc-1 { width: 100%; height: 100%; }
.arc-2 { width: 78%; height: 78%; border-color: rgba(255,255,255,0.1); }
.arc-3 { width: 55%; height: 55%; border-color: rgba(255,255,255,0.08); }

.cover-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: clamp(0.8rem, 2vh, 1.4rem);
    max-width: min(72%, 1100px);
}

.cover-title {
    font-size: clamp(2.5rem, 7.5vw, 6.5rem);
    line-height: 1.03;
    font-weight: 900;
    letter-spacing: -0.04em;
}

.cover-subtitle {
    font-size: clamp(0.95rem, 1.7vw, 1.25rem);
    color: rgba(255,255,255,0.82);
    max-width: 48ch;
    line-height: 1.55;
}

.cover-date {
    font-size: clamp(0.8rem, 1vw, 0.95rem);
    color: rgba(255,255,255,0.75);
}

.cover-bottom {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    position: relative;
    z-index: 2;
}

.cover-speaker {
    text-align: right;
}

.cover-speaker-name {
    font-size: clamp(0.85rem, 1.5vw, 1.1rem);
    font-weight: 700;
    color: #fff;
}

.cover-speaker-role {
    margin-top: 2px;
    font-size: clamp(0.65rem, 1vw, 0.8rem);
    color: rgba(255,255,255,0.65);
}

.slide-header {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    gap: clamp(0.2rem, 0.5vh, 0.4rem);
    width: min(100%, var(--deck-safe-width));
    margin-left: auto;
    margin-right: auto;
    margin-bottom: 0;
    padding: clamp(2.2rem, 5vh, 3.4rem) var(--slide-padding) clamp(0.3rem, 0.8vh, 0.6rem);
}

.header-mark {
    display: none;
}

.header-title {
    font-size: clamp(1.48rem, 3vw, 2.45rem);
    font-weight: 900;
    color: var(--text-1);
    text-align: left;
    letter-spacing: -0.02em;
    line-height: 1.1;
}

.header-sub {
    min-height: 1.4em;
    color: var(--primary);
    font-size: clamp(1.02rem, 1.8vw, 1.32rem);
    line-height: 1.32;
}

.slide-subtitle {
    font-size: clamp(0.84rem, 1.55vw, 1.12rem);
    font-weight: 500;
    color: var(--primary);
    line-height: 1.32;
}

.section-num {
    font-size: clamp(0.78rem, 1vw, 0.92rem);
    letter-spacing: 0.18em;
    opacity: 0.72;
}

.section-title {
    font-size: clamp(2rem, 6vw, 4.8rem);
    font-weight: 900;
    line-height: 1.04;
    margin-top: 0.7rem;
}

.section-desc,
.qa-sub {
    margin-top: 0.8rem;
    font-size: clamp(0.85rem, 1.4vw, 1rem);
    color: rgba(255,255,255,0.8);
    max-width: 56ch;
}

.qa-title {
    font-size: clamp(2.4rem, 7vw, 5.6rem);
    font-weight: 900;
    line-height: 1;
}

.grid-2,
.grid-3,
.grid-4 {
    display: grid;
    gap: clamp(0.75rem, 1.5vw, 1rem);
    align-items: stretch;
    margin-left: auto;
    margin-right: auto;
}

.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.card,
.stat-block,
.quote-box,
.step-card,
.agenda-item {
    background: var(--bg-white);
    border: 1px solid var(--border);
    border-radius: 18px;
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.05);
}

.card,
.step-card,
.quote-box,
.agenda-item {
    padding: clamp(0.8rem, 1.6vw, 1.2rem);
}

.card-title,
.step-title {
    font-size: clamp(0.95rem, 1.2vw, 1.05rem);
    font-weight: 800;
}

.card-body,
.step-desc,
.mini-list li {
    font-size: var(--body-size);
    line-height: 1.6;
    color: var(--text-2);
}

.quote-box {
    background: linear-gradient(180deg, #F9FCFF 0%, #EDF5FF 100%);
}

.quote-box strong {
    color: var(--primary-dark);
}

.stat-block {
    padding: 1rem;
    text-align: center;
}

.stat-num,
.stat-number {
    font-size: clamp(2.2rem, 4.8vw, 4.2rem);
    font-weight: 900;
    color: var(--primary);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.stat-label {
    margin-top: 0.55rem;
    color: var(--text-2);
    font-size: clamp(0.78rem, 1vw, 0.92rem);
}

.stat-icon {
    width: 36px;
    height: 36px;
    margin: 0 auto 0.5rem;
    color: var(--primary);
    display: block;
    opacity: 0.3;
}
.stat-icon svg { width: 100%; height: 100%; display: block; }
.stat-num.red ~ .stat-label,
.stat-number.red ~ .stat-label,
.stat-block.red .stat-icon,
.stat-block.red .stat-label { color: var(--red); }

.mini-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
    margin-top: 0.6rem;
}

.mini-list li {
    position: relative;
    padding-left: 1rem;
}

.mini-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.58em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--primary);
}

.agenda-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.7rem;
    width: min(900px, 100%);
    margin: 0 auto;
}

.agenda-item {
    display: grid;
    grid-template-columns: 56px 1fr 110px;
    align-items: center;
    gap: 0.8rem;
}

.agenda-num {
    font-size: 1.4rem;
    font-weight: 900;
    color: var(--primary);
}

.agenda-text {
    font-size: 1rem;
    font-weight: 700;
}

.agenda-tag {
    justify-self: end;
    border-radius: 999px;
    padding: 0.32rem 0.7rem;
    font-size: 0.8rem;
    font-weight: 700;
    background: var(--primary-pale);
    color: var(--primary-dark);
}

.two-col,
.three-col {
    display: grid;
    gap: clamp(0.8rem, 1.6vw, 1.1rem);
    align-items: stretch;
    margin-left: auto;
    margin-right: auto;
}

.two-col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.three-col { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.grid-2,
.two-col {
    max-width: min(1240px, 100%);
}

.grid-3,
.three-col {
    max-width: min(1180px, 100%);
}

.grid-4 {
    max-width: min(1240px, 100%);
}

.goal-card,
.info-card,
.support-card,
.role-card,
.highlight-box,
.level-row {
    border-radius: 18px;
    border: 1px solid var(--border);
    background: var(--bg-white);
    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.05);
}

.goal-card,
.info-card,
.support-card,
.role-card,
.step-card,
.card,
.quote-box {
    height: 100%;
}

.goal-card {
    padding: clamp(0.9rem, 1.8vw, 1.3rem);
    background: linear-gradient(180deg, #FFFFFF 0%, #F6FAFF 100%);
}

.goal-icon {
    width: 38px;
    height: 38px;
    margin-bottom: 0.6rem;
    color: var(--primary);
    display: block;
    opacity: 0.3;
}
.goal-icon svg { width: 100%; height: 100%; display: block; }

.goal-num-big {
    font-size: clamp(2.1rem, 4.5vw, 3.8rem);
    line-height: 1;
    font-weight: 900;
    color: var(--primary);
}

.goal-num-big .unit {
    font-size: 0.45em;
    margin-left: 0.1em;
}

.goal-label {
    margin-top: 0.7rem;
    font-size: clamp(0.95rem, 1.3vw, 1.08rem);
    font-weight: 800;
    color: var(--text-1);
}

.goal-sublabel {
    margin-top: 0.35rem;
    font-size: var(--small-size);
    color: var(--text-3);
}

.info-card {
    position: relative;
    padding: 1rem 1rem 1rem 1.1rem;
    background: linear-gradient(180deg, #FFFFFF 0%, #FAFBFF 100%);
}

.info-card::before {
    content: "";
    position: absolute;
    left: 0;
    top: 14px;
    bottom: 14px;
    width: 5px;
    border-radius: 999px;
    background: var(--primary);
}

.info-card.red::before { background: var(--red); }
.info-card.green::before { background: var(--green); }
.info-card.orange::before { background: var(--orange); }

.card-icon {
    width: 38px;
    height: 38px;
    margin-bottom: 0.5rem;
    color: var(--primary);
    display: block;
    opacity: 0.3;
}
.card-icon svg { width: 100%; height: 100%; display: block; }
.info-card.red    .card-icon { color: var(--red); }
.info-card.green  .card-icon { color: var(--green); }
.info-card.orange .card-icon { color: var(--orange); }

.step-icon {
    width: 34px;
    height: 34px;
    margin-bottom: 0.4rem;
    color: var(--primary);
    display: block;
    opacity: 0.3;
}
.step-icon svg { width: 100%; height: 100%; display: block; }

.step-card {
    min-height: 150px;
}

.step-card-1 { background: linear-gradient(180deg, #F8FBFF 0%, #EBF4FF 100%); }
.step-card-2 { background: linear-gradient(180deg, #F7F8FF 0%, #EEF0FF 100%); }
.step-card-3 { background: linear-gradient(180deg, #FBF7FF 0%, #F3ECFF 100%); }
.step-card-4 { background: linear-gradient(180deg, #FFF8F1 0%, #FFF0DE 100%); }

.step-num {
    font-size: 0.76rem;
    font-weight: 900;
    letter-spacing: 0.08em;
    color: var(--primary);
    margin-bottom: 0.5rem;
}

.support-card,
.role-card {
    padding: 1rem;
}

.support-icon,
.role-icon {
    width: 42px;
    height: 42px;
    display: block;
    color: var(--primary);
    opacity: 0.3;
}
.support-icon svg,
.role-icon svg { width: 100%; height: 100%; display: block; }

.support-title,
.role-name {
    margin-top: 0.7rem;
    font-size: 1rem;
    font-weight: 800;
    color: var(--text-1);
}

.support-items {
    list-style: none;
    margin-top: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.45rem;
}

.support-items li,
.role-arrow,
.role-evolution {
    color: var(--text-2);
    font-size: var(--body-size);
    line-height: 1.55;
}

.role-arrow {
    margin-top: 0.55rem;
    font-weight: 700;
    color: var(--primary-dark);
}

.role-evolution {
    margin-top: 0.45rem;
}

.highlight-box {
    padding: 0.95rem 1rem 0.95rem 1.1rem;
    border-left: 5px solid var(--primary);
    background: linear-gradient(180deg, #F9FCFF 0%, #EDF5FF 100%);
    color: var(--text-2);
    font-size: var(--body-size);
    line-height: 1.7;
}

.highlight-box strong { color: var(--primary-dark); }

.highlight-box.red-box {
    border-left-color: var(--red);
    background: linear-gradient(180deg, #FFF8F7 0%, #FFF1F0 100%);
}

.tag {
    display: inline-flex;
    align-items: center;
    padding: 0.35rem 0.7rem;
    border-radius: 999px;
    font-size: var(--small-size);
    font-weight: 700;
    line-height: 1;
}

.tag-blue { background: #E6F4FF; color: #1677FF; }
.tag-red { background: #FFF1F0; color: #E8380D; }
.tag-green { background: #F6FFED; color: #52C41A; }
.tag-dark { background: #1F1F1F; color: #fff; }

.styled-list {
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
}

.styled-list li {
    position: relative;
    padding-left: 1rem;
    color: var(--text-2);
    font-size: var(--body-size);
    line-height: 1.65;
}

.styled-list li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.58em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--primary);
}

.insight {
    display: flex;
    align-items: flex-start;
    gap: 0.7rem;
    padding: 0.9rem 1rem;
    border-radius: 16px;
    background: rgba(22, 119, 255, 0.06);
}

.insight-dot {
    width: 10px;
    height: 10px;
    margin-top: 0.45rem;
    border-radius: 50%;
    background: var(--primary);
    flex-shrink: 0;
}

.level-row {
    display: grid;
    grid-template-columns: 60px minmax(0, 1fr) 22px;
    align-items: center;
    gap: 0.8rem;
    padding: 0.8rem 0.95rem;
}

.level-row.active {
    border-color: rgba(22, 119, 255, 0.22);
    background: linear-gradient(180deg, #F7FBFF 0%, #EEF5FF 100%);
}

.level-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 34px;
    border-radius: 999px;
    background: var(--primary-pale);
    color: var(--primary-dark);
    font-weight: 900;
    font-size: 0.85rem;
}

.level-title {
    font-size: 0.96rem;
    font-weight: 800;
    color: var(--text-1);
}

.level-desc {
    margin-top: 0.2rem;
    font-size: var(--small-size);
    color: var(--text-2);
    line-height: 1.5;
}

.level-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #D9D9D9;
}

.level-row.active .level-dot {
    background: var(--primary);
    box-shadow: 0 0 0 5px rgba(22, 119, 255, 0.12);
}

.step-flow-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 36px minmax(0, 1fr) 36px minmax(0, 1fr) 36px minmax(0, 1fr);
    gap: 0.45rem;
    align-items: center;
}

.step-flow-arrow {
    text-align: center;
    font-size: 1.4rem;
    font-weight: 900;
    color: var(--primary);
}

.step-flow-top {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: clamp(0.45rem, 1vw, 0.9rem);
    align-items: stretch;
}

.step-flow-bottom {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(0.65rem, 1.4vw, 1rem);
    align-items: stretch;
}

.step-flow-bridge {
    display: flex;
    justify-content: center;
    margin: clamp(0.45rem, 1vh, 0.75rem) 0;
}

.step-flow-bridge span {
    font-size: clamp(0.7rem, 1.1vw, 0.84rem);
    font-weight: 700;
    color: var(--text-3);
    letter-spacing: 0.04em;
    padding: 0.38em 0.9em;
    border-radius: 999px;
    background: rgba(22,119,255,0.06);
    border: 1px solid rgba(22,119,255,0.08);
}

.support-board {
    width: 100%;
    background: linear-gradient(180deg, #F9FBFF 0%, #F4F8FE 100%);
    border: 1.5px solid rgba(22,119,255,0.16);
    border-radius: 26px;
    padding: clamp(1rem, 2.2vw, 1.5rem);
}

.support-board-title {
    font-size: clamp(0.78rem, 1.25vw, 0.96rem);
    font-weight: 800;
    color: var(--text-2);
    letter-spacing: 0.02em;
}

.support-board-main {
    font-size: clamp(1.15rem, 2.2vw, 1.9rem);
    font-weight: 900;
    color: var(--text-1);
    margin-top: 0.15rem;
}

.support-board-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(0.7rem, 1.4vw, 1rem);
    margin-top: clamp(0.8rem, 1.8vh, 1.1rem);
}

.support-board-col {
    background: rgba(255,255,255,0.5);
    border-radius: 18px;
    padding: clamp(0.8rem, 1.6vw, 1.1rem);
}

.support-board-col-title {
    font-size: clamp(0.92rem, 1.55vw, 1.2rem);
    font-weight: 800;
    color: var(--text-1);
    margin-bottom: clamp(0.55rem, 1vh, 0.8rem);
}

.support-board-items {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: clamp(0.55rem, 1vw, 0.8rem);
}

.support-mini-card {
    background: rgba(255,255,255,0.92);
    border: 1px solid rgba(0,0,0,0.06);
    border-radius: 16px;
    padding: clamp(0.62rem, 1.1vw, 0.82rem);
    box-shadow: 0 4px 12px rgba(0,0,0,0.03);
    min-height: 88px;
}

.support-mini-card-title {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    font-size: clamp(0.8rem, 1.3vw, 1rem);
    font-weight: 800;
    color: var(--text-1);
    line-height: 1.35;
}

.support-owner {
    display: inline-flex;
    align-items: center;
    padding: 0.18em 0.55em;
    border-radius: 999px;
    background: #F3F4F6;
    color: #8C8C8C;
    font-size: clamp(0.55rem, 0.85vw, 0.68rem);
    font-weight: 700;
}

.support-mini-card-body {
    margin-top: 0.35rem;
    font-size: clamp(0.68rem, 1.02vw, 0.82rem);
    color: var(--text-3);
    line-height: 1.6;
}

.demand-flow {
    width: 100%;
    display: flex;
    flex-direction: column;
    gap: clamp(0.4rem, 0.9vh, 0.7rem);
}

.demand-flow-board {
    position: relative;
    width: 100%;
    min-height: min(33vh, 250px);
    border-radius: 24px;
    border: 1px solid rgba(22,119,255,0.1);
    background: #fff;
    overflow: hidden;
}

.demand-flow-stage-head {
    position: absolute;
    left: 0;
    right: 0;
    top: clamp(0.35rem, 0.8vh, 0.5rem);
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    padding: 0 clamp(1rem, 2vw, 1.4rem);
    z-index: 3;
    pointer-events: none;
}

.demand-flow-stage-head span {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.28rem;
    text-align: center;
    font-size: clamp(0.86rem, 1.4vw, 1.08rem);
    font-weight: 800;
    color: rgba(0,0,0,0.58);
}

.demand-flow-stage-role {
    font-size: clamp(0.52rem, 0.9vw, 0.68rem);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: rgba(0,0,0,0.38);
    white-space: nowrap;
}

.demand-flow-stage-grid {
    position: absolute;
    inset: clamp(2.5rem, 5vh, 3.4rem) 0 auto 0;
    height: clamp(3rem, 6vh, 4.2rem);
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    pointer-events: none;
    z-index: 1;
}

.demand-flow-stage-grid span {
    position: relative;
}

.demand-flow-stage-grid span::after {
    content: '';
    position: absolute;
    left: 50%;
    top: 0;
    transform: translateX(-50%);
    height: 100%;
    border-left: 2px dashed rgba(0,0,0,0.12);
}

.demand-flow-lines {
    position: absolute;
    inset: clamp(4.4rem, 9vh, 5.6rem) clamp(0.75rem, 1.2vw, 1rem) clamp(0.9rem, 1.5vw, 1rem);
    pointer-events: none;
    z-index: 1;
}

.demand-flow-track {
    position: absolute;
    inset: clamp(4.4rem, 9vh, 5.6rem) clamp(0.75rem, 1.2vw, 1rem) clamp(0.9rem, 1.5vw, 1rem);
    overflow: hidden;
    z-index: 2;
}

.demand-dot {
    position: absolute;
    border-radius: 50%;
}

.slide-white::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1440 700'><defs><pattern id='dp' x='0' y='0' width='11' height='11' patternUnits='userSpaceOnUse'><circle cx='5.5' cy='5.5' r='1.6' fill='%23C8C8C8'/></pattern></defs><path d='M160,50 L190,38 L240,36 L285,42 L318,58 L342,84 L355,115 L348,150 L326,174 L304,190 L275,202 L250,218 L228,248 L208,260 L190,252 L170,236 L150,210 L142,178 L140,145 L145,105 L150,72 Z' fill='url(%23dp)'/><path d='M218,272 L274,266 L300,288 L312,326 L306,372 L288,418 L264,448 L238,456 L212,438 L198,402 L194,358 L200,308 L208,280 Z' fill='url(%23dp)'/><path d='M638,52 L684,44 L726,50 L748,72 L742,98 L720,116 L688,122 L658,112 L638,90 L635,68 Z' fill='url(%23dp)'/><path d='M638,148 L694,140 L736,148 L760,178 L764,224 L752,280 L730,336 L700,378 L668,398 L642,388 L622,356 L612,304 L612,244 L620,192 L630,164 Z' fill='url(%23dp)'/><path d='M754,40 L860,32 L974,40 L1070,56 L1140,82 L1188,114 L1204,150 L1188,180 L1152,200 L1102,214 L1040,220 L978,230 L918,238 L870,244 L828,258 L790,250 L756,232 L734,206 L730,170 L736,132 L744,86 Z' fill='url(%23dp)'/><path d='M1082,372 L1164,356 L1228,368 L1258,398 L1252,438 L1216,462 L1158,468 L1098,452 L1068,422 L1066,392 Z' fill='url(%23dp)'/></svg>") center 42% / 82% auto no-repeat;
    opacity: 0.22;
}

.slide-white::after {
    content: '';
    position: absolute;
    bottom: 0;
    right: 0;
    width: 48%;
    height: 30%;
    z-index: 0;
    pointer-events: none;
    background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 720 300'><path d='M720,0 C600,60 460,200 60,300' fill='none' stroke='%231677FF' stroke-width='1.5' opacity='0.35'/><path d='M720,80 C650,140 540,240 200,300' fill='none' stroke='%231677FF' stroke-width='1.5' opacity='0.28'/></svg>") bottom right / 100% 100% no-repeat;
}

.slide-white .slide-header,
.slide-white .slide-body {
    position: relative;
    z-index: 1;
}

.reveal-left {
    opacity: 0;
    transform: translateX(-20px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.slide.visible .reveal-left {
    opacity: 1;
    transform: translateX(0);
}

/* --- fixed UI primitives --- */
.progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    height: 3px;
    width: 0;
    background: var(--primary);
    z-index: var(--z-progress);
    transition: width 0.25s ease;
}

.nav-dots {
    position: fixed;
    right: 12px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    flex-direction: column;
    gap: 8px;
    z-index: var(--z-fixed-ui);
}

.nav-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    border: none;
    background: rgba(0,0,0,0.18);
    cursor: pointer;
    transition: transform 0.2s ease, background 0.3s ease;
}

.nav-dot.active {
    background: var(--primary);
    transform: scale(1.2);
}

body.on-blue .nav-dot {
    background: rgba(255,255,255,0.35);
}

body.on-blue .nav-dot.active {
    background: rgba(255,255,255,0.9);
}

#globalLogoGroup {
    position: fixed;
    top: clamp(1.15rem, 2.8vh, 1.9rem);
    right: calc(var(--deck-safe-side-gap) + clamp(0.9rem, 2vw, 1.6rem));
    z-index: var(--z-fixed-ui);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(0.5rem, 1vw, 0.9rem);
    width: clamp(8.5rem, 15vw, 11.5rem);
    pointer-events: none;
    transition: opacity 0.4s ease;
    opacity: 1;
}

#globalLogoGroup img {
    height: clamp(1.4rem, 2.8vw, 2.2rem);
    max-height: 36px;
    max-width: clamp(5.5rem, 11vw, 9rem);
    width: auto;
    object-fit: contain;
    display: block;
}

#globalLogoGroup .logo-divider {
    width: 1.5px;
    height: clamp(1.4rem, 2.8vw, 2.2rem);
    flex-shrink: 0;
    background: linear-gradient(to bottom, transparent 0%, rgba(0,0,0,0.18) 28%, rgba(0,0,0,0.32) 50%, rgba(0,0,0,0.18) 72%, transparent 100%);
}

#globalLogoGroup.logo-group-single img {
    height: clamp(2.1rem, 3.8vw, 3rem);
    max-height: 52px;
    max-width: clamp(8rem, 16vw, 13rem);
}

#fullscreenBtn {
    position: fixed;
    right: 10px;
    bottom: clamp(0.6rem, 1.5vh, 1rem);
    z-index: var(--z-progress);
    width: 2rem;
    height: 2rem;
    border: none;
    background: rgba(0,0,0,0.12);
    border-radius: 6px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0.5;
    transition: background 0.2s, opacity 0.2s;
}
#fullscreenBtn svg { color: #333; transition: color 0.3s; }
body.on-blue #fullscreenBtn { background: rgba(255,255,255,0.18); }
body.on-blue #fullscreenBtn svg { color: #fff; }

#footnote {
    position: fixed;
    bottom: clamp(0.6rem, 1.5vh, 1rem);
    left: clamp(1rem, 3vw, 2.5rem);
    font-size: clamp(0.5rem, 0.75vw, 0.65rem);
    color: #ABABAB;
    z-index: var(--z-meta);
    pointer-events: none;
    transition: opacity 0.4s ease;
}

#powered-by {
    position: fixed;
    bottom: clamp(0.6rem, 1.5vh, 1rem);
    right: calc(10px + 2rem + 0.5rem);
    font-size: clamp(0.5rem, 0.75vw, 0.65rem);
    color: #ABABAB;
    z-index: var(--z-meta);
    pointer-events: auto;
    transition: opacity 0.4s ease;
    white-space: nowrap;
}

/* --- reveal animation --- */
.reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.slide.visible .reveal {
    opacity: 1;
    transform: translateY(0);
}

.slide.visible .reveal:nth-child(2) { transition-delay: 0.08s; }
.slide.visible .reveal:nth-child(3) { transition-delay: 0.16s; }
.slide.visible .reveal:nth-child(4) { transition-delay: 0.24s; }

@media (max-width: 900px) {
    .grid-2, .grid-3, .grid-4, .two-col, .three-col, .step-flow-grid, .step-flow-top, .step-flow-bottom, .support-board-grid, .support-board-items { grid-template-columns: 1fr; }
    .cover-main { max-width: 100%; }
    .slide-header { padding-right: 0; }
    .header-sub { display: none; }
    .agenda-item { grid-template-columns: 48px 1fr; }
    .agenda-tag { justify-self: start; }
    .nav-dots { display: none; }
    #globalLogoGroup { display: none; }
    .step-flow-arrow { display: none; }
    .cover-bottom { flex-direction: column; align-items: flex-start; gap: 0.8rem; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        transition-duration: 0.2s !important;
    }
    html { scroll-behavior: auto; }
}

#pageCounter {
    position: fixed;
    bottom: clamp(0.6rem, 1.5vh, 1rem);
    left: 50%;
    transform: translateX(-50%);
    font-size: clamp(0.5rem, 0.75vw, 0.65rem);
    color: #ABABAB;
    font-family: var(--font);
    z-index: var(--z-meta);
    pointer-events: none;
    transition: opacity 0.4s ease, color 0.4s ease;
    letter-spacing: 0.06em;
    white-space: nowrap;
}

#wheelHint {
    position: fixed;
    bottom: clamp(2rem, 4vh, 3rem);
    left: 50%;
    transform: translateX(-50%) translateY(8px);
    background: rgba(20, 20, 20, 0.82);
    color: #fff;
    font-family: var(--font);
    font-size: clamp(0.7rem, 1.1vw, 0.88rem);
    padding: 0.55em 1.1em;
    border-radius: 999px;
    display: flex;
    align-items: center;
    gap: 0.5em;
    white-space: nowrap;
    pointer-events: none;
    z-index: var(--z-overlay);
    opacity: 0;
    transition: opacity 0.25s ease, transform 0.25s ease;
}

#wheelHint.visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}

#wheelHint kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: rgba(255,255,255,0.15);
    border: 1px solid rgba(255,255,255,0.25);
    border-radius: 5px;
    padding: 0.1em 0.45em;
    font-family: inherit;
    font-size: 0.95em;
    line-height: 1.6;
}
</style>
<!-- %%THEME_STYLE%% -->
<style>
/* ── Large screens (4K+): lift fixed px caps ── */
@media (min-width: 1921px) {
    :root {
        --title-size:    clamp(1.5rem, 5vw, 6rem);
        --h2-size:       clamp(1.25rem, 3.5vw, 4rem);
        --h3-size:       clamp(1rem, 2.5vw, 3rem);
        --body-size:     clamp(0.75rem, 1.5vw, 2rem);
        --small-size:    clamp(0.65rem, 1vw, 1.5rem);
        --slide-padding: clamp(1rem, 4vw, 6rem);
        --content-gap:   clamp(0.5rem, 2vw, 3rem);
        --element-gap:   clamp(0.25rem, 1vw, 1.5rem);
    }
    .card, .container, .content-box {
        max-width: min(90vw, calc(1000px * var(--vp-scale, 1)));
        max-height: min(80vh, calc(700px * var(--vp-scale, 1)));
    }
    .slide img { width: 100%; max-height: 50vh; }
    .info-card { min-height: calc(88px * var(--vp-scale, 1)); }
    .nav-dots  { gap: calc(8px * var(--vp-scale, 1)); }
    .nav-dot   { width: calc(8px * var(--vp-scale, 1)); height: calc(8px * var(--vp-scale, 1)); }
    .progress-bar { height: calc(3px * var(--vp-scale, 1)); }
    #globalLogoGroup img { max-height: none !important; width: auto !important; }
    #footnote { font-size: 0.65rem !important; }
}
</style>
</head>
<body>

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

<div id="globalLogoGroup" class="logo-group-single" style="display:none;"></div>


<nav class="nav-dots" id="navDots" aria-label="幻灯片导航"></nav>

<button id="fullscreenBtn" title="全屏播放 (F)">
    <svg id="fsIconExpand" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"></path>
    </svg>
    <svg id="fsIconCompress" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:none;">
        <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"></path>
    </svg>
</button>

<div id="footnote">* 仅限内部交流使用，如需公开请联系作者。</div>
<div id="powered-by">Powered by <a href="https://github.com/FeeiCN/slide-writer" style="color:inherit;text-decoration:none;" target="_blank">Slide-Writer</a></div>
<div id="pageCounter" style="opacity:0;">1 / 1</div>
<div id="wheelHint"><kbd>↑</kbd><kbd>↓</kbd><kbd>←</kbd><kbd>→</kbd><kbd>Space</kbd> 翻页 &nbsp;·&nbsp; <kbd>F</kbd> 全屏</div>

<!-- %%SLIDES%% -->


<script>
function updateViewportScale() {
    const scale = Math.max(1, window.innerWidth / 1600);
    document.documentElement.style.fontSize = (scale * 16) + 'px';
    document.documentElement.style.setProperty('--vp-scale', scale);
}

function markStatBlocks() {
    document.querySelectorAll('.stat-block').forEach((block) => {
        const hasRedMetric = block.querySelector('.stat-num.red, .stat-number.red');
        block.classList.toggle('red', !!hasRedMetric);
    });
}

function tuneLogoImage(img, groupSize) {
    if (!img) return;

    const apply = () => {
        if (!img.naturalWidth || !img.naturalHeight) return;

        img.style.height = '';
        img.style.maxWidth = '';

        const ratio = img.naturalWidth / img.naturalHeight;
        const baseHeight = parseFloat(getComputedStyle(img).height) || 32;
        let scale = 1;
        let maxWidth = 160;

        if (ratio >= 5) {
            scale = 0.74;
            maxWidth = 132;
        } else if (ratio >= 4) {
            scale = 0.82;
            maxWidth = 144;
        } else if (ratio >= 3) {
            scale = 0.9;
            maxWidth = 156;
        } else if (ratio <= 1.4) {
            scale = 1.08;
            maxWidth = 72;
        } else if (ratio <= 1.8) {
            scale = 1.04;
            maxWidth = 92;
        }

        if (groupSize === 1) {
            scale *= ratio >= 3 ? 1.08 : 1.16;
            maxWidth += ratio <= 2.2 ? 18 : 8;
        } else {
            scale *= 0.96;
        }

        img.style.height = `${Math.round(baseHeight * scale)}px`;
        img.style.maxWidth = `${maxWidth}px`;
    };

    if (img.complete) apply();
    else img.addEventListener('load', apply, { once: true });
}

function tuneLogoGroups() {
    document.querySelectorAll('#globalLogoGroup').forEach((group) => {
        const allImgs = Array.from(group.querySelectorAll('img'));
        if (!allImgs.length) return;

        const slotCount = group.classList.contains('logo-group-dual') ? 2
                        : group.classList.contains('logo-group-single') ? 1
                        : allImgs.length;
        allImgs.forEach(img => tuneLogoImage(img, slotCount));

        requestAnimationFrame(() => {
            const heights = allImgs
                .filter(img => getComputedStyle(img).display !== 'none')
                .map(img => img.getBoundingClientRect().height)
                .filter(Boolean);
            const divider = group.querySelector('.logo-divider');
            if (!divider || !heights.length) return;
            divider.style.height = `${Math.round(Math.max(...heights) * 0.88)}px`;
        });
    });
}

class MiniSlidePresentation {
    constructor() {
        this.slides = Array.from(document.querySelectorAll('.slide'));
        this.totalSlides = this.slides.length;
        this.currentIndex = 0;
        this.lastHash = '';
        this.wheelLocked = false;

        this.progressBar = document.getElementById('progressBar');
        this.navDotsContainer = document.getElementById('navDots');
        this.fsBtn = document.getElementById('fullscreenBtn');
        this.fsExpand = document.getElementById('fsIconExpand');
        this.fsCompress = document.getElementById('fsIconCompress');

        this.setupNavDots();
        this.setupIntersectionObserver();
        this.setupKeyboardNav();
        this.setupTouchNav();
        this.setupWheelNav();
        this.setupFullscreen();
        this.updateProgress(0);
        this.restoreFromHash();
    }

    setupNavDots() {
        this.slides.forEach((_, i) => {
            const btn = document.createElement('button');
            btn.className = 'nav-dot' + (i === 0 ? ' active' : '');
            btn.setAttribute('aria-label', `跳到第 ${i + 1} 张`);
            btn.addEventListener('click', () => this.goTo(i));
            this.navDotsContainer.appendChild(btn);
        });
    }

    updateNavDots(index) {
        const dots = this.navDotsContainer.querySelectorAll('.nav-dot');
        dots.forEach((d, i) => d.classList.toggle('active', i === index));
    }

    setupIntersectionObserver() {
        const observer = new IntersectionObserver((entries) => {
            entries.forEach(entry => {
                if (!entry.isIntersecting) return;
                entry.target.classList.add('visible');
                const idx = this.slides.indexOf(entry.target);
                if (idx === -1) return;

                this.currentIndex = idx;
                this.updateProgress(idx);
                this.updateNavDots(idx);
                this.updateHash(idx);

                const fn = document.getElementById('footnote');
                if (fn) fn.style.opacity = idx === 0 ? '0' : '1';
                const pb = document.getElementById('powered-by');
                if (pb) pb.style.opacity = idx === 0 ? '0' : '1';

                const isBlueSide = entry.target.classList.contains('slide-cover') ||
                    entry.target.classList.contains('slide-section') ||
                    entry.target.classList.contains('slide-qa');
                document.body.classList.toggle('on-blue', isBlueSide);
            });
        }, { threshold: 0.55 });

        this.slides.forEach(slide => observer.observe(slide));
    }

    updateProgress(index) {
        const pct = this.totalSlides > 1 ? (index / (this.totalSlides - 1)) * 100 : 100;
        this.progressBar.style.width = pct + '%';
        const isBlue = this.slides[index] &&
            (this.slides[index].classList.contains('slide-cover') ||
             this.slides[index].classList.contains('slide-section') ||
             this.slides[index].classList.contains('slide-qa'));
        this.progressBar.style.background = isBlue ? 'rgba(255,255,255,0.7)' : 'var(--primary)';
        const counter = document.getElementById('pageCounter');
        if (counter) {
            counter.textContent = `${index + 1} / ${this.totalSlides}`;
            counter.style.opacity = index === 0 ? '0' : '1';
            counter.style.color = isBlue ? 'rgba(255,255,255,0.55)' : '#ABABAB';
        }
    }

    updateHash(index) {
        const slide = this.slides[index];
        if (!slide || !slide.id || this.lastHash === slide.id) return;
        this.lastHash = slide.id;
        history.replaceState(null, '', `#${slide.id}`);
    }

    restoreFromHash() {
        const hash = window.location.hash.replace('#', '');
        if (!hash) return;
        const index = this.slides.findIndex(slide => slide.id === hash);
        if (index === -1) return;
        this.currentIndex = index;
        this.updateProgress(index);
        this.updateNavDots(index);
        this.lastHash = hash;
        requestAnimationFrame(() => {
            this.slides[index].scrollIntoView({ behavior: 'auto', block: 'start' });
        });
    }

    setupKeyboardNav() {
        document.addEventListener('keydown', (e) => {
            if (e.key === 'ArrowDown' || e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') {
                e.preventDefault();
                this.goTo(this.currentIndex + 1);
            } else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft' || e.key === 'PageUp') {
                e.preventDefault();
                this.goTo(this.currentIndex - 1);
            } else if (e.key === 'Home') {
                e.preventDefault();
                this.goTo(0);
            } else if (e.key === 'End') {
                e.preventDefault();
                this.goTo(this.totalSlides - 1);
            } else if (e.key === 'f' || e.key === 'F') {
                this.fsBtn.click();
            }
        });
    }

    setupTouchNav() {
        let startY = 0;
        let startX = 0;
        document.addEventListener('touchstart', (e) => {
            startY = e.touches[0].clientY;
            startX = e.touches[0].clientX;
        }, { passive: true });
        document.addEventListener('touchend', (e) => {
            const dy = startY - e.changedTouches[0].clientY;
            const dx = startX - e.changedTouches[0].clientX;
            if (Math.abs(dy) > Math.abs(dx) && Math.abs(dy) > 40) {
                this.goTo(this.currentIndex + (dy > 0 ? 1 : -1));
            }
        }, { passive: true });
    }

    setupWheelNav() {
        let hideTimer = null;
        const hint = document.getElementById('wheelHint');
        document.addEventListener('wheel', (e) => {
            hint.classList.add('visible');
            clearTimeout(hideTimer);
            hideTimer = setTimeout(() => hint.classList.remove('visible'), 2000);
            if (Math.abs(e.deltaY) < 24 || Math.abs(e.deltaY) < Math.abs(e.deltaX)) return;
            e.preventDefault();
            if (this.wheelLocked) return;
            this.wheelLocked = true;
            this.goTo(this.currentIndex + (e.deltaY > 0 ? 1 : -1));
            window.setTimeout(() => { this.wheelLocked = false; }, 650);
        }, { passive: false });
    }

    goTo(index) {
        index = Math.max(0, Math.min(this.totalSlides - 1, index));
        if (index === this.currentIndex) return;
        this.currentIndex = index;
        this.slides[index].scrollIntoView({ behavior: 'smooth' });
    }

    setupFullscreen() {
        const updateFsIcon = () => {
            const isFs = !!document.fullscreenElement;
            this.fsExpand.style.display = isFs ? 'none' : 'block';
            this.fsCompress.style.display = isFs ? 'block' : 'none';
            this.fsBtn.style.background = isFs ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.12)';
        };

        this.fsBtn.addEventListener('mouseenter', () => { this.fsBtn.style.opacity = '1'; });
        this.fsBtn.addEventListener('mouseleave', () => { this.fsBtn.style.opacity = '0.5'; });
        this.fsBtn.addEventListener('click', () => {
            if (!document.fullscreenElement) document.documentElement.requestFullscreen();
            else document.exitFullscreen();
        });

        document.addEventListener('fullscreenchange', updateFsIcon);
    }
}

document.addEventListener('DOMContentLoaded', () => {
    updateViewportScale();
    window.addEventListener('resize', updateViewportScale);
    markStatBlocks();
    tuneLogoGroups();
    new MiniSlidePresentation();
    window.addEventListener('resize', tuneLogoGroups);
});
</script>
</body>
</html>


path: themes/alibaba.md
content: # 阿里巴巴主题 `alibaba`

品牌橙，来自阿里巴巴集团标志色。

## Logo
- 白色页（内容页）：`./themes/logos/alibaba-color.png`（橙色彩色版）
- 深色页（封面/章节/结尾）：无白色版，使用彩色版 + `filter:brightness(0) invert(1)` 转白

## CSS

```css
:root {
    --primary:       #FF6A00;
    --primary-dark:  #CC4400;
    --primary-light: #FF8C38;
    --primary-pale:  #FFF3E8;
    --primary-dim:   rgba(255, 106, 0, 0.12);
    --cover-bg:      linear-gradient(125deg, #7A2000 0%, #CC3800 35%, #FF6A00 65%, #FF9A50 100%);
    --section-bg:    linear-gradient(135deg, #7A2000 0%, #CC4400 50%, #FF6A00 100%);
    --red:           #CF1322;
    --green:         #389E0D;
    --orange:        #FA8C16;
}
.slide-section { background: linear-gradient(135deg, #7A2000 0%, #CC4400 50%, #FF6A00 100%) !important; }
.slide-qa      { background: linear-gradient(125deg, #7A2000 0%, #CC3800 35%, #FF6A00 65%, #FF9A50 100%) !important; }
.header-sub    { color: var(--primary) !important; }
.agenda-num    { color: var(--primary) !important; }
```


path: themes/ant-group.md
content: # 蚂蚁集团主题 `ant-group`（默认）

品牌蓝，来自 Ant Design 设计系统。

## Logo

Logo 显示规则：
- 检测到**子品牌**（支付宝、花呗等）→ 双 Logo：集团 ＋ 子品牌
- **其他所有情况（默认）** → 双 Logo：蚂蚁集团 ＋ 支付宝
- 例外：内容明确为**集团层面**（投资者关系、集团战略、ESG报告等）→ 单 Logo：仅集团

双 Logo 路径：
- 深色页：`./themes/logos/ant-group-white.png` ＋ 分隔线 ＋ `./themes/logos/alipay-white.png`
- 白色页：`./themes/logos/ant-group-color.png` ＋ 分隔线 ＋ `./themes/logos/alipay-color.png`

单 Logo 路径：
- 深色页：`./themes/logos/ant-group-white.png`
- 白色页：`./themes/logos/ant-group-color.png`

其他子品牌（明确提及时）：
- mybank：`./themes/logos/mybank-white.png` / `./themes/logos/mybank-color.png`

## CSS

```css
:root {
    --primary:       #1677FF;
    --primary-dark:  #0950D9;
    --primary-light: #4096FF;
    --primary-pale:  #E6F4FF;
    --primary-dim:   rgba(22, 119, 255, 0.12);
    --cover-bg:      linear-gradient(125deg, #0A3DA8 0%, #1263EA 35%, #2B8FFF 65%, #5AB6FF 100%);
    --section-bg:    linear-gradient(135deg, #0B2F8A 0%, #1554AD 50%, #1677FF 100%);
    --red:           #E8380D;
    --green:         #52C41A;
    --orange:        #FA8C16;
}
.slide-section { background: linear-gradient(135deg, #0B2F8A 0%, #1554AD 50%, #1677FF 100%) !important; }
.slide-qa      { background: linear-gradient(125deg, #0A3DA8 0%, #1263EA 35%, #2B8FFF 65%, #5AB6FF 100%) !important; }
```


path: themes/apple.md
content: # 苹果主题 `apple`

极简深色，来自 Apple 官网深色系。副标题用浅灰代替彩色。

## Logo
- 待补充（封面 cover-top 保留部门名文字）

## CSS

```css
:root {
    --primary:       #0071E3;
    --primary-dark:  #0055B0;
    --primary-light: #3A9AFF;
    --primary-pale:  #EEF5FF;
    --primary-dim:   rgba(0, 113, 227, 0.12);
    --cover-bg:      linear-gradient(125deg, #000000 0%, #1A1A1A 35%, #2C2C2E 65%, #3A3A3C 100%);
    --section-bg:    linear-gradient(135deg, #000000 0%, #1C1C1E 50%, #2C2C2E 100%);
    --text-1:        #1D1D1F;
    --text-2:        #424245;
    --text-3:        #86868B;
    --bg-page:       #F5F5F7;
    --red:           #FF3B30;
    --green:         #34C759;
    --orange:        #FF9500;
}
.slide-section { background: linear-gradient(135deg, #000000 0%, #1C1C1E 50%, #2C2C2E 100%) !important; }
.slide-qa      { background: linear-gradient(125deg, #000000 0%, #1A1A1A 35%, #2C2C2E 65%, #3A3A3C 100%) !important; }
.header-sub    { color: var(--text-3) !important; font-weight: 300 !important; }
```

## 补充规则

- **信息密度最低**：每页内容比其他主题少 20–30%，大量留白是设计语言的一部分。
- **黑白灰为主**：图表、卡片、标签优先黑、深灰、浅灰；主色蓝只做少量强调点缀，不做大面积底色。
- **字重偏细**：正文和副标题权重优先 `300`/`400`；只有核心判断句、数字用 `700`+。
- **圆角更大**：卡片圆角可比默认值（6px）更大，如 12–16px；边框极细（0.5px）或省略。
- **封面接近纯黑**：封面渐变是黑→深灰，避免彩色装饰弧形；字体纯白，简洁有力。


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