html-presentations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

HTML Presentations

HTML演示文稿

Create self-contained HTML slide presentations. No frameworks, no CDN, no build step — one
.html
file you can open in any browser.
创建自包含的HTML幻灯片演示文稿。无需框架、无需CDN、无需构建步骤——仅需一个
.html
文件,你可以在任意浏览器中打开。

Usage

使用说明

Use this skill when the user asks for a presentation, slide deck, or slides and wants a single HTML file with vanilla HTML, CSS, and JavaScript. Not for reveal.js or framework-based presentations. Before generating slides, ask about goals, audience, and delivery mode (Step 0); use the answers to shape length, emphasis, and content density.
当用户需要演示文稿、slide deck或幻灯片,且想要使用原生HTML、CSS、JavaScript编写的单HTML文件时,可以使用此技能。不适用于reveal.js或基于框架的演示文稿。生成幻灯片前,请先询问用户演示目标、受众和交付模式(步骤0);根据回答调整幻灯片长度、内容重点和信息密度。

Requirements

依赖要求

None. Output is a single
.html
file. Works in any modern browser (Chrome, Firefox, Safari, Edge).
无依赖。输出为单个
.html
文件,可在所有现代浏览器(Chrome、Firefox、Safari、Edge)中运行。

What It Does

功能特性

  1. Single file — All CSS, JS, SVG diagrams, and content in one
    .html
    file
  2. Themed — Three color presets: terminal.css (default), catppuccin, nord
  3. Keyboard navigation — Space/Right/k forward, Shift+Space/Left/j backward; Escape opens a visual grid of all slides to jump to any slide
  4. URL hash persistence — The current slide index is stored in the URL hash (e.g.
    #5
    ). Refreshing the page keeps you on the same slide; sharing a link with a hash opens that slide directly
  5. SVG diagrams — Flowcharts, architecture diagrams, pipelines, comparisons — all themed via CSS custom properties
  6. SVG animations — Path drawing, sequential fade-in, pulse/glow, flow — triggered when a slide becomes active
  1. 单文件部署——所有CSS、JS、SVG图表和内容都整合在一个
    .html
    文件中
  2. 多主题支持——三种预设配色:terminal.css(默认)、catppuccin、nord
  3. 键盘导航——空格键/右方向键/k键向前翻页,Shift+空格键/左方向键/j键向后翻页;ESC键可打开所有幻灯片的可视化网格,快速跳转到任意幻灯片
  4. URL哈希持久化——当前幻灯片索引会存储在URL哈希中(例如
    #5
    )。刷新页面会停留在当前幻灯片;分享带哈希的链接可直接打开对应幻灯片
  5. SVG图表支持——流程图、架构图、流水线图、对比图,所有图表都可通过CSS自定义属性适配主题
  6. SVG动画支持——路径绘制、顺序淡入、脉冲/发光、流动效果,幻灯片激活时自动触发

How It Works

实现原理

Exemplars: what’s possible

示例:支持的效果

Study these full presentations to see what the skill can produce:
  1. assets/example-agent-skills-overview.html — Polished overview with spotlight layout, two-column grids, rich SVG diagrams, pill badges, progress nav, and overview grid. Use it as a quality benchmark for structure and visuals.
  2. assets/canvas-chat-overview.html — Product overview with interactive, looping slide demos:
    • Mini-canvases — Draggable nodes and live-updating SVG edges on the slide
    • Chat & Reply — Simulated typing, node reveal, edge draw; loops automatically
    • Highlight & Branch — Simulated text selection, excerpt node, source highlight; loops
    • Multi-Select — Simulated node selection, reply, “Thinking…”, merged node; loops
    • Auto-Layout — Simulated cursor moving to a toolbar “Layout” button, sunburst/rays on click, nodes animate from messy to clean; resets and loops
    • Navigate — Simulated cursor and scroll hint; semantic zoom in (full node content) and zoom out (summary view); loops
    • URL hash persistence — Refreshing keeps the current slide;
      slidechange
      dispatched with
      setTimeout(0)
      so demo scripts can run on load
Use the Canvas Chat overview when the user wants automated, looping demos (cursor, click effects, zoom, or step-by-step UI simulation) instead of static diagrams.
参考这些完整演示文稿,了解此技能可产出的效果:
  1. assets/example-agent-skills-overview.html——完整的演示示例,包含聚光灯布局、双列网格、丰富的SVG图表、胶囊标签、进度导航、总览网格,可作为结构和视觉效果的质量标杆
  2. assets/canvas-chat-overview.html——产品演示文稿,包含交互式循环幻灯片演示
    • 迷你画布——幻灯片上支持可拖拽节点和实时更新的SVG连线
    • 聊天与回复——模拟输入、节点显示、连线绘制;自动循环
    • 高亮与分支——模拟文本选择、摘录节点、源内容高亮;自动循环
    • 多选操作——模拟节点选择、回复、“思考中…”、合并节点;自动循环
    • 自动布局——模拟光标移动到工具栏“布局”按钮,点击时放射状动画,节点从混乱状态动画变为整洁排列;重置后循环
    • 导航功能——模拟光标和滚动提示;语义化放大(展示完整节点内容)和缩小(展示概要视图);自动循环
    • URL哈希持久化——刷新页面保留当前幻灯片;
      setTimeout(0)
      触发
      slidechange
      事件,确保演示脚本可在加载时运行
当用户需要自动化循环演示(光标、点击效果、缩放、分步UI模拟)而非静态图表时,可以参考Canvas Chat演示示例。

Step 0: Ask the user (before building)

步骤0:先询问用户需求(构建前)

Do not generate slides until you have clarified the following with the user. Ask conversationally; one or two questions per message is fine. Use answers to decide length, emphasis, and how much text vs. diagrams to use.
(a) Goals of the presentation
If the user hasn't stated a clear goal, offer choices so they can pick or mix:
  • Pitch / persuade — convince the audience of an idea, product, or decision
  • Explain / teach — convey a concept, process, or system so the audience understands
  • Update / status — share progress, results, or findings (e.g. project update, experiment results)
  • Reference / handout — something to keep and skim later; dense but scannable
  • Overview / roadmap — high-level structure or plan; minimal detail, big picture
  • Other — let them describe
(b) Intended audience
Ask who will see it (e.g. executives, engineers, clients, students, general public). This drives jargon level, depth, and what to spell out vs. assume.
(c) Delivery mode
  • With a speaker — slides support the talk; less text per slide, more visuals; speaker fills in. Favor short bullets and diagrams.
  • Kiosk-style (no speaker) — audience reads alone; slides must stand on their own. More explanatory text, clearer labels, possibly more slides or denser content.
(d) Other useful questions
  • Rough length — "A few minutes?" / "10–15?" / "Deep dive?" — to calibrate slide count and detail.
  • Must-haves and skip — "Anything that must be included or that we should skip?"
  • Tone — formal vs. casual, serious vs. light, if it affects wording or design.
Record the user's answers and use them when choosing theme, building the outline, and deciding how much to put on each slide.
在和用户确认以下信息前,不要生成幻灯片。以对话形式提问,每次提问1-2个问题即可。根据回答决定幻灯片长度、重点、文本和图表的占比。
(a) 演示目标
如果用户没有说明明确目标,可以提供选项供用户选择或组合:
  • 推介/说服——向受众传递想法、产品或决策的价值,说服受众接受
  • 讲解/教学——传递概念、流程或系统知识,让受众理解相关内容
  • 进展/状态同步——分享进度、结果或发现(例如项目更新、实验结果)
  • 参考/手册——可供后续留存和浏览的材料,信息密度高且易于扫描
  • 概览/路线图——高层架构或计划,细节少,侧重全局视野
  • 其他——请用户自行描述
(b) 目标受众
询问演示的受众群体(例如高管、工程师、客户、学生、普通公众)。这将决定术语使用层级、内容深度、哪些内容需要明确说明、哪些内容可以默认受众已知。
(c) 交付模式
  • 有演讲者——幻灯片作为演讲的辅助,每页文本更少,更多视觉内容,演讲者补充细节。优先使用简短要点和图表。
  • 自助浏览(无演讲者)——受众自行阅读,幻灯片内容需要独立完整。可加入更多说明文本、更清晰的标签,可能需要更多幻灯片或更高的内容密度。
(d) 其他实用问题
  • 大致时长——“几分钟?”/“10-15分钟?”/“深度讲解?”——以此调整幻灯片数量和细节丰富度
  • 必选内容和排除内容——“有什么内容必须包含,或者应该跳过的吗?”
  • 风格要求——正式还是 casual,严肃还是轻松,是否会影响措辞或设计
记录用户的回答,在选择主题、构建大纲、决定每页幻灯片内容量时参考这些信息。

Step 1: Pick a theme

步骤1:选择主题

Read one of the theme reference files and paste its CSS into the presentation's
<style>
block. Default to catppuccin unless the user requests otherwise.
ThemeFileAesthetic
terminal.cssreferences/theme-terminal.cssGreen phosphor on black, monospace, scanlines
catppuccinreferences/theme-catppuccin.cssPastel accents on warm dark base (Mocha variant)
nordreferences/theme-nord.cssArctic blue-gray, muted palette
All themes define the same CSS custom properties (
--bg
,
--fg
,
--accent
, etc.), so the base layout CSS and all diagrams work with any theme.
读取任意主题参考文件,将其CSS粘贴到演示文稿的
<style>
块中。默认使用catppuccin,除非用户有其他要求。
主题文件风格
terminal.cssreferences/theme-terminal.css黑色背景绿色荧光效果,等宽字体,扫描线效果
catppuccinreferences/theme-catppuccin.css暖深色基底搭配柔和亮色(Mocha变体)
nordreferences/theme-nord.css北极蓝灰色调,低饱和度配色
所有主题都定义了相同的CSS自定义属性(
--bg
--fg
--accent
等),因此基础布局CSS和所有图表都可以适配任意主题。

Step 2: Generate the HTML file

步骤2:生成HTML文件

Use this skeleton. The three sections — theme CSS, base layout CSS, and navigation JS — are always the same structure; only slide content varies.
html
<!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 &mdash; Date</p>
    </section>

    <section class="slide" id="slide-2">
      <h2>Slide Heading</h2>
      <ul>
        <li>Point one</li>
        <li>Point two</li>
      </ul>
    </section>

    <!-- more <section class="slide"> elements -->

  </div>

  <div class="nav-bar">
    <div class="nav-arrow nav-prev"><span class="arrow">&#8249;</span><span class="key">j</span></div>
    <div class="progress"></div>
    <div class="nav-arrow nav-next"><span class="arrow">&#8250;</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, '&lt;') + '</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>
使用以下骨架结构。三个部分——主题CSS基础布局CSS导航JS——结构固定,仅幻灯片内容可变。
html
<!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 &mdash; Date</p>
    </section>

    <section class="slide" id="slide-2">
      <h2>Slide Heading</h2>
      <ul>
        <li>Point one</li>
        <li>Point two</li>
      </ul>
    </section>

    <!-- more <section class="slide"> elements -->

  </div>

  <div class="nav-bar">
    <div class="nav-arrow nav-prev"><span class="arrow">&#8249;</span><span class="key">j</span></div>
    <div class="progress"></div>
    <div class="nav-arrow nav-next"><span class="arrow">&#8250;</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, '&lt;') + '</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>

Step 3: Add slide content

步骤3:添加幻灯片内容

Title slide — centered, large heading, subtitle:
html
<section class="slide" id="title">
  <h1>Title Text</h1>
  <p>Subtitle or author</p>
</section>
Content slide — heading + body:
html
<section class="slide" id="topic-name">
  <h2>Heading</h2>
  <p>Body text or a list:</p>
  <ul>
    <li>Item</li>
  </ul>
</section>
Two-column slide — use inline CSS grid:
html
<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>
Diagram slide — inline SVG using diagram classes:
html
<section class="slide" id="architecture">
  <h2>System Architecture</h2>
  <svg viewBox="0 0 800 400">
    <!-- diagram elements using .diagram-node, .diagram-edge, etc. -->
  </svg>
</section>
Spotlight / feature slide — two-column grid with text on the left and a large SVG on the right. Use for showcasing a tool, feature, or concept with visual impact:
html
<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">
        <!-- Large, detailed diagram that fills the right half -->
      </svg>
    </div>
  </div>
</section>
Spotlight design principles:
  • Fill both columns. The SVG
    viewBox
    should be generous (e.g.
    0 0 400 340
    ) so the diagram fills the right half of the slide. Tiny SVGs with excess whitespace look unfinished.
  • No bullets. Replace bullet lists with a short tagline (line-broken with
    <br>
    ) and keyword pills below.
  • One highlighted pill. Use
    background:var(--accent); color:white; font-weight:600
    for the primary keyword; keep the rest neutral (
    var(--surface)
    /
    var(--fg)
    ).
  • Rich diagrams. The right-column SVG should tell a story — use multiple nodes, arrows, labels, phases, or a miniature wireframe. The diagram is the hero of the slide.
Supplementary slide — detail, FAQ, or reference slides placed after the Thank You slide. Linked from main slides via
data-goto
:
html
<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>
Supplementary slide conventions:
  • Place after Thank You. The presentation flows: main slides → acknowledgments → thank-you → supplementary. The audience sees a clean ending; supplementary material is available for Q&A or self-study.
  • Mark with
    data-supplementary
    .
    Add this attribute to the
    <section>
    . The JS uses it to insert a visual separator in the progress bar and dim supplementary dots.
  • Add
    <span class="supp-label">
    .
    Shows "Supplementary" or "Supplementary · FAQ" badge at the top of the slide.
  • Link from main slides. Use
    <a class="supp-link" data-goto="supp-topic">Detail →</a>
    in any main slide. The JS intercepts clicks and jumps to the target slide by ID.
  • Link from Thank You. List all supplementary slides as links on the thank-you slide so the audience knows what's available.
  • ID convention. Prefix supplementary slide IDs with
    supp-
    (e.g.
    supp-detail
    ,
    supp-harness
    ).
Required CSS (already in the base layout above):
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; }
Required JS additions (already in the base script above):
  • Build a
    slideIdMap
    mapping each slide's
    id
    to its index.
  • On
    data-goto
    link click:
    show(slideIdMap[target])
    .
  • When building progress dots, insert a
    .progress-sep
    <div>
    before the first supplementary slide.
  • Add
    .supplementary
    class to dots and overview cards for
    data-supplementary
    slides.
  • URL hash persistence: Store the current slide index in the URL hash (e.g.
    #5
    ). On load, call
    show(getSlideIndexFromHash())
    instead of
    show(0)
    so that refreshing the page keeps the user on the same slide. In
    show(idx)
    , call
    updateHashForSlide(current)
    (using
    history.replaceState
    when available) so the URL always reflects the current slide. Add a
    hashchange
    listener to sync the deck when the user edits the URL or uses browser back/forward.
Every
<section>
must have a unique
id
.
标题幻灯片——居中对齐,大标题,副标题:
html
<section class="slide" id="title">
  <h1>Title Text</h1>
  <p>Subtitle or author</p>
</section>
内容幻灯片——标题+正文:
html
<section class="slide" id="topic-name">
  <h2>Heading</h2>
  <p>Body text or a list:</p>
  <ul>
    <li>Item</li>
  </ul>
</section>
双列幻灯片——使用内联CSS grid:
html
<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:
html
<section class="slide" id="architecture">
  <h2>System Architecture</h2>
  <svg viewBox="0 0 800 400">
    <!-- diagram elements using .diagram-node, .diagram-edge, etc. -->
  </svg>
</section>
聚光灯/功能介绍幻灯片——双列网格,左侧为文本,右侧为大型SVG。用于展示工具、功能或概念,打造视觉冲击力:
html
<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">
        <!-- Large, detailed diagram that fills the right half -->
      </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应该能够传递信息——使用多个节点、箭头、标签、阶段或微型线框图。图表是这页幻灯片的核心。
补充幻灯片——细节、FAQ或参考幻灯片,放在感谢页之后。通过
data-goto
从主幻灯片跳转:
html
<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">
    。在幻灯片顶部显示“Supplementary”或“Supplementary · FAQ”徽章。
  • 从主幻灯片链接跳转。在任意主幻灯片中使用
    <a class="supp-link" data-goto="supp-topic">Detail →</a>
    。JS会拦截点击事件,按ID跳转到目标幻灯片。
  • 从感谢页链接跳转。在感谢页列出所有补充幻灯片的链接,让受众了解有哪些可用内容。
  • ID命名规范。补充幻灯片的ID前缀为
    supp-
    (例如
    supp-detail
    supp-harness
    )。
所需CSS(已包含在上面的基础布局中):
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

Presentation structure

演示文稿结构

A well-structured presentation follows this order:
  1. Title — name, author, date
  2. Main slides — the core content (definition, why, how, examples, etc.)
  3. Acknowledgments — credit collaborators, tools, sources
  4. Thank You — closing slide with links and a list of supplementary topics
  5. Supplementary — detail slides, FAQ, reference material (linked from main slides and the thank-you slide)
The progress bar visually separates main from supplementary with a thin vertical line.
结构合理的演示文稿遵循以下顺序:
  1. 标题页——演示名称、作者、日期
  2. 主幻灯片——核心内容(定义、原因、实现方式、示例等)
  3. 致谢页——感谢协作者、工具、来源
  4. 感谢页——收尾页,包含链接和补充主题列表
  5. 补充内容——细节幻灯片、FAQ、参考材料(从主幻灯片和感谢页链接跳转)
进度条会用细竖线在视觉上分隔主内容和补充内容。

Step 4: Add SVG diagrams

步骤4:添加SVG图表

Read references/svg-diagrams.md for complete patterns:
  • Flowcharts — decision trees, process flows with boxes, diamonds, and arrows
  • Architecture diagrams — layered stacks, connected services
  • Pipeline diagrams — data flowing through processing stages
  • Comparison diagrams — side-by-side feature or option comparisons
All diagram SVG elements use the
.diagram-*
CSS classes defined in the base layout, so they automatically match the active theme.
Arrowhead markers — every SVG that uses arrows needs this
<defs>
block:
html
<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>
Use
marker-end="url(#arrow)"
on
.diagram-edge
lines and
marker-end="url(#arrow-accent)"
on
.diagram-edge-accent
lines.
阅读references/svg-diagrams.md获取完整的图表模板:
  • 流程图——决策树、流程步骤,包含方框、菱形、箭头
  • 架构图——分层栈、关联服务
  • 流水线图——数据流经处理阶段的流程
  • 对比图——功能或选项的并排对比
所有SVG图表元素都使用基础布局中定义的
.diagram-*
CSS类,因此会自动适配当前主题。
箭头标记——所有使用箭头的SVG都需要添加此
<defs>
块:
html
<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)"

Step 5: Add SVG animations where appropriate

步骤5:按需添加SVG动画

Read references/svg-animations.md for complete patterns and guidance on when each animation type is appropriate.
Three built-in animation classes (defined in the base CSS):
ClassEffectTrigger
.anim-fade
Fade up into viewSlide becomes active
.anim-draw
SVG path draws itself (stroke-dashoffset)Slide becomes active
.anim-pulse
Pulsing glow on an SVG elementSlide becomes active
Stagger timing with inline
--delay
:
html
<p class="anim-fade" style="--delay:0.2s">Appears first</p>
<p class="anim-fade" style="--delay:0.5s">Appears second</p>
For SVG path drawing, set
--len
to the path's total length:
html
<path class="diagram-edge anim-draw" style="--len:350"
      d="M 50,100 C 150,50 250,150 350,100" />
When to use animations (summary — see reference file for full guidance):
  • Path drawing: showing connections being established, network topology, building a diagram
  • Sequential fade-in: step-by-step concept building, lists that tell a story
  • Pulse/glow: highlighting the current step in a process, drawing attention to a key node
  • Do NOT animate purely decorative elements, every bullet point, or content that the audience needs to read immediately
阅读references/svg-animations.md获取完整模板,以及各类动画适用场景的指导。
三种内置动画类(在基础CSS中定义):
类名效果触发时机
.anim-fade
向上淡入显示幻灯片激活时
.anim-draw
SVG路径自行绘制(stroke-dashoffset实现)幻灯片激活时
.anim-pulse
SVG元素脉冲发光效果幻灯片激活时
使用内联
--delay
设置错开的动画时间:
html
<p class="anim-fade" style="--delay:0.2s">Appears first</p>
<p class="anim-fade" style="--delay:0.5s">Appears second</p>
对于SVG路径绘制,设置
--len
为路径的总长度:
html
<path class="diagram-edge anim-draw" style="--len:350"
      d="M 50,100 C 150,50 250,150 350,100" />
动画适用场景(摘要——完整指导参考参考文件):
  • 路径绘制:展示连接建立过程、网络拓扑、逐步构建图表
  • 顺序淡入:分步讲解概念、按叙事逻辑展示的列表
  • 脉冲/发光:高亮流程中的当前步骤、吸引用户关注关键节点
  • 不要为纯装饰元素、每个要点、或受众需要立即阅读的内容添加动画

Design guidelines

设计指南

  1. One idea per slide — avoid cramming. More slides is fine.
  2. Large text — if a slide has little content, scale up. Use
    font-size: 2.5vw
    or larger.
  3. Diagrams over bullet lists — when explaining a process, architecture, or flow, use an SVG diagram rather than a bulleted list.
  4. Consistent spacing — use
    vh
    /
    vw
    units for padding and margins so spacing scales with the viewport.
  5. Color hierarchy
    --heading
    for titles,
    --fg
    for body,
    --muted
    for secondary text,
    --accent
    for emphasis.
  6. Code blocks — use
    <pre><code>
    with the theme's
    --code-bg
    /
    --code-fg
    . Keep code short (< 15 lines per slide).
  7. Python in slides — when including Python code, make it runnable with
    uv run script.py
    so it is self-contained and does not require external software environments. Use PEP 723 inline script metadata at the top of the script (
    # /// script
    ,
    requires-python
    ,
    dependencies
    ,
    # ///
    ) and state in the slide or speaker notes that the audience runs it with
    uv run script.py
    .
  8. No external assets — everything inline. If an image is needed, use an inline SVG illustration rather than a raster image.
  9. Fill the slide — avoid layouts where content clusters in one corner and the rest is empty space. Use two-column grids (
    grid-template-columns: 1fr 1fr
    ) to balance text and diagrams side by side, with
    align-items:center
    to vertically center both columns.
  10. Taglines over bullets for features — when spotlighting a feature or concept, replace bullet lists with a short tagline (2–3 short lines broken with
    <br>
    ) and keyword pills beneath. This is punchier and more scannable.
  11. Make SVGs the hero — when a slide has a diagram, give it at least half the slide width. Use generous
    viewBox
    dimensions and fill the space with detail: multiple nodes, labels, phases, wireframe lines, loop-back arrows. A tiny diagram floating in whitespace looks unfinished.
  1. 每页一个核心观点——避免内容堆砌,幻灯片数量多是可以接受的。
  2. 大号文本——如果幻灯片内容少,可以放大字号,使用
    font-size: 2.5vw
    或更大的尺寸。
  3. 优先使用图表而非要点列表——讲解流程、架构或流转时,使用SVG图表代替项目符号列表。
  4. 一致的间距——内边距和外边距使用
    vh
    /
    vw
    单位,确保间距随视口大小缩放。
  5. 颜色层级——标题用
    --heading
    ,正文用
    --fg
    ,次要文本用
    --muted
    ,强调内容用
    --accent
  6. 代码块——使用
    <pre><code>
    标签,适配主题的
    --code-bg
    /
    --code-fg
    。每页代码保持简短(<15行)。
  7. 幻灯片中的Python代码——包含Python代码时,确保可通过**
    uv run script.py
    **直接运行,实现自包含,无需外部软件环境。在脚本顶部添加PEP 723内联脚本元数据(
    # /// script
    requires-python
    dependencies
    # ///
    ),并在幻灯片或演讲备注中说明受众可通过
    uv run script.py
    运行。
  8. 无外部资源——所有内容都内联。如果需要图片,使用内联SVG插图而非栅格图片。
  9. 填满幻灯片空间——避免内容集中在某个角落、其余位置留白的布局。使用双列网格(
    grid-template-columns: 1fr 1fr
    )平衡文本和图表的并排布局,配合
    align-items:center
    实现两列垂直居中。
  10. 功能介绍优先使用标语而非要点——重点介绍功能或概念时,用短标语(2-3行短文本,用
    <br>
    换行)和下方的关键词胶囊代替要点列表,更有冲击力,也更便于快速浏览。
  11. SVG作为核心元素——如果幻灯片包含图表,至少为其分配一半的幻灯片宽度。使用宽松的
    viewBox
    尺寸,用细节填满空间:多个节点、标签、阶段、线框图线条、回环箭头。留白过多的小尺寸图表会显得未完成。