关于 html-presentations
html-presentations 是用于制作单个 HTML 文件幻灯片的技能,仅使用原生 HTML、CSS 和 JavaScript。它涵盖主题布局、键盘导航、URL 幻灯片位置保存,以及 SVG 图表和动画。
主要功能
- 01单文件 HTML 幻灯片
- 02三种配色主题
- 03键盘切换幻灯片
- 04通过 URL 哈希保存页码
- 05主题化 SVG 图表与动画
适用场景
- 01无需构建步骤制作浏览器演示文稿
- 02分享可直接打开指定幻灯片的链接
- 03用 SVG 动画图表讲解流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ericmjl/ skills --skill html-presentations安装后在 Claude Code 中输入/html-presentations确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/canvas-chat-overview.htmlexample-agent-skills-overview.htmlreferences/svg-animations.mdsvg-diagrams.mdtheme-catppuccin.csstheme-nord.csstheme-terminal.css详细介绍
HTML 演示文稿
创建独立的 HTML 幻灯片演示文稿。无需框架、CDN 或构建步骤——只需一个 .html 文件,即可在任何浏览器中打开。
用法
当用户需要演示文稿、幻灯片组或幻灯片,并希望使用原生 HTML、CSS 和 JavaScript 制作单个 HTML 文件时,请使用此技能。不适用于 reveal.js 或基于框架的演示文稿。生成幻灯片之前,请先询问目标、受众和交付方式(步骤 0);根据答案调整长度、重点和内容密度。
要求
无。输出为单个 .html 文件,可在任何现代浏览器(Chrome、Firefox、Safari、Edge)中运行。
功能
- 单个文件——所有 CSS、JS、SVG 图表和内容都放在一个
.html文件中 - 主题化——提供三种配色预设:terminal.css(默认)、catppuccin、nord
- 键盘导航——按 Space/Right/k 前进,按 Shift+Space/Left/j 后退;按 Escape 可打开所有幻灯片的可视网格,跳转到任意幻灯片
- URL 哈希持久化——当前幻灯片的索引会存储在 URL 哈希中(例如
#5)。刷新页面后仍会停留在同一张幻灯片;分享带有哈希的链接即可直接打开对应幻灯片 - SVG 图表——流程图、架构图、流水线、对比图,全部通过 CSS 自定义属性应用主题
- SVG 动画——路径绘制、依次淡入、脉冲/发光、流动动画;幻灯片激活时触发
工作原理
示例:功能展示
查看以下完整演示文稿,了解此技能能够生成的内容:
-
assets/example-agent-skills-overview.html — 精致的概览,包含聚焦式布局、双列网格、丰富的 SVG 图表、胶囊徽标、进度导航和概览网格。可将其作为结构和视觉效果的质量标杆。
-
assets/canvas-chat-overview.html — 产品概览,包含交互式循环幻灯片演示:
- 迷你画布——可拖动节点,以及实时更新的幻灯片 SVG 连线
- 聊天与回复——模拟输入、节点显示和连线绘制;自动循环
- 高亮与分支——模拟文本选择、摘录节点和来源高亮;循环播放
- 多选——模拟节点选择、回复、“Thinking…”和合并节点
- 自动布局——模拟光标移至工具栏的“Layout”按钮,点击时出现日芒/射线效果,节点从杂乱状态动画变为整齐布局;重置后循环播放
- 导航——模拟光标和滚动提示;语义缩放放大(显示完整节点内容)和缩小(显示摘要视图);循环播放
- URL 哈希持久化——刷新后仍停留在当前幻灯片;使用
setTimeout(0)分发slidechange,以便演示脚本在加载时运行
当用户需要自动循环演示(光标、点击效果、缩放或逐步 UI 模拟),而不是静态图表时,请使用 Canvas Chat 概览。
步骤 0:询问用户(开始制作前)
在向用户确认以下事项之前,不要生成幻灯片。 以对话的方式提问;每条消息问一两个问题即可。根据用户的回答,决定演示文稿的长度、侧重点,以及文字与图表的占比。
(a) 演示目标
如果用户还没有明确说明目标,提供选项供其选择或组合:
- 推介 / 说服 — 说服受众接受某个想法、产品或决策
- 解释 / 教学 — 传达某个概念、流程或系统,让受众理解
- 更新 / 状态 — 分享进展、结果或发现(例如项目更新、实验结果)
- 参考资料 / 讲义 — 供受众留存并日后浏览;内容密集,但便于快速查阅
- 概览 / 路线图 — 提供高层次的结构或计划;细节少,着眼全局
- 其他 — 请用户自行描述
(b) 目标受众
询问演示文稿面向哪些人(例如高管、工程师、客户、学生、公众)。这会影响术语的使用程度、内容深度,以及需要解释哪些内容、哪些内容可以默认受众已知。
(c) 呈现方式
- 有演讲者 — 幻灯片辅助演讲;每张幻灯片文字更少,视觉元素更多,由演讲者补充说明。优先使用简短要点和图表。
- 自助浏览式(无演讲者) — 受众自行阅读;幻灯片必须能够独立传达信息。需要更多说明性文字、更清晰的标签,也可以增加幻灯片数量或提高内容密度。
(d) 其他有用的问题
- 大致时长 — “几分钟?” / “10–15 分钟?” / “深入讲解?”——据此调整幻灯片数量和细节程度。
- 必备内容与省略内容 — “有哪些内容必须包含,或应该省略?”
- 语气 — 正式还是随意、严肃还是轻松,若会影响措辞或设计,请予以确认。
记录用户的回答,并在选择主题、编写大纲和决定每张幻灯片的内容量时加以参考。
步骤 1:选择主题
阅读一份主题参考文件,并将其中的 CSS 粘贴到演示文稿的 <style> 块中。默认使用 catppuccin,除非用户另有要求。
| 主题 | 文件 | 风格 |
|---|---|---|
| terminal.css | references/theme-terminal.css | 黑底绿色磷光、等宽字体、扫描线 |
| catppuccin | references/theme-catppuccin.css | 暖色深色底上的柔和点缀色(Mocha 变体) |
| nord | references/theme-nord.css | 北极蓝灰色调、低饱和配色 |
所有主题都定义了相同的 CSS 自定义属性(--bg、--fg、--accent 等),因此基础布局 CSS 和所有图表都可以与任意主题搭配使用。
步骤 2:生成 HTML 文件
使用此骨架。三个部分——主题 CSS、基础布局 CSS 和 导航 JS——结构始终相同;只有幻灯片内容会变化。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PRESENTATION_TITLE</title>
<style>
/* ===== Theme (paste from reference file) ===== */
:root { /* ... theme variables ... */ }
/* ===== Base layout ===== */
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }
body {
background: var(--bg);
color: var(--fg);
font-family: var(--body-font);
}
.deck { position: relative; width: 100vw; height: 100vh; overflow: hidden; }
.slide {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
justify-content: center;
padding: 6vh 8vw;
opacity: 0;
transition: opacity 0.5s ease;
pointer-events: none;
overflow: hidden;
}
.slide.active {
opacity: 1;
pointer-events: auto;
}
/* ===== Typography ===== */
h1 { font-size: 3.5vw; font-family: var(--heading-font); color: var(--heading); margin-bottom: 2vh; }
h2 { font-size: 2.8vw; font-family: var(--heading-font); color: var(--heading); margin-bottom: 2vh; }
h3 { font-size: 2.2vw; font-family: var(--heading-font); color: var(--heading); margin-bottom: 1.5vh; }
p, li { font-size: 1.6vw; line-height: 1.7; margin-bottom: 1vh; }
ul, ol { padding-left: 2vw; }
code {
font-family: var(--mono-font);
background: var(--surface);
padding: 0.15em 0.4em;
border-radius: 4px;
font-size: 0.9em;
}
pre {
background: var(--surface);
padding: 2vh 2vw;
border-radius: 8px;
overflow-x: auto;
}
pre code { background: none; padding: 0; font-size: 1.3vw; }
/* ===== Bottom nav bar (arrows + progress dots) ===== */
.nav-bar {
position: fixed;
bottom: 2.5vh;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 12px;
align-items: center;
z-index: 100;
}
.progress {
display: flex;
gap: 6px;
align-items: center;
}
.progress-dot {
width: 8px;
height: 8px;
border-radius: 4px;
background: var(--muted);
transition: width 0.35s ease, background 0.35s ease;
cursor: pointer;
}
.progress-dot.active {
width: 32px;
background: var(--accent);
}
.nav-arrow {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 36px;
height: 32px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 6px;
color: var(--muted);
cursor: pointer;
transition: color 0.2s, border-color 0.2s;
user-select: none;
flex-shrink: 0;
}
.nav-arrow:hover {
color: var(--accent);
border-color: var(--accent);
}
.nav-arrow .arrow {
font-size: 1vw;
line-height: 1;
font-family: var(--body-font);
}
.nav-arrow .key {
font-size: 0.55vw;
font-family: var(--mono-font);
opacity: 0.7;
}
/* ===== Overview grid (Escape) ===== */
.overview-overlay {
position: fixed;
inset: 0;
background: var(--bg);
z-index: 200;
display: none;
align-items: center;
justify-content: center;
padding: 3vh 3vw;
overflow: auto;
}
.overview-overlay.visible {
display: flex;
flex-direction: column;
}
.overview-overlay .overview-hint {
font-size: 1.2vw;
color: var(--muted);
margin-bottom: 2vh;
}
.overview-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 1rem;
max-width: 90vw;
}
.overview-card {
background: var(--surface);
border: 2px solid var(--border);
border-radius: 8px;
padding: 1rem;
cursor: pointer;
text-align: center;
transition: border-color 0.2s, transform 0.2s;
}
.overview-card:hover {
border-color: var(--accent);
transform: scale(1.02);
}
.overview-card.active {
border-color: var(--accent);
background: var(--surface2);
}
.overview-card .num {
font-size: 1.5vw;
font-weight: 700;
color: var(--accent);
display: block;
}
.overview-card .title {
font-size: 0.9vw;
margin-top: 0.5rem;
color: var(--fg);
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ===== Supplementary slides ===== */
.supp-label {
display: inline-block; font-size: 0.85vw; text-transform: uppercase;
letter-spacing: 0.15em; color: var(--muted); border: 1px solid var(--border);
border-radius: 4px; padding: 0.2em 0.7em; margin-bottom: 2vh;
}
.supp-link {
color: var(--accent); text-decoration: none;
border-bottom: 1px dashed var(--accent); cursor: pointer;
}
.supp-link:hover { border-bottom-style: solid; }
.progress-sep {
width: 1px; height: 12px; background: var(--border); margin: 0 4px; flex-shrink: 0;
}
.progress-dot.supplementary { opacity: 0.5; }
.progress-dot.supplementary.active { opacity: 1; }
.overview-card.supplementary { opacity: 0.7; }
/* ===== Diagram base classes ===== */
.diagram-node { fill: var(--diagram-fill); stroke: var(--diagram-stroke); stroke-width: 2; }
.diagram-highlight { fill: var(--diagram-highlight); stroke: var(--diagram-highlight); stroke-width: 2; }
.diagram-diamond { fill: var(--surface2); stroke: var(--diagram-stroke); stroke-width: 2; }
.diagram-label { fill: var(--diagram-text); font-family: var(--body-font); font-size: 14px; text-anchor: middle; dominant-baseline: central; }
.diagram-label-inv { fill: var(--bg); font-family: var(--body-font); font-size: 14px; text-anchor: middle; dominant-baseline: central; }
.diagram-label-sm { fill: var(--diagram-text); font-family: var(--body-font); font-size: 11px; text-anchor: middle; dominant-baseline: central; opacity: 0.7; }
.diagram-edge { stroke: var(--diagram-arrow); stroke-width: 2; fill: none; }
.diagram-edge-accent { stroke: var(--diagram-stroke); stroke-width: 2; fill: none; }
.slide svg { max-width: 100%; height: auto; }
/* ===== Animation base classes ===== */
.slide .anim-fade {
opacity: 0;
transform: translateY(20px);
}
.slide.active .anim-fade {
opacity: 1;
transform: translateY(0);
transition: opacity 0.6s ease var(--delay, 0s),
transform 0.6s ease var(--delay, 0s);
}
.slide .anim-draw {
stroke-dasharray: var(--len, 1000);
stroke-dashoffset: var(--len, 1000);
}
.slide.active .anim-draw {
stroke-dashoffset: 0;
transition: stroke-dashoffset 1.2s ease-in-out var(--delay, 0s);
}
@keyframes pulse-glow {
0%, 100% { filter: drop-shadow(0 0 3px var(--diagram-stroke)); }
50% { filter: drop-shadow(0 0 10px var(--diagram-stroke)); }
}
.slide.active .anim-pulse {
animation: pulse-glow 2s ease-in-out infinite;
animation-delay: var(--delay, 0s);
}
/* Additional theme-specific rules (paste from theme file if any) */
</style>
</head>
<body>
<div class="deck">
<section class="slide" id="title">
<h1>Presentation Title</h1>
<p>Author — Date</p>
</section>
<section class="slide" id="slide-2">
<h2>Slide Heading</h2>
<ul>
<li>Point one</li>
<li>Point two</li>
</ul>
</section>
</div>
<div class="nav-bar">
<div class="nav-arrow nav-prev"><span class="arrow">‹</span><span class="key">j</span></div>
<div class="progress"></div>
<div class="nav-arrow nav-next"><span class="arrow">›</span><span class="key">k</span></div>
</div>
<div class="overview-overlay" id="overview" aria-label="Slide overview">
<p class="overview-hint">Click a slide to jump · Esc to close</p>
<div class="overview-grid"></div>
</div>
<script>
(function () {
var slides = document.querySelectorAll('.slide');
var current = 0;
var total = slides.length;
var progress = document.querySelector('.progress');
var prevBtn = document.querySelector('.nav-prev');
var nextBtn = document.querySelector('.nav-next');
var overview = document.getElementById('overview');
var overviewGrid = overview.querySelector('.overview-grid');
var suppInserted = false;
for (var i = 0; i < total; i++) {
if (!suppInserted && slides[i].hasAttribute('data-supplementary')) {
var sep = document.createElement('div');
sep.className = 'progress-sep';
progress.appendChild(sep);
suppInserted = true;
}
var dot = document.createElement('div');
dot.className = 'progress-dot';
if (slides[i].hasAttribute('data-supplementary')) dot.classList.add('supplementary');
dot.dataset.index = i;
dot.addEventListener('click', function () {
show(parseInt(this.dataset.index));
});
progress.appendChild(dot);
}
var dots = progress.querySelectorAll('.progress-dot');
for (var i = 0; i < total; i++) {
var card = document.createElement('button');
card.type = 'button';
card.className = 'overview-card';
if (slides[i].hasAttribute('data-supplementary')) card.classList.add('supplementary');
card.dataset.index = i;
var titleEl = slides[i].querySelector('h1, h2, h3');
var title = titleEl ? titleEl.textContent.trim() : 'Slide ' + (i + 1);
card.innerHTML = '<span class="num">' + (i + 1) + '</span><span class="title">' + title.replace(/</g, '<') + '</span>';
card.addEventListener('click', function () {
show(parseInt(this.dataset.index));
overview.classList.remove('visible');
});
overviewGrid.appendChild(card);
}
var overviewCards = overviewGrid.querySelectorAll('.overview-card');
var slideIdMap = {};
for (var i = 0; i < total; i++) {
if (slides[i].id) slideIdMap[slides[i].id] = i;
}
function getSlideIndexFromHash() {
var hash = window.location.hash;
if (!hash || hash.length < 2) return 0;
var n = parseInt(hash.slice(1), 10);
if (isNaN(n) || n < 0 || n >= total) return 0;
return n;
}
function updateHashForSlide(idx) {
var url = window.location.pathname + window.location.search + '#' + idx;
if (window.history.replaceState) {
window.history.replaceState(null, '', url);
} else {
window.location.hash = idx;
}
}
function show(idx) {
if (idx < 0 || idx >= total) return;
slides[current].classList.remove('active');
dots[current].classList.remove('active');
if (overviewCards[current]) overviewCards[current].classList.remove('active');
current = idx;
slides[current].classList.add('active');
dots[current].classList.add('active');
if (overviewCards[current]) overviewCards[current].classList.add('active');
updateHashForSlide(current);
}
document.addEventListener('click', function (e) {
var link = e.target.closest('[data-goto]');
if (link) {
e.preventDefault();
var target = link.getAttribute('data-goto');
if (slideIdMap[target] !== undefined) show(slideIdMap[target]);
}
});
prevBtn.addEventListener('click', function () { show(current - 1); });
nextBtn.addEventListener('click', function () { show(current + 1); });
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
overview.classList.toggle('visible');
if (overview.classList.contains('visible')) {
for (var i = 0; i < overviewCards.length; i++) {
overviewCards[i].classList.toggle('active', i === current);
}
}
return;
}
if (overview.classList.contains('visible')) return;
switch (e.key) {
case ' ':
e.preventDefault();
show(current + (e.shiftKey ? -1 : 1));
break;
case 'ArrowRight': case 'k':
show(current + 1);
break;
case 'ArrowLeft': case 'j':
show(current - 1);
break;
}
});
window.addEventListener('hashchange', function () {
var idx = getSlideIndexFromHash();
if (idx !== current) show(idx);
});
show(getSlideIndexFromHash());
})();
</script>
</body>
</html>
第 3 步:添加幻灯片内容
标题幻灯片——居中、大号标题、副标题:
<section class="slide" id="title">
<h1>Title Text</h1>
<p>Subtitle or author</p>
</section>
内容幻灯片——标题 + 正文:
<section class="slide" id="topic-name">
<h2>Heading</h2>
<p>Body text or a list:</p>
<ul>
<li>Item</li>
</ul>
</section>
双栏幻灯片——使用内联 CSS 网格:
<section class="slide" id="comparison">
<h2>Side by Side</h2>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:4vw; flex:1;">
<div>Left column content</div>
<div>Right column content</div>
</div>
</section>
图表幻灯片——使用图表类的内联 SVG:
<section class="slide" id="architecture">
<h2>System Architecture</h2>
<svg viewBox="0 0 800 400">
</svg>
</section>
聚焦展示 / 功能幻灯片——双栏网格,左侧为文字,右侧为大型 SVG。适用于以醒目的视觉效果展示工具、功能或概念:
<section class="slide" id="spotlight-feature" style="justify-content:center;">
<div style="display:grid; grid-template-columns:1fr 1fr; gap:4vw; align-items:center;">
<div>
<p class="dim anim-fade" style="--delay:0.1s; font-size:1vw; text-transform:uppercase; letter-spacing:0.2em; margin-bottom:1vh;">Label</p>
<h2 class="anim-fade" style="--delay:0.2s; font-size:3.2vw;"><code style="background:var(--surface); padding:0.15em 0.4em; border-radius:6px;">Feature Name</code></h2>
<p class="anim-fade" style="--delay:0.4s; font-size:2.4vw; color:var(--accent); margin-top:2vh; line-height:1.3;">Short tagline.<br>One line per idea.</p>
<div class="anim-fade" style="--delay:0.7s; display:flex; gap:0.8vw; flex-wrap:wrap; margin-top:3vh;">
<span style="background:var(--accent); color:white; padding:0.4em 1em; border-radius:999px; font-size:1.1vw; font-weight:600;">Primary pill</span>
<span style="background:var(--surface); color:var(--fg); padding:0.4em 1em; border-radius:999px; font-size:1.1vw;">Secondary pill</span>
<span style="background:var(--surface); color:var(--fg); padding:0.4em 1em; border-radius:999px; font-size:1.1vw;">Another pill</span>
</div>
</div>
<div class="anim-fade" style="--delay:0.5s;">
<svg viewBox="0 0 400 340">
</svg>
</div>
</div>
</section>
聚焦展示的设计原则:
- 填满两栏。 SVG 的
viewBox应足够宽裕(例如0 0 400 340),让图表填满幻灯片右半部分。过小且留白过多的 SVG 会显得不完整。 - 不使用项目符号。 将项目符号列表替换为简短的标语(使用
<br>换行),并在下方放置关键词胶囊。 - 突出显示一个胶囊。 主关键词使用
background:var(--accent); color:white; font-weight:600;其余关键词保持中性色(var(--surface)/var(--fg))。 - 丰富图表内容。 右栏的 SVG 应讲述一个故事——使用多个节点、箭头、标签、阶段或微型线框图。图表是幻灯片的视觉焦点。
补充幻灯片——放在“谢谢”幻灯片 之后 的详情、常见问题或参考幻灯片。通过 data-goto 从主幻灯片链接到这些幻灯片:
<section class="slide" id="supp-topic" data-supplementary>
<span class="supp-label">Supplementary</span>
<h2>Detailed Topic</h2>
<p>Deep-dive content that supports a main slide.</p>
</section>
补充幻灯片的约定:
- 放在“谢谢”之后。 演示文稿的顺序为:主幻灯片 → 致谢 → 谢谢 → 补充内容。观众看到的是简洁的结尾;补充材料可供问答或自主学习时使用。
- 使用
data-supplementary标记。 将此属性添加到<section>。JS 会用它在进度条中插入视觉分隔线,并将补充内容对应的圆点淡化显示。 - 添加
<span class="supp-label">。 在幻灯片顶部显示“补充内容”或“补充内容 · 常见问题”徽标。 - 从主幻灯片链接。 在任意主幻灯片中使用
<a class="supp-link" data-goto="supp-topic">详情 →</a>。JS 会拦截点击事件,并按 ID 跳转到目标幻灯片。 - 从“谢谢”幻灯片链接。 在“谢谢”幻灯片上列出所有补充幻灯片的链接,让观众了解可用内容。
- ID 约定。 补充幻灯片的 ID 以
supp-为前缀(例如supp-detail、supp-harness)。
必需的 CSS(已包含在上方的基础布局中):
.supp-label {
display: inline-block; font-size: 0.85vw; text-transform: uppercase;
letter-spacing: 0.15em; color: var(--muted); border: 1px solid var(--border);
border-radius: 4px; padding: 0.2em 0.7em; margin-bottom: 2vh;
}
.supp-link {
color: var(--accent); text-decoration: none;
border-bottom: 1px dashed var(--accent); cursor: pointer;
}
.supp-link:hover { border-bottom-style: solid; }
.progress-sep {
width: 1px; height: 12px; background: var(--border); margin: 0 4px; flex-shrink: 0;
}
.progress-dot.supplementary { opacity: 0.5; }
.progress-dot.supplementary.active { opacity: 1; }
必需的 JS 补充内容(已包含在上方的基础脚本中):
- 构建
slideIdMap,将每张幻灯片的id映射到其索引。 - 点击
data-goto链接时:show(slideIdMap[target])。 - 构建进度圆点时,在第一张补充幻灯片之前插入
.progress-sep<div>。 - 为带有
data-supplementary的幻灯片对应的圆点和概览卡片添加.supplementary类。 - URL 哈希持久化: 将当前幻灯片索引存入 URL 哈希(例如
#5)。页面加载时,调用show(getSlideIndexFromHash())而不是show(0),这样刷新页面后仍会停留在同一张幻灯片中。在show(idx)中调用updateHashForSlide(current)(可用时使用history.replaceState),让 URL 始终反映当前幻灯片。添加hashchange监听器,以便用户编辑 URL 或使用浏览器的前进 / 后退功能时同步演示文稿。
每个 <section> 都必须有唯一的 id。
演示文稿结构
结构良好的演示文稿应按以下顺序排列:
- 标题——名称、作者、日期
- 主幻灯片——核心内容(定义、缘由、方法、示例等)
- 致谢——鸣谢协作者、工具和来源
- 谢谢——结尾幻灯片,包含链接和补充主题列表
- 补充内容——详情幻灯片、常见问题、参考资料(从主幻灯片和“谢谢”幻灯片链接)
进度条会用一条细竖线在视觉上分隔主内容与补充内容。
步骤 4:添加 SVG 图表
阅读 references/svg-diagrams.md,了解完整示例:
- 流程图 — 决策树、包含方框、菱形和箭头的流程
- 架构图 — 分层结构、互连的服务
- 管线图 — 流经各处理阶段的数据
- 对比图 — 并排对比功能或选项
所有图表 SVG 元素都使用基础布局中定义的 .diagram-* CSS 类,因此会自动匹配当前主题。
箭头标记 — 每个使用箭头的 SVG 都需要此 <defs> 块:
<defs>
<marker id="arrow" markerWidth="10" markerHeight="7"
refX="9" refY="3.5" orient="auto-start-reverse">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--diagram-arrow)" />
</marker>
<marker id="arrow-accent" markerWidth="10" markerHeight="7"
refX="9" refY="3.5" orient="auto-start-reverse">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--diagram-stroke)" />
</marker>
<filter id="shadow" x="-5%" y="-5%" width="110%" height="120%">
<feDropShadow dx="0" dy="2" stdDeviation="3" flood-opacity="0.15" />
</filter>
</defs>
在 .diagram-edge 线上使用 marker-end="url(#arrow)",在 .diagram-edge-accent 线上使用 marker-end="url(#arrow-accent)"。
步骤 5:酌情添加 SVG 动画
阅读 references/svg-animations.md,了解完整示例和每种动画适用场景的指导。
三个内置动画类(在基础 CSS 中定义):
| 类 | 效果 | 触发时机 |
|---|---|---|
.anim-fade | 淡入视图 | 幻灯片变为活动状态 |
.anim-draw | SVG 路径自行绘制(stroke-dashoffset) | 幻灯片变为活动状态 |
.anim-pulse | SVG 元素发出脉冲光晕 | 幻灯片变为活动状态 |
使用内联 --delay 设置错开时间:
<p class="anim-fade" style="--delay:0.2s">Appears first</p>
<p class="anim-fade" style="--delay:0.5s">Appears second</p>
绘制 SVG 路径时,将 --len 设为路径的总长度:
<path class="diagram-edge anim-draw" style="--len:350"
d="M 50,100 C 150,50 250,150 350,100" />
动画的适用场景(摘要 — 完整指导请参阅参考文件):
- 路径绘制:展示连接的建立过程、网络拓扑、图表的构建过程
- 依次淡入:逐步讲解概念、讲述一个故事的列表
- 脉冲/光晕:突出流程中的当前步骤、吸引注意力到关键节点
- 不要为纯装饰元素、每个项目符号,或观众需要立即阅读的内容添加动画
设计指南
- 每张幻灯片只讲一个要点 — 避免内容过于拥挤。幻灯片多一些也没关系。
- 使用大号文字 — 如果幻灯片内容较少,就放大文字。使用
font-size: 2.5vw或更大。 - 优先使用图表,而非项目符号列表 — 讲解流程、架构或流转时,使用 SVG 图表,而不是项目符号列表。
- 保持间距一致 — 使用
vh/vw单位设置内边距和外边距,让间距随视口缩放。 - 建立颜色层次 — 标题使用
--heading,正文使用--fg,次要文字使用--muted,强调内容使用--accent。 - 代码块 — 使用
<pre><code>,并应用主题的--code-bg/--code-fg。代码应简短(每张幻灯片少于 15 行)。 - 在幻灯片中使用 Python — 添加 Python 代码时,应确保代码可通过
uv run script.py运行,从而独立运行,无需外部软件环境。在脚本顶部使用 PEP 723 内联脚本元数据(# /// script、requires-python、dependencies、# ///),并在幻灯片或演讲者备注中说明观众使用uv run script.py运行代码。 - 不使用外部素材 — 所有内容都应内联。需要图像时,使用内联 SVG 插图,而非栅格图像。
- 充实幻灯片 — 避免内容挤在一个角落、其余部分空空如也的布局。使用双栏网格(
grid-template-columns: 1fr 1fr),让文字和图表并排平衡,并使用align-items:center使两栏内容垂直居中。 - 功能介绍用标语代替项目符号 — 突出介绍某项功能或概念时,用简短标语(分成 2–3 行短句,并以
<br>换行)取代项目符号列表,并在下方添加关键词标签。这种方式更有冲击力,也更易于浏览。 - 让 SVG 成为视觉焦点 — 幻灯片包含图表时,图表至少应占幻灯片宽度的一半。使用宽裕的
viewBox尺寸,并通过多个节点、标签、阶段、线框线条和回环箭头等细节填充空间。小图表孤零零地漂浮在大片留白中,会显得未完成。
Eric 的演示文稿原则(适用于所有演示文稿:HTML、PPTX、咨询风格;于 2026-09-01 委托制作 BCG 风格的咨询演示文稿技能时提出)
- 每张幻灯片只传达一个关键信息,并用行动式标题表达(结论 + 意义),绝不使用主题标签。
- 使用人人都能理解的通俗语言。避免高深的咨询术语(协同效应、杠杆、整体性)。
- 幻灯片应简洁清晰:留出充足的页边距和空白;如果内容太挤,就拆分幻灯片。
- 除非几何结构本身能够承载论点,否则绝不要把要点排进方框/网格/支柱结构(四宫格、2x2、六大支柱)。如果布局不能让信息更快传达,就使用段落或列表。
- 只保留一种强调色,用于突出决定性的对比;每张展示证据的幻灯片都要注明来源。
- 品牌演示文稿应使用品牌的官方颜色,并通过品牌自有网站的实时 CSS 核实(以 HTML 格式获取;获取路径见 webfetch-empty-use-agent-browser),绝不使用第三方聚合网站列出的颜色。BCG 调色板于 2026-09-01 根据 bcg.com 中 id=global-colors 的内联样式块核实:#144622、#0C2B15、#197A56、#21BF61、#96F878。