别再用 Markdown 了!用一个 HTML 文件搞定架构图、流程图、交互原型

318 阅读1小时+

别再用 Markdown 了!用一个 HTML 文件搞定架构图、流程图、交互原型

当 AI 输出遇上"纯文本天花板",一个 Skill 让你的产出从黑白文档跃升为可交互的视觉页面。

引子:我们为什么需要 HTML Artifact?

你有没有遇到过这样的场景:让 AI 帮你画一张系统架构图,它给你吐了一屏 Mermaid 代码或者一段文字描述,你看完之后……一脸茫然。或者你想做个方案对比,结果收到一个巨长的 Markdown 表格,滑了三屏都没滑完。

问题的根源在于:很多信息天然就不适合用纯文本表达。架构图需要空间布局,流程图需要箭头连线,方案对比需要并排陈列,状态报告需要进度条和颜色编码。Markdown 在这些场景下就像用锤子拧螺丝——能用,但别扭。

html-artifact 这个 Skill 的思路非常直接:既然浏览器是最强大的渲染引擎,那就生成一个单文件 HTML,所有 CSS 写在 <style> 里,所有 JS 写在 <script> 里,所有 SVG 内联。没有构建步骤、没有 npm、没有框架 CDN。双击就能打开,发邮件就能分享,提交到仓库就能存档。

这篇文章会完整介绍这个 Skill 的设计思路、设计系统、11 种制品类型以及动画架构图的实现细节,内容较长,建议收藏慢读。


Skill 概览

html-artifact 的核心定位是:生成自包含、零依赖的 HTML 文件,用视觉化的交互页面替代纯文本输出。

它覆盖的场景包括但不限于:

  • 空间信息:架构图、流程图、模块依赖图
  • 对比分析:方案并排、设计选项、前后对照
  • 结构化报告:状态更新、事故时间线、PR 评审
  • 交互原型:动画演示、参数调优、点击跳转
  • 自定义编辑 UI:优先级看板、配置编辑器、Feature Flag 管理
  • 演示文稿:会议用的幻灯片
  • 概念解释:带折叠展开和术语表的文档

触发方式也很自然——当你对 AI 说"画个架构图""做个方案对比""生成报告""做个 demo""可视化一下"的时候,它就会启动。甚至你没明确要求 HTML,但输出明显更适合视觉呈现时,它也会自动介入。


核心原则

单文件、零依赖

这是整个 Skill 最重要的约束。每个产出物都是一个 .html 文件,所有样式和脚本内联,不引用任何外部资源。唯一的例外是:当布局极其复杂时,允许引入 Tailwind CDN(<script src="https://cdn.tailwindcss.com">),但大多数场景下推荐手写 CSS。

内容优先,不是装饰优先

颜色、图标、动画都是为了传达信息,不是为了好看。一个有色边框意味着某种语义,一个图标信号代表某个类别,动画展示的是数据流方向。不用占位符,不用 Lorem ipsum,填充的是用户的真实数据。


设计系统

html-artifact 内置了一套完整的设计规范,保证所有产出物风格统一、专业美观。

调色板

基调是暖色、纸质感、专业范:

:root {
  /* 背景 */
  --ivory: #FAF9F5;     /* 页面背景 — 暖白 */
  --paper: #FFFFFF;      /* 卡片/面板背景 */

  /* 主色 */
  --slate: #141413;      /* 主文本、深色 UI 元素 */
  --clay:  #D97757;      /* 强调色、CTA、高亮 */
  --clay-d:#B85C3E;      /* clay 的悬停深色变体 */

  /* 辅助色 */
  --oat:   #E3DACC;      /* 柔和强调、次级边框 */
  --olive: #788C5D;      /* 成功、正面指标 */
  --rust:  #B04A3F;      /* 错误、失败、负面指标 */

  /* 灰阶 */
  --g100:  #F0EEE6;      /* 微弱背景、斑马纹 */
  --g200:  #E6E3DA;      /* 内部边框、分割线 */
  --g300:  #D1CFC5;      /* 卡片边框、输入框边框 */
  --g500:  #87867F;      /* 次要文本、标签、弱化内容 */
  --g700:  #3D3D3A;      /* 正文(比 slate 稍柔和) */
}

语义用色总结:页面背景用 --ivory(暖白),卡片用 --paper(纯白),标题用 --slate(近黑),正文用 --g700,标签和说明文字用 --g500,主要强调色是 --clay(陶土色),成功/正面用 --olive(橄榄绿),错误/负面用 --rust(铁锈红)。

在架构图场景中,每个功能角色还有专属配色:

Cyan    #0891B2  — 入口、客户端、路由
Purple  #7C3AED  — LLM、AI、推理
Pink    #BE185D  — 调度、编排
Green   #15803D  — 服务端执行、工具
Orange  #D97706  — 沙箱、隔离执行
Teal    #0E7490  — 用户/客户端节点

节点背景保持白色,角色颜色体现在图标边框、图标描边色、激活态描边和粒子动画的填充色上。

字体栈

全部使用系统字体,不加载任何外部字体:

:root {
  --serif: ui-serif, Georgia, "Times New Roman", Times, serif;
  --sans:  system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono:  ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
}

排版规则如下:

  • 页面标题 (h1):--serif,34-38px,font-weight: 500,letter-spacing: -0.018em
  • 章节标题 (h2):--serif,27px,font-weight: 500
  • 卡片标题:--serif,19px
  • 正文:--sans,14.5px,line-height: 1.55
  • 描述文字:--sans,13.5px,color: var(--g700)
  • 眉标 / 类别标签:--mono,11-12px,letter-spacing: 0.08-0.12em,全大写,color: var(--g500)
  • 代码 / 数据 / 文件路径:--mono,11-13px
  • Badge / Tag:--mono,10-11px

其中"眉标"(eyebrow)是一个高频复用的模式——标题上方的小号大写标签:

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--g500);
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.eyebrow::before {
  content: "";
  width: 24px;
  height: 1.5px;
  background: var(--clay);
}

布局与间距

页面整体布局:

body {
  margin: 0;
  background: var(--ivory);
  color: var(--slate);
  font-family: var(--sans);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 32px 140px;
}

间距遵循一套统一的数值体系:4px(紧凑间距)→ 8px(小间距)→ 12-14px(组件内边距)→ 20px(卡片内边距/网格间距)→ 32px(页面水平内边距)→ 48px(小节间距)→ 72px(大节上边距)→ 80px(头部上边距)。

组件模式

卡片——最核心的容器组件:

.card {
  background: var(--paper);
  border: 1.5px solid var(--g300);
  border-radius: 14px;
  overflow: hidden;
  transition: transform 150ms ease, box-shadow 150ms ease, border-color 150ms ease;
}
.card:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(20, 20, 19, 0.10);
  border-color: var(--slate);
}

Badge / 标签:

.badge {
  font-family: var(--mono);
  font-size: 10.5px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--g300);
  background: var(--paper);
  color: var(--g500);
}
.badge.accent {
  border-color: var(--oat);
  background: #FFF7ED;
  color: var(--clay-d);
}

Tab 标签页、进度条、侧边面板等组件也都有标准化的实现,此处不再逐一展开。

响应式规则

内容最大宽度 1120px 居中。920px 以下双栏布局折叠为单栏;768px 以下对比网格折叠为单栏;640px 以下移除缩进、缩减间距。SVG 图表使用 viewBox + preserveAspectRatio="xMidYMid meet" + 百分比宽度容器,不使用固定像素宽度。


11 种制品类型

html-artifact 定义了 11 种标准制品类型,覆盖了日常开发中最常见的可视化需求。下面逐一介绍。

1. Side-by-Side Explorer(并排比较器)

适用场景:对比 2-4 种方案、设计或实现。

结构上用 CSS Grid 等宽列排列,每个选项一张卡片,包含选项编号 Badge、方案名称、描述文字、代码示例(如有)、优缺点列表。推荐方案用 border-color: var(--clay) 高亮边框标识,优点用 --olive,缺点用 --rust。底部附一张汇总表,沿关键维度横向比较所有方案。

2. Annotated Diff(带标注的代码差异)

适用场景:代码变更展示、PR Review。

采用主面板 + 侧边栏布局。主面板按文件分段展示差异行(添加行绿色、删除行红色、上下文行灰色),行内可插入标注块。标注有四级严重度:Critical 用 --rust 底色加粗边框,Warning 用 --clay 底色,Suggestion 用 --olive 底色,Info 用 --g100 底色。侧边栏展示变更统计和文件快速跳转导航。

3. Interactive Flowchart(交互流程图)

适用场景:部署流水线、决策树、业务流程。

基于 SVG,支持点击节点弹出详情侧面板。节点类型包括:方形(流程节点,圆角矩形)、菱形(决策节点,四点 path)、药丸形(终止节点,rx=半高)、成功态(绿色填充 + 绿色描边)、失败态(红色填充 + 红色描边)。连线类型:默认实线灰色带箭头,通过分支绿色实线,失败分支红色虚线。底部始终包含图例。

4. Animated Architecture Diagram(动画架构图)

这是最复杂也是最有价值的制品类型,后面会单独详细讲解。

5. Arrow-Key Slide Deck(键盘翻页幻灯片)

适用场景:快速会议演示。

每个 <section> 是一张幻灯片,默认隐藏,.active 类控制显示。左右/上下方向键翻页,右下角显示页码。通常 5-10 页,每页只讲一个要点,使用大号衬线标题,尽量少的文字,需要图解时内联 SVG。整个翻页逻辑只有约 20 行 JS。

6. Structured Report(结构化报告)

适用场景:周报、项目进度、仪表盘。

顶部是眉标+日期,接着 3-4 张 KPI 卡片(数值 + 标签 + 变化指标),然后按"已完成""进行中""风险/阻塞"分段展示,进行中的用进度条,风险项用红色标记。可选内嵌 SVG 小型图表。

7. Incident Timeline(事故时间线)

适用场景:事故复盘、Post-mortem。

顶部是严重度横幅(P1/P2/P3 配色),接着 TL;DR 摘要框,主体是纵向时间线——左侧时间戳、中间状态圆点、右侧事件详情和日志摘录(等宽字体块)。底部是影响指标和行动项清单。

8. Collapsible Explainer(可折叠解释器)

适用场景:技术概念解释、功能文档。

顶部 TL;DR 摘要,主体用 <details>/<summary> 做可折叠章节,代码示例用 Tab 切换不同语言/配置,侧边栏或底部放术语表,需要图解时内联 SVG。

9. Drag-and-Drop Board(拖拽看板)

适用场景:任务排优先级、Bug 分拣、看板式管理。

多列布局,卡片可在列间拖拽,用原生 HTML5 Drag and Drop API 实现。关键设计:始终包含一个"导出"按钮,点击后将当前排列输出为 Markdown 或 JSON,方便粘贴回对话或提交。

10. Config Editor(配置编辑器)

适用场景:Feature Flag 管理、参数调优。

开关按类别分组排列,有依赖警告(如 Flag A 关闭时 Flag B 不可用),"复制差异"按钮只导出修改过的值,大量配置项时支持搜索过滤。

11. Module Map(模块地图)

适用场景:代码库导览、模块关系展示。

SVG 方框加箭头展示模块间依赖关系(箭头方向是 import 方向),热路径用强调色高亮,入口模块用特殊 Badge 标记。点击模块弹出侧面板展示其导出项、关键函数和文件列表。


动画架构图:最硬核的部分

动画架构图是整个 Skill 中技术含量最高的制品类型。它能把一个系统的请求流转过程用动画粒子可视化出来,让人一眼就能理解数据是怎么在各个服务之间流动的。

SVG 分层架构

SVG 的绘制顺序就是层叠顺序,后绘制的在上面。html-artifact 要求严格按以下 6 层从底到顶排列:

Layer 0: <defs>         — 图案、箭头标记、滤镜定义
Layer 1: <rect>         — 纯色背景填充
Layer 2: <rect>         — 网格图案叠加
Layer 3: <g>            — 区域边界矩形(虚线轮廓,逻辑分组)
Layer 4: <g>            — 连接路径(节点间的线条)
Layer 5: <g>            — 节点组(rect + foreignObject)
Layer 6: <g id="particles"> — 动画粒子(JS 管理)

核心原则:连线在节点下方(这样节点视觉上"坐在"连线上面),粒子在最上层(移动的小圆点始终可见)。搞反这个顺序是最常见的视觉 bug。

foreignObject:最关键的技术

这是整个架构图质量的决定性技术。绝不使用 SVG <text> 渲染节点内的多行内容,而是用 foreignObject 在 SVG 坐标系中嵌入真正的 HTML:

<g>
  <!-- 可点击/可设样式的矩形 -->
  <rect data-node-id="my-node"
        x="220" y="320" width="170" height="88" rx="12"
        fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>

  <!-- HTML 内容叠加层,位置和尺寸完全一致 -->
  <foreignObject x="220" y="320" width="170" height="88">
    <div xmlns="http://www.w3.org/1999/xhtml"
         style="width:100%; height:100%; display:flex; align-items:center;
                gap:10px; padding:8px 12px; font-family:system-ui,sans-serif;">
      <!-- 图标容器 -->
      <div style="flex-shrink:0; width:36px; height:36px; border-radius:8px;
                  background:#FFF7ED; border:1px solid #FED7AA;
                  display:flex; align-items:center; justify-content:center;">
        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"
             viewBox="0 0 24 24" fill="none" stroke="#0E7490"
             stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <!-- 图标路径 -->
        </svg>
      </div>
      <!-- 文字内容 -->
      <div style="min-width:0; flex:1;">
        <div style="font-size:13px; font-weight:600; color:#1F1B16;
                    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
          Node Title
        </div>
        <div style="font-size:11px; color:#6B6258; margin-top:2px;
                    display:-webkit-box; -webkit-line-clamp:2;
                    -webkit-box-orient:vertical; overflow:hidden;">
          描述文字 · 更多细节
        </div>
      </div>
    </div>
  </foreignObject>
</g>

为什么这很重要?SVG <text> 做不了:自动换行、文字截断、Flexbox 布局、富文本格式。foreignObject 让你在 SVG 坐标系内享受完整的 HTML/CSS 能力。rect 提供边框、填充和点击目标,foreignObject 提供内容渲染。两者共享完全相同的 x/y/width/height。

多实例堆叠

当一个节点代表多个实例(比如一组服务器集群)时,在主卡片后面堆叠 2-3 个偏移矩形:

<g>
  <!-- 影子卡片 2(最后面) -->
  <rect x="230" y="310" width="170" height="88" rx="12"
        fill="#FFFFFF" opacity="0.85" stroke="#E5E0D8" stroke-width="1"/>
  <!-- 影子卡片 1 -->
  <rect x="225" y="315" width="170" height="88" rx="12"
        fill="#FFFFFF" opacity="0.67" stroke="#E5E0D8" stroke-width="1"/>
  <!-- 主卡片(最前面) -->
  <rect data-node-id="cluster" x="220" y="320" width="170" height="88" rx="12"
        fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>
  <foreignObject x="220" y="320" width="170" height="88">
    <!-- HTML 内容 -->
  </foreignObject>
</g>

每张影子卡片相对前一张偏移 (+5, -5) 像素,透明度递减:0.85、0.67。如果节点处于"休眠/未创建"状态,使用弱化配色:fill="#F4EFE7" stroke="#D6D0C4",文字颜色改为 #A8A29E。

连接路径

连线端点对齐到节点边缘而非中心,这是常见的计算错误来源:

右边缘:(node.x + node.width, node.y + node.height/2)
左边缘:(node.x, node.y + node.height/2)
上边缘:(node.x + node.width/2, node.y)
下边缘:(node.x + node.width/2, node.y + node.height)

线型语义:实线 = 同步/直接调用,虚线 (stroke-dasharray: 6 5) = 异步/事件驱动,红色虚线 = 错误/失败路径。

虚线流动动画

让虚线看起来在"流动"的技巧,纯 SVG 实现,零 JS:

<path d="M 830 350 C 880 330, 893 316, 923 256"
      fill="none" stroke="#A8A29E" stroke-opacity="0.85"
      stroke-width="1.4" stroke-dasharray="6 5" stroke-linecap="round">
  <animate attributeName="stroke-dashoffset"
           values="22;0" dur="1.2s" repeatCount="indefinite"/>
</path>

values="22;0" 中的 22 是 dash+gap(6+5=11)的两倍,保证循环平滑。dur="1.2s" 是合适的默认速度。

数据粒子动画

这是架构图最酷炫的部分:用 JS 驱动小圆点沿路径移动,模拟请求/数据在系统中的流转。

每个粒子由 3-4 个 SVG 元素组成:光晕圆(大半径低透明度)、实心点(描边白色)、拖尾点(小半径更低透明度)、可选的标签(用 rect+text 组合)。

移动逻辑借助 SVGPathElement.getTotalLength() 和 getPointAtLength() 实现:

function animateParticle(pathId, color, label, duration) {
  const path = document.getElementById(pathId);
  const totalLen = path.getTotalLength();
  const particleGroup = document.getElementById('particles');

  const halo = createCircle(0, 0, 10, color, 0.06);
  const dot = createCircle(0, 0, 5.5, color, 1);
  dot.setAttribute('stroke', '#FFF');
  dot.setAttribute('stroke-width', '1');
  particleGroup.append(halo, dot);

  let start = null;
  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = Math.min((timestamp - start) / duration, 1);

    // 缓入缓出
    const t = progress < 0.5
      ? 2 * progress * progress
      : 1 - Math.pow(-2 * progress + 2, 2) / 2;

    const point = path.getPointAtLength(t * totalLen);
    halo.setAttribute('cx', point.x);
    halo.setAttribute('cy', point.y);
    dot.setAttribute('cx', point.x);
    dot.setAttribute('cy', point.y);

    // 起始淡入、结束淡出
    const opacity = progress < 0.1 ? progress / 0.1
                  : progress > 0.9 ? (1 - progress) / 0.1 : 1;
    dot.setAttribute('opacity', opacity);
    halo.setAttribute('opacity', opacity * 0.06);

    if (progress < 1) {
      requestAnimationFrame(step);
    } else {
      halo.remove();
      dot.remove();
    }
  }
  requestAnimationFrame(step);
}

粒子颜色按数据类型区分:请求/入口用 Cyan #0891B2,消息/上下文用 Purple #7C3AED,工具调用用 Pink #BE185D,服务端结果用 Green #15803D,沙箱结果用 Orange #D97706。

播放控制

对于多阶段流程的动画图,会提供"重置""下一阶段""播放"按钮,把整个数据流转过程拆成若干步骤,逐步演示。


常见错误清单

使用这个 Skill 或者自己按此思路制作 HTML Artifact 时,以下是最容易踩的坑:

  1. 用 SVG <text> 写多行内容 → 应该用 foreignObject + HTML div
  2. 连线端点对准节点中心 → 应该对准节点边缘
  3. 层级顺序搞反 → 路径在节点下方,粒子在节点上方
  4. 引入外部依赖 → 除了 Tailwind 特殊情况外不引 CDN
  5. 使用占位内容 → 填充用户的真实数据
  6. 全图只有一种颜色 → 每种角色/类型分配不同强调色
  7. 没有图例 → 有颜色/线型语义的图必须附图例
  8. 没有交互 → 至少加点击高亮或悬停效果

完整示例:最小动画架构图

下面是一个可以直接运行的完整 HTML 文件,展示两个节点之间的数据流动:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Architecture Demo</title>
  <style>
    :root {
      --ivory:#FAF9F5; --paper:#FFFFFF; --slate:#141413;
      --clay:#D97757; --g300:#D1CFC5; --g500:#87867F; --g700:#3D3D3A;
      --serif: ui-serif, Georgia, serif;
      --sans: system-ui, sans-serif;
      --mono: ui-monospace, "SF Mono", Menlo, monospace;
    }
    * { box-sizing: border-box; margin: 0; }
    body { background: var(--ivory); color: var(--slate);
           font-family: var(--sans); padding: 40px 24px; }
    .wrap { max-width: 1000px; margin: 0 auto; }
    h1 { font-family: var(--serif); font-size: 28px; font-weight: 500;
         margin-bottom: 20px; }
    .canvas { border: 1.5px solid var(--g300); border-radius: 14px;
              background: var(--paper); overflow: hidden; }
    button { font-family: var(--mono); font-size: 12px; padding: 8px 16px;
             background: var(--paper); border: 1.5px solid var(--g300);
             border-radius: 8px; cursor: pointer; }
    button:hover { border-color: var(--slate); }
    button.primary { background: var(--clay); color: white;
                     border-color: var(--clay); }
  </style>
</head>
<body>
  <div class="wrap">
    <h1>System Architecture</h1>
    <div style="display:flex; gap:8px; margin-bottom:16px;">
      <button onclick="reset()">重置</button>
      <button class="primary" onclick="runDemo()">▶ 播放</button>
    </div>
    <div class="canvas">
      <div style="aspect-ratio:800/400; width:100%;">
        <svg viewBox="0 0 800 400" style="width:100%;height:100%;"
             preserveAspectRatio="xMidYMid meet">
          <defs>
            <pattern id="grid" width="40" height="40"
                     patternUnits="userSpaceOnUse">
              <path d="M 40 0 L 0 0 0 40" fill="none"
                    stroke="#E5E0D8" stroke-opacity="0.5" stroke-width="1"/>
            </pattern>
          </defs>
          <rect width="800" height="400" fill="#FAF7F2"/>
          <rect width="800" height="400" fill="url(#grid)"/>

          <!-- 连接路径 -->
          <path id="p1" d="M 270 200 C 350 200, 450 200, 530 200"
                fill="none" stroke="#A8A29E" stroke-width="1.4"
                stroke-dasharray="6 5" stroke-linecap="round">
            <animate attributeName="stroke-dashoffset"
                     values="22;0" dur="1.2s" repeatCount="indefinite"/>
          </path>

          <!-- 节点 A -->
          <g>
            <rect data-node-id="a" x="100" y="156" width="170" height="88"
                  rx="12" fill="#FFF" stroke="#E5E0D8" stroke-width="1"/>
            <foreignObject x="100" y="156" width="170" height="88">
              <div xmlns="http://www.w3.org/1999/xhtml"
                   style="width:100%;height:100%;display:flex;align-items:center;
                          gap:10px;padding:8px 12px;font-family:system-ui;">
                <div style="font-size:13px;font-weight:600;color:#1F1B16;">
                  Service A</div>
              </div>
            </foreignObject>
          </g>

          <!-- 节点 B -->
          <g>
            <rect data-node-id="b" x="530" y="156" width="170" height="88"
                  rx="12" fill="#FFF" stroke="#E5E0D8" stroke-width="1"/>
            <foreignObject x="530" y="156" width="170" height="88">
              <div xmlns="http://www.w3.org/1999/xhtml"
                   style="width:100%;height:100%;display:flex;align-items:center;
                          gap:10px;padding:8px 12px;font-family:system-ui;">
                <div style="font-size:13px;font-weight:600;color:#1F1B16;">
                  Service B</div>
              </div>
            </foreignObject>
          </g>

          <!-- 粒子层 -->
          <g id="particles"></g>
        </svg>
      </div>
    </div>
  </div>
  <script>
  function createCircle(cx,cy,r,fill,opacity){
    const c=document.createElementNS('http://www.w3.org/2000/svg','circle');
    c.setAttribute('cx',cx);c.setAttribute('cy',cy);
    c.setAttribute('r',r);c.setAttribute('fill',fill);
    c.setAttribute('opacity',opacity);return c;
  }
  function animateParticle(pathId,color,dur){
    const path=document.getElementById(pathId);
    const len=path.getTotalLength();
    const g=document.getElementById('particles');
    const halo=createCircle(0,0,10,color,0.06);
    const dot=createCircle(0,0,5.5,color,1);
    dot.setAttribute('stroke','#FFF');dot.setAttribute('stroke-width','1');
    g.append(halo,dot);
    let start=null;
    function step(ts){
      if(!start)start=ts;
      const p=Math.min((ts-start)/dur,1);
      const t=p<.5?2*p*p:1-Math.pow(-2*p+2,2)/2;
      const pt=path.getPointAtLength(t*len);
      halo.setAttribute('cx',pt.x);halo.setAttribute('cy',pt.y);
      dot.setAttribute('cx',pt.x);dot.setAttribute('cy',pt.y);
      const op=p<.1?p/.1:p>.9?(1-p)/.1:1;
      dot.setAttribute('opacity',op);halo.setAttribute('opacity',op*.06);
      if(p<1)requestAnimationFrame(step);
      else{halo.remove();dot.remove();}
    }
    requestAnimationFrame(step);
  }
  function activateNode(id,color){
    const n=document.querySelector(`[data-node-id="${id}"]`);
    if(n){n.setAttribute('stroke',color);n.setAttribute('stroke-width','2');}
  }
  function reset(){
    document.querySelectorAll('[data-node-id]').forEach(n=>{
      n.setAttribute('stroke','#E5E0D8');n.setAttribute('stroke-width','1');
    });
    document.getElementById('particles').innerHTML='';
  }
  function runDemo(){
    reset();
    activateNode('a','#0891B2');
    setTimeout(()=>animateParticle('p1','#0891B2',1500),300);
    setTimeout(()=>activateNode('b','#15803D'),1800);
  }
  </script>
</body>
</html>

把这段代码保存为 .html 文件,双击打开,点"播放"按钮,你就能看到一个蓝色小圆点从 Service A 沿着虚线流向 Service B,同时两个节点依次高亮。这就是"unreasonable effectiveness of HTML"的缩影。


附录:Skill 完整源码

下面是 html-artifact Skill 的全部 4 个文件原文,想自己搭建或二次开发的同学可以直接复制使用。

文件 1:SKILL.md(主文件)

---
name: html-artifact
description: >-
  Generate rich, interactive single-file HTML artifacts instead of plain markdown.
  Covers all scenarios where visual output beats text: architecture diagrams with
  animated data flow, code review with annotated diffs, side-by-side design
  explorations, clickable prototypes, slide decks, status/incident reports,
  flowcharts, feature explainers, and custom editing UIs (triage boards, config
  editors, prompt tuners). Use this skill whenever the user asks to "画个图",
  "做个流程图", "画架构图", "生成报告", "做个对比", "方案对比", "做个演示",
  "做个 demo", "可视化", "画个示意图", "做个 slide", "交互原型", or whenever the
  output would clearly benefit from layout, color, diagrams, or interactivity
  rather than flat text — even if the user doesn't explicitly ask for HTML.
  Also trigger when the user shares a link to a page and asks to create something
  similar, or when comparing multiple approaches/designs side by side.
---

# HTML Artifact Generator

Generate self-contained, zero-dependency HTML files that replace walls of markdown
with visually rich, interactive artifacts people actually read.

Based on the "unreasonable effectiveness of HTML" methodology: every artifact is a
single `.html` file — no build step, no npm, no framework CDN. Just open in a browser.

## When to Use

Trigger this skill when the output would be **more useful as a visual page** than as
plain text. Key signals:

- Spatial information: diagrams, flowcharts, architecture maps, module graphs
- Comparison: side-by-side approaches, design options, before/after
- Structured reports: status updates, incident timelines, PR writeups
- Interactive prototypes: animation, click-through flows, parameter tuning
- Custom UIs: triage boards, config editors, feature flag managers
- Presentations: slide decks for meetings
- Explanations: concept explainers with collapsible sections and glossaries

## Core Principles

### 1. Single File, Zero Dependencies

Every artifact is ONE `.html` file. All CSS is in `<style>`, all JS is in `<script>`,
all SVG is inline. No CDN links, no external fonts, no build step. The user can
double-click the file to open it, email it, or commit it to a repo.

Exception: Tailwind CDN (`<script src="https://cdn.tailwindcss.com">`) is acceptable
ONLY for complex layout-heavy artifacts where writing all utility classes by hand
would be impractical. Prefer hand-written CSS for most artifacts.

### 2. Design System

Read `references/design-system.md` for the complete design token reference. Here's the
quick summary:

**Color palette** — warm, paper-like, professional:
```css
:root {
  --ivory: #FAF9F5;   /* page background */
  --paper: #FFFFFF;   /* card/panel background */
  --slate: #141413;   /* primary text */
  --clay:  #D97757;   /* accent / highlight */
  --oat:   #E3DACC;   /* soft accent / borders */
  --olive: #788C5D;   /* success / positive */
  --rust:  #B04A3F;   /* error / negative */
  --g100:  #F0EEE6;   --g200: #E6E3DA;
  --g300:  #D1CFC5;   --g500: #87867F;   --g700: #3D3D3A;
}
```

**Typography** — three font stacks, no external fonts:
```css
:root {
  --serif: ui-serif, Georgia, "Times New Roman", serif;
  --sans:  system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono:  ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}
```

- Headings: `--serif`, titles around 34px, subtle negative letter-spacing
- Body: `--sans`, 14-15px, line-height 1.55
- Labels/code/data: `--mono`, 11-12px, letter-spacing 0.08em, uppercase for eyebrows

**Card pattern**:
```css
.card {
  background: var(--paper);
  border: 1.5px solid var(--g300);
  border-radius: 14px;
  padding: 20px;
}
```

### 3. Content-First, Not Decoration-First

The artifact should communicate the actual information. Don't generate placeholder
Lorem ipsum content — fill in real data from the user's context. Decoration serves
communication: a colored border means something, an icon signals a category, animation
shows data flow direction.

## Artifact Types

Read `references/artifact-types.md` for detailed templates and patterns for each type.
Here's the map of what to reach for:

| User Intent | Artifact Type | Key Technique |
|---|---|---|
| Architecture / system diagram | **Animated Flow Diagram** | SVG + foreignObject + path animation |
| Compare approaches / designs | **Side-by-Side Explorer** | CSS Grid columns, tabbed panels |
| Code review / PR | **Annotated Diff** | Two-column layout, severity badges |
| Module / codebase map | **Module Graph** | SVG boxes + arrows, click-to-detail |
| Flowchart / pipeline | **Interactive Flowchart** | SVG nodes + edges, click side panel |
| Slide deck / presentation | **Arrow-Key Deck** | `<section>` tags + 20 lines of JS |
| Status report / weekly | **Structured Report** | Progress bars, small inline charts |
| Incident / post-mortem | **Timeline Report** | Vertical timeline, log excerpts |
| Feature explainer | **Collapsible Explainer** | Details/summary, tabbed code, glossary |
| Concept explainer | **Interactive Explainer** | Live widget + text + glossary |
| Triage / prioritization | **Drag-and-Drop Board** | Columns + drag + export button |
| Config / feature flags | **Config Editor** | Toggle switches + dependency warnings |
| Prompt tuning | **Live Preview Editor** | Split pane: editable left, preview right |

### Architecture / Flow Diagrams (the most complex type)

This is the highest-value artifact type and has the most specific techniques. Read
`references/animation-patterns.md` for the full reference. Key patterns:

**SVG layering order** (bottom to top):
1. `<defs>` — patterns, markers, filters
2. Background rect + grid pattern overlay
3. Region boundaries (dashed rounded rects for logical groups)
4. Connection paths (lines/curves between nodes)
5. Nodes (rect + foreignObject with HTML content)
6. Animation particle layer (JS-managed circles)

**Node rendering — the foreignObject technique**:
```svg
<rect x="220" y="320" width="170" height="88" rx="12"
      fill="#FFFFFF" stroke="#E5E0D8"/>
<foreignObject x="220" y="320" width="170" height="88">
  <div style="display:flex; align-items:center; gap:10px; padding:8px 12px;
              width:100%; height:100%;">
    <div style="width:36px; height:36px; border-radius:8px;
                background:#FFF7ED; border:1px solid #FED7AA;
                display:flex; align-items:center; justify-content:center;">
      <!-- inline SVG icon here -->
    </div>
    <div>
      <div style="font-size:13px; font-weight:600;">Node Title</div>
      <div style="font-size:11px; color:#6B6258;">Description text</div>
    </div>
  </div>
</foreignObject>
```

This is THE critical technique — never use SVG `<text>` for multi-line node content.
`foreignObject` gives you full HTML/CSS layout: flexbox, text wrapping, truncation,
rich formatting. This single decision is what makes these diagrams look professional.

**Connection paths** — use cubic Bézier curves:
```svg
<!-- Solid = synchronous -->
<path d="M 390 364 C 410 364, 410 364, 430 364"
      stroke="#A8A29E" stroke-width="1.4" fill="none"/>

<!-- Dashed = asynchronous, with flow animation -->
<path d="M 830 350 C 880 330, 893 316, 923 256"
      stroke="#A8A29E" stroke-dasharray="6 5" fill="none">
  <animate attributeName="stroke-dashoffset"
           values="22;0" dur="1.2s" repeatCount="indefinite"/>
</path>
```

Path endpoints must align with node edges: start at `(node.x + node.width, node.y + node.height/2)`,
end at `(nextNode.x, nextNode.y + nextNode.height/2)`. Adjust control points for curves.

**Multi-instance stacking** — show multiple instances with offset rects:
```svg
<rect x="230" y="310" width="170" height="88" rx="12"
      fill="#FFF" opacity="0.85" stroke="#E5E0D8"/>
<rect x="225" y="315" width="170" height="88" rx="12"
      fill="#FFF" opacity="0.67" stroke="#E5E0D8"/>
<rect data-node-id="main" x="220" y="320" width="170" height="88" rx="12"
      fill="#FFF" stroke="#E5E0D8"/>
<!-- then foreignObject at 220,320 -->
```
Each shadow card offsets by (5, -5) px with decreasing opacity.

## Output Workflow

1. **Clarify intent**: Identify which artifact type fits. If ambiguous, suggest 2-3
   options and let the user pick.
2. **Gather content**: Ask for the actual data/content to fill in. Don't use
   placeholder data.
3. **Generate the HTML**: Write the complete single-file HTML. Use the design system
   tokens. Follow the patterns from `references/artifact-types.md`.
4. **Save and open**: Write to the workspace, then open in browser:
   ```bash
   open <path-to-file.html>
   ```
5. **Iterate**: Take a screenshot, review, and fix issues. Common problems:
   - SVG text not rendering (use foreignObject instead)
   - Connection paths misaligned (recalculate from node edge coordinates)
   - Too much whitespace or overflow (adjust viewBox and positions)

## Common Mistakes to Avoid

- **Using SVG `<text>` for multi-line content** → Use `foreignObject` with HTML divs
- **Connecting paths to node centers** → Connect to node edges (x+width for right, x for left)
- **Forgetting the layering order** → Paths UNDER nodes, particles ABOVE nodes
- **External dependencies** → No CDN links (except Tailwind when justified)
- **Placeholder content** → Always use real data from the user's context
- **Monochrome diagrams** → Use semantic colors: each role/type gets a distinct accent
- **No legend** → Always include a legend for diagrams with color/line-style semantics
- **No interactivity** → Add at least click-to-highlight or hover effects where appropriate

## Deployment (Optional)

If the user wants to share the artifact with others, suggest these options:

1. **Direct file sharing** — The HTML file works standalone, just send it
2. **WebStatic** — Use the `webstatic-deploy` skill to publish to internal hosting
3. **NoCode** — For more complex interactive artifacts

文件 2:references/design-system.md(设计系统参考)

# HTML Artifact Design System

Complete design token reference for generating consistent, professional HTML artifacts.

## Table of Contents
- [Color Palette](#color-palette)
- [Typography](#typography)
- [Spacing and Layout](#spacing-and-layout)
- [Component Patterns](#component-patterns)
- [SVG Conventions](#svg-conventions)
- [Responsive Rules](#responsive-rules)

---

## Color Palette

### CSS Custom Properties

```css
:root {
  /* Backgrounds */
  --ivory: #FAF9F5;     /* page background — warm off-white */
  --paper: #FFFFFF;      /* card / panel background */

  /* Primary */
  --slate: #141413;      /* primary text, dark UI elements */
  --clay:  #D97757;      /* accent, CTA, highlights, active borders */
  --clay-d:#B85C3E;      /* darker clay for hover states */

  /* Secondary */
  --oat:   #E3DACC;      /* soft accent, secondary borders, warm neutral */
  --olive: #788C5D;      /* success, positive, "green" indicators */
  --rust:  #B04A3F;      /* error, failure, negative indicators */

  /* Gray scale */
  --g100:  #F0EEE6;      /* subtle backgrounds, zebra stripes */
  --g200:  #E6E3DA;      /* inner borders, separator lines */
  --g300:  #D1CFC5;      /* card borders, input borders */
  --g500:  #87867F;      /* secondary text, labels, muted content */
  --g700:  #3D3D3A;      /* body text (slightly softer than --slate) */
}
```

### Semantic Color Usage

| Purpose | Color | Token |
|---|---|---|
| Page background | warm off-white | `--ivory` |
| Card / panel | white | `--paper` |
| Headings, important text | near-black | `--slate` |
| Body text | dark warm gray | `--g700` |
| Labels, captions, metadata | mid gray | `--g500` |
| Primary accent, CTA | terracotta | `--clay` |
| Soft borders, decorative | warm beige | `--oat` |
| Success, passing, positive | sage green | `--olive` |
| Error, failing, negative | muted red | `--rust` |
| Card borders | light warm gray | `--g300` |

### For Architecture Diagrams: Role Colors

When drawing architecture/flow diagrams, assign each functional role a distinct color:

```
Cyan    #0891B2  — entry points, client-facing, routing
Purple  #7C3AED  — LLM, AI, inference
Pink    #BE185D  — dispatching, orchestration
Green   #15803D  — server-side execution, tools
Orange  #D97706  — sandbox, isolated execution
Teal    #0E7490  — user/client nodes (icon stroke)
```

Node backgrounds stay white (`#FFFFFF`); the role color appears as:
- Icon background border: `border: 1px solid <role-lighter-shade>`
- Icon stroke color
- Active state stroke on the node rect
- Particle fill color for data flowing through that path

---

## Typography

### Font Stacks (system fonts only)

```css
:root {
  --serif: ui-serif, Georgia, "Times New Roman", Times, serif;
  --sans:  system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --mono:  ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
}
```

### Usage Rules

| Element | Font | Size | Weight | Extra |
|---|---|---|---|---|
| Page title (h1) | `--serif` | 34-38px (use clamp) | 500 | `letter-spacing: -0.018em` |
| Section heading (h2) | `--serif` | 27px | 500 | `letter-spacing: -0.012em` |
| Card title | `--serif` | 19px | 500 | |
| Body text | `--sans` | 14.5px | 400 | `line-height: 1.55` |
| Description text | `--sans` | 13.5px | 400 | `line-height: 1.5; color: var(--g700)` |
| Eyebrow / category label | `--mono` | 11-12px | 400 | `letter-spacing: 0.08-0.12em; text-transform: uppercase; color: var(--g500)` |
| Code / data / file paths | `--mono` | 11-13px | 400 | |
| Badge / tag | `--mono` | 10-11px | 400 | |

### Eyebrow Pattern

A recurring pattern — the small uppercase label before a heading:

```css
.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--g500);
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 12px;
}
.eyebrow::before {
  content: "";
  width: 24px;
  height: 1.5px;
  background: var(--clay);
}
```

---

## Spacing and Layout

### Page Layout

```css
body {
  margin: 0;
  background: var(--ivory);
  color: var(--slate);
  font-family: var(--sans);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.wrap {
  max-width: 1120px;    /* content max width */
  margin: 0 auto;
  padding: 0 32px 140px;
}
```

### Spacing Scale

Common spacing values used consistently:
- `4px` — tight gaps (inline badges)
- `8px` — small gaps (between related elements)
- `12-14px` — default component padding, card gaps
- `20px` — card internal padding, grid gaps
- `32px` — page horizontal padding
- `48px` — section spacing (small), grid gaps (large)
- `72px` — section top margin
- `80px` — header top padding

### Grid Patterns

Cards in a grid:
```css
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(316px, 1fr));
  gap: 20px;
}
```

Two-panel layout (main + sidebar):
```css
.layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: 32px;
  align-items: start;
}
@media (max-width: 920px) {
  .layout { grid-template-columns: 1fr; }
}
```

Side-by-side comparison:
```css
.compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}
@media (max-width: 768px) {
  .compare { grid-template-columns: 1fr; }
}
```

---

## Component Patterns

### Card

```css
.card {
  background: var(--paper);
  border: 1.5px solid var(--g300);
  border-radius: 14px;
  overflow: hidden;
  transition: transform 150ms ease, box-shadow 150ms ease, border-color 150ms ease;
}
.card:hover {
  transform: translateY(-3px);
  box-shadow: 0 10px 30px rgba(20, 20, 19, 0.10);
  border-color: var(--slate);
}
```

### Badge / Tag

```css
.badge {
  font-family: var(--mono);
  font-size: 10.5px;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--g300);
  background: var(--paper);
  color: var(--g500);
}
.badge.accent {
  border-color: var(--oat);
  background: #FFF7ED;
  color: var(--clay-d);
}
```

### Side Panel (sticky detail)

```css
aside {
  position: sticky;
  top: 24px;
  border: 1.5px solid var(--g300);
  border-radius: 14px;
  background: var(--paper);
  padding: 20px;
}
```

### Tabs

```html
<div class="tabs">
  <button class="tab active" data-tab="a">Tab A</button>
  <button class="tab" data-tab="b">Tab B</button>
</div>
<div class="tab-panel active" id="panel-a">...</div>
<div class="tab-panel" id="panel-b">...</div>
```

```css
.tabs { display: flex; gap: 4px; border-bottom: 1.5px solid var(--g300); }
.tab {
  font-family: var(--mono); font-size: 12px; padding: 8px 16px;
  background: none; border: none; color: var(--g500); cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1.5px;
}
.tab.active { color: var(--slate); border-bottom-color: var(--clay); }
.tab-panel { display: none; padding: 20px 0; }
.tab-panel.active { display: block; }
```

### Progress Bar

```html
<div style="height:3px; background:var(--g200); border-radius:2px; overflow:hidden;">
  <div style="height:100%; width:42%; background:var(--clay);"></div>
</div>
```

---

## SVG Conventions

### Markers (arrowheads)

```svg
<defs>
  <marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5"
          markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M0,0 L10,5 L0,10 z" fill="#87867F"/>
  </marker>
  <marker id="arrow-accent" viewBox="0 0 10 10" refX="9" refY="5"
          markerWidth="6" markerHeight="6" orient="auto-start-reverse">
    <path d="M0,0 L10,5 L0,10 z" fill="#D97757"/>
  </marker>
</defs>
```

### Grid Pattern

```svg
<defs>
  <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
    <path d="M 40 0 L 0 0 0 40" fill="none"
          stroke="#E5E0D8" stroke-opacity="0.5" stroke-width="1"/>
  </pattern>
</defs>
<rect width="1400" height="760" fill="#FAF7F2"/>
<rect width="1400" height="760" fill="url(#grid)"/>
```

### Node Styling in SVG

Default state:
```svg
<rect rx="12" fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>
```

Active/highlighted state:
```svg
<rect rx="12" fill="#FFFFFF" stroke="#0891B2" stroke-width="2"/>
```

Disabled/dormant state:
```svg
<rect rx="12" fill="#F4EFE7" stroke="#D6D0C4" stroke-width="1"/>
```

---

## Responsive Rules

- Max content width: `1120px` centered
- At `920px`: two-column layouts collapse to single column
- At `768px`: comparison grids collapse to single column
- At `640px`: remove left margin indents, reduce section spacing
- SVG diagrams: use `viewBox` + `preserveAspectRatio="xMidYMid meet"` + percentage width container, never fixed pixel widths
- Wrap SVG in an aspect-ratio container: `<div style="aspect-ratio:1400/760; width:100%;">`

文件 3:references/artifact-types.md(制品类型参考)

# Artifact Types Reference

Detailed patterns and templates for each artifact type. Read the section relevant
to the user's intent.

## Table of Contents
- [1. Side-by-Side Explorer](#1-side-by-side-explorer)
- [2. Annotated Diff / Code Review](#2-annotated-diff--code-review)
- [3. Interactive Flowchart](#3-interactive-flowchart)
- [4. Animated Architecture Diagram](#4-animated-architecture-diagram)
- [5. Arrow-Key Slide Deck](#5-arrow-key-slide-deck)
- [6. Structured Report](#6-structured-report)
- [7. Incident Timeline](#7-incident-timeline)
- [8. Collapsible Explainer](#8-collapsible-explainer)
- [9. Drag-and-Drop Board](#9-drag-and-drop-board)
- [10. Config Editor](#10-config-editor)
- [11. Module Map](#11-module-map)

---

## 1. Side-by-Side Explorer

**Use when**: comparing 2-4 approaches, designs, or implementations

**Structure**:
```html
<div class="compare-grid">
  <!-- one card per option -->
  <div class="option-card">
    <div class="option-header">
      <span class="option-num">A</span>
      <h3>Approach Name</h3>
      <span class="badge">recommended</span>  <!-- optional -->
    </div>
    <div class="option-body">
      <p>Prose description of this approach...</p>
      <pre><code>code sample if relevant</code></pre>
    </div>
    <div class="option-footer">
      <div class="pro">✓ Fast to implement</div>
      <div class="con">✗ Hard to maintain</div>
    </div>
  </div>
</div>
```

**Key patterns**:
- Use CSS Grid with equal-width columns (2-3 columns max)
- Give each option a letter or number badge with a distinct accent color
- The "recommended" option gets a highlighted border (`border-color: var(--clay)`)
- Pros use `--olive`, cons use `--rust`
- Include a summary table at the bottom comparing key dimensions across all options

---

## 2. Annotated Diff / Code Review

**Use when**: showing code changes with explanations, PR review

**Structure**:
```html
<div class="layout">
  <div class="diff-panel">
    <!-- file sections -->
    <div class="file-section">
      <div class="file-header">src/auth/handler.ts</div>
      <div class="diff-lines">
        <div class="line removed"><span class="ln">42</span><code>- old code</code></div>
        <div class="line added"><span class="ln">42</span><code>+ new code</code></div>
        <div class="line context"><span class="ln">43</span><code>  unchanged</code></div>
      </div>
      <!-- inline annotation -->
      <div class="annotation severity-warning">
        <span class="severity-badge">⚠ suggestion</span>
        <p>Consider using a constant here to avoid magic numbers.</p>
      </div>
    </div>
  </div>
  <aside class="summary-panel">
    <h3>Review Summary</h3>
    <div class="stat">Files changed: <strong>4</strong></div>
    <div class="stat">Additions: <strong class="added">+127</strong></div>
    <div class="stat">Deletions: <strong class="removed">-43</strong></div>
    <h4>Jump to</h4>
    <nav><!-- file links --></nav>
  </aside>
</div>
```

**Severity colors**:
- Critical: `--rust` background tint, bold border
- Warning: `--clay` background tint
- Suggestion: `--olive` background tint
- Info: `--g100` background

---

## 3. Interactive Flowchart

**Use when**: deploy pipelines, decision trees, process flows

**Structure**: SVG-based with click-to-detail side panel.

```html
<div class="layout">
  <div class="canvas">
    <svg class="flow" viewBox="0 0 620 920">
      <defs><!-- arrow markers --></defs>
      <!-- edges (draw FIRST so nodes overlap them) -->
      <path class="edge" d="M310,56 L310,92"/>
      <path class="edge yes" d="M310,326 L310,368"/>
      <path class="edge no" d="M268,294 C190,294 150,294 150,212"/>
      <!-- nodes -->
      <g class="node" data-k="step-id">
        <rect x="210" y="92" width="200" height="48" rx="8"/>
        <text x="310" y="112" text-anchor="middle">Step Name</text>
        <text class="sub" x="310" y="128" text-anchor="middle">~2 min</text>
      </g>
      <!-- decision diamond -->
      <g class="node gate" data-k="decision-id">
        <path d="M310,262 L352,294 L310,326 L268,294 Z"/>
        <text x="310" y="298" text-anchor="middle">pass?</text>
      </g>
    </svg>
  </div>
  <aside id="panel">
    <div class="hint">Click a step →</div>
    <h3 id="p-title"></h3>
    <p id="p-body"></p>
    <pre id="p-code"></pre>
  </aside>
</div>
```

**JS for click interaction**:
```js
const DETAIL = {
  "step-id": { title: "...", meta: "...", body: "...", code: "..." }
};
document.querySelectorAll(".node").forEach(n => {
  n.addEventListener("click", () => {
    document.querySelectorAll(".node").forEach(x => x.classList.remove("active"));
    n.classList.add("active");
    const d = DETAIL[n.dataset.k];
    document.getElementById("p-title").textContent = d.title;
    document.getElementById("p-body").innerHTML = d.body;
    document.getElementById("p-code").textContent = d.code;
  });
});
```

**Node types**:
- Process: `<rect>` with rounded corners
- Decision: diamond `<path>` (4 points)
- Terminal: pill shape (`rx` = half height)
- Success: `fill: rgba(120,140,93,0.12); stroke: var(--olive)`
- Failure: `fill: rgba(176,74,63,0.10); stroke: var(--rust)`

**Edge types**:
- Default: solid `stroke: var(--g500)` with arrow marker
- Yes/pass: solid `stroke: var(--olive)` with olive arrow
- No/fail: dashed `stroke: var(--rust)` with rust arrow

**Legend**: Always include at bottom showing node types and edge types.

---

## 4. Animated Architecture Diagram

**Use when**: showing system architecture with data flow, service interactions

This is the most complex type. Read `references/animation-patterns.md` for full details.

**Quick reference — minimum viable animated diagram**:

```html
<div style="aspect-ratio:1400/760; width:100%;">
  <svg viewBox="0 0 1400 760" style="width:100%; height:100%;"
       preserveAspectRatio="xMidYMid meet">
    <!-- Layer 1: Background -->
    <defs><pattern id="grid" ...>...</pattern></defs>
    <rect width="1400" height="760" fill="#FAF7F2"/>
    <rect width="1400" height="760" fill="url(#grid)"/>

    <!-- Layer 2: Region boundaries -->
    <rect x="50" y="50" width="600" height="660" rx="20"
          fill="#FFF" fill-opacity="0.5"
          stroke="#D6D0C4" stroke-dasharray="6 6" stroke-width="1.5"/>

    <!-- Layer 3: Connections -->
    <path d="M 270 364 C 300 364, 300 364, 330 364"
          stroke="#A8A29E" stroke-width="1.4" fill="none"/>

    <!-- Layer 4: Nodes (rect + foreignObject) -->
    <g>
      <rect x="100" y="320" width="170" height="88" rx="12"
            fill="#FFF" stroke="#E5E0D8"/>
      <foreignObject x="100" y="320" width="170" height="88">
        <div><!-- HTML content --></div>
      </foreignObject>
    </g>

    <!-- Layer 5: Animation particles (managed by JS) -->
    <g id="particles"></g>
  </svg>
</div>
```

---

## 5. Arrow-Key Slide Deck

**Use when**: quick presentation for meetings

**Structure**:
```html
<style>
  .slide { display: none; min-height: 100vh; padding: 80px 60px;
           flex-direction: column; justify-content: center; }
  .slide.active { display: flex; }
  .slide-num { position: fixed; bottom: 20px; right: 24px;
               font-family: var(--mono); font-size: 12px; color: var(--g500); }
</style>

<section class="slide active">
  <div class="eyebrow">Topic</div>
  <h1>Slide Title</h1>
  <p>Key point here.</p>
</section>

<section class="slide">
  <h2>Next Slide</h2>
  <ul><li>Point one</li><li>Point two</li></ul>
</section>

<div class="slide-num"><span id="cur">1</span> / <span id="tot">5</span></div>

<script>
const slides = document.querySelectorAll('.slide');
let idx = 0;
function go(n) {
  slides[idx].classList.remove('active');
  idx = Math.max(0, Math.min(slides.length - 1, n));
  slides[idx].classList.add('active');
  document.getElementById('cur').textContent = idx + 1;
}
document.addEventListener('keydown', e => {
  if (e.key === 'ArrowRight' || e.key === 'ArrowDown') go(idx + 1);
  if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') go(idx - 1);
});
document.getElementById('tot').textContent = slides.length;
</script>
```

Typically 5-10 slides. Each slide is one clear point. Use large serif headings,
minimal text, and inline SVG for figures where needed.

---

## 6. Structured Report

**Use when**: weekly status, project updates, dashboards

**Key elements**:
- Eyebrow label with date/period
- 3-4 KPI cards at top (number + label + delta indicator)
- "Shipped" section with green checkmarks
- "In progress" section with progress bars
- "Blocked / risks" section with red flags
- Optional: small inline bar/line chart using SVG

**KPI card pattern**:
```html
<div class="kpi-row">
  <div class="kpi">
    <div class="kpi-value">94<span class="unit">%</span></div>
    <div class="kpi-label">Uptime</div>
    <div class="kpi-delta positive">+2.1%</div>
  </div>
</div>
```

---

## 7. Incident Timeline

**Use when**: post-mortem, incident report, event sequence

**Key elements**:
- Severity banner at top (P1/P2/P3 with color coding)
- TL;DR box
- Vertical timeline with timestamps on left, events on right
- Log excerpts in monospace blocks
- Impact metrics
- Action items checklist at bottom

**Timeline pattern**:
```html
<div class="timeline">
  <div class="event">
    <div class="timestamp">14:32 UTC</div>
    <div class="dot critical"></div>
    <div class="event-body">
      <strong>Alert fired</strong>
      <p>Error rate exceeded 5% threshold...</p>
      <pre class="log">ERROR: connection pool exhausted</pre>
    </div>
  </div>
</div>
```

---

## 8. Collapsible Explainer

**Use when**: "explain how X works", feature documentation, concept guides

**Key elements**:
- TL;DR box at top
- Collapsible `<details>` sections for each step/concept
- Tabbed code examples (show same concept in different languages/configs)
- Glossary in a sidebar or footer
- Inline diagrams using SVG

---

## 9. Drag-and-Drop Board

**Use when**: prioritization, triage, kanban-style organization

**Key pattern**: CSS columns with drag-and-drop JS, plus an "Export" button that
generates copyable markdown/JSON from the current state.

```js
// Minimal drag-and-drop
el.setAttribute('draggable', true);
el.addEventListener('dragstart', e => e.dataTransfer.setData('text/plain', el.id));
col.addEventListener('dragover', e => e.preventDefault());
col.addEventListener('drop', e => {
  const id = e.dataTransfer.getData('text/plain');
  col.querySelector('.items').appendChild(document.getElementById(id));
});
```

Always include an "Export" button that outputs the current arrangement as markdown
or JSON that can be pasted back into a conversation or committed.

---

## 10. Config Editor

**Use when**: feature flags, settings, parameter tuning

**Key elements**:
- Toggle switches grouped by category
- Dependency warnings (if flag A is off, flag B can't work)
- "Copy diff" button — only exports the changed values
- Search/filter for large flag sets

---

## 11. Module Map

**Use when**: "explain this codebase", "how is this module organized"

**Structure**: SVG boxes and arrows showing module relationships.
- Each module is a rounded rect with its name and description
- Arrows show dependencies (import direction)
- Hot path highlighted with accent color
- Entry points marked with a distinct badge
- Click a module to see its exports, key functions, and file list in a side panel

Uses the same SVG node + click pattern as Interactive Flowchart (type 3),
but with different semantic grouping (packages/modules instead of process steps).

文件 4:references/animation-patterns.md(动画架构图完整参考)

# Animation Patterns for Architecture Diagrams

Complete reference for building animated data-flow diagrams. These patterns were
reverse-engineered from production architecture demos and represent the highest-quality
approach to interactive system visualization.

## Table of Contents
- [SVG Layering Architecture](#svg-layering-architecture)
- [Node Rendering with foreignObject](#node-rendering-with-foreignobject)
- [Multi-Instance Stacking](#multi-instance-stacking)
- [Connection Paths](#connection-paths)
- [Dashed Line Flow Animation](#dashed-line-flow-animation)
- [Data Particle Animation](#data-particle-animation)
- [Node State Management](#node-state-management)
- [Grid Background Pattern](#grid-background-pattern)
- [Region Grouping](#region-grouping)
- [Legend Pattern](#legend-pattern)
- [Playback Controls](#playback-controls)
- [Complete Minimal Example](#complete-minimal-example)

---

## SVG Layering Architecture

The SVG must be layered in this exact order (bottom to top). Getting this wrong
causes visual overlap bugs:

```
Layer 0: <defs>         — patterns, markers, filters
Layer 1: <rect>         — solid background fill
Layer 2: <rect>         — grid pattern overlay
Layer 3: <g>            — region boundary rects (dashed outlines)
Layer 4: <g>            — connection paths (lines between nodes)
Layer 5: <g>            — node groups (rect + foreignObject)
Layer 6: <g id="particles"> — animation particles (JS-managed)
```

Connections UNDER nodes (so nodes visually "sit on top" of their connections).
Particles ABOVE everything (so moving dots are always visible).

---

## Node Rendering with foreignObject

The single most important technique. Never use SVG `<text>` for node labels.

```svg
<g>
  <!-- The clickable/styleable rect -->
  <rect data-node-id="my-node"
        x="220" y="320" width="170" height="88" rx="12"
        fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>

  <!-- HTML content overlay, EXACTLY same position and size -->
  <foreignObject x="220" y="320" width="170" height="88">
    <div xmlns="http://www.w3.org/1999/xhtml"
         style="width:100%; height:100%; display:flex; align-items:center;
                gap:10px; padding:8px 12px; font-family:system-ui,sans-serif;">

      <!-- Icon container -->
      <div style="flex-shrink:0; width:36px; height:36px; border-radius:8px;
                  background:#FFF7ED; border:1px solid #FED7AA;
                  display:flex; align-items:center; justify-content:center;">
        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18"
             viewBox="0 0 24 24" fill="none" stroke="#0E7490"
             stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <!-- icon paths here -->
        </svg>
      </div>

      <!-- Text content -->
      <div style="min-width:0; flex:1;">
        <div style="font-size:13px; font-weight:600; color:#1F1B16;
                    white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">
          Node Title
        </div>
        <div style="font-size:11px; color:#6B6258; margin-top:2px;
                    display:-webkit-box; -webkit-line-clamp:2;
                    -webkit-box-orient:vertical; overflow:hidden;">
          Description text · more details
        </div>
      </div>

      <!-- Optional: instance count badge -->
      <div style="flex-shrink:0; align-self:flex-start; margin-top:2px;
                  font-family:ui-monospace,monospace; font-size:10px;
                  padding:2px 6px; border-radius:4px;
                  color:#6B6258; background:#F4EFE7; border:1px solid #E5E0D8;">
        ×3
      </div>
    </div>
  </foreignObject>
</g>
```

Why this works:
- SVG `<text>` can't do: wrapping, truncation, flexbox, rich formatting
- foreignObject renders real HTML inside SVG coordinates
- The rect provides the border/fill/click target; the foreignObject provides content
- Both share the EXACT same x/y/width/height

---

## Multi-Instance Stacking

To show that a node represents multiple instances (like a cluster of servers),
stack 2-3 rects behind the main one:

```svg
<g>
  <!-- Shadow card 2 (farthest back) -->
  <rect x="230" y="310" width="170" height="88" rx="12"
        fill="#FFFFFF" opacity="0.85" stroke="#E5E0D8" stroke-width="1"/>
  <!-- Shadow card 1 -->
  <rect x="225" y="315" width="170" height="88" rx="12"
        fill="#FFFFFF" opacity="0.67" stroke="#E5E0D8" stroke-width="1"/>
  <!-- Main card (front) -->
  <rect data-node-id="cluster" x="220" y="320" width="170" height="88" rx="12"
        fill="#FFFFFF" stroke="#E5E0D8" stroke-width="1"/>
  <foreignObject x="220" y="320" width="170" height="88">
    <!-- HTML content -->
  </foreignObject>
</g>
```

Each shadow card offsets by `(+5, -5)` relative to the one in front, with
decreasing opacity: 0.85, 0.67, etc. The main card is fully opaque.

For a "dormant/not yet created" variant, use muted colors:
```svg
<rect fill="#F4EFE7" stroke="#D6D0C4" stroke-width="1"/>
<!-- and muted text colors: #A8A29E instead of #1F1B16 -->
```

---

## Connection Paths

### Coordinate Calculation

Connection endpoints align to node EDGES, not centers:

```
Right edge:  (node.x + node.width, node.y + node.height/2)
Left edge:   (node.x, node.y + node.height/2)
Top edge:    (node.x + node.width/2, node.y)
Bottom edge: (node.x + node.width/2, node.y + node.height)
```

### Straight horizontal connection

```svg
<path d="M 170 364 C 195 364, 195 364, 220 364"
      fill="none" stroke="#A8A29E" stroke-opacity="0.85"
      stroke-width="1.4" stroke-linecap="round"/>
```

For short gaps between adjacent nodes, the control points can be simple interpolation.

### Curved connection (for non-adjacent or diagonal links)

```svg
<path d="M 830 350 C 880 330, 893 316, 923 256"
      fill="none" stroke="#A8A29E" stroke-width="1.4"
      stroke-dasharray="6 5" stroke-linecap="round"/>
```

### Semantic line styles

| Meaning | stroke-dasharray | Extra |
|---|---|---|
| Synchronous / direct call | `"0"` (solid) | — |
| Asynchronous / event-driven | `"6 5"` (dashed) | Add `<animate>` for flow |
| Error / failure path | `"4 4"` (dashed) | Use `--rust` color |
| Data return / callback | `"6 5"` (dashed) | Different color than outgoing |

---

## Dashed Line Flow Animation

Make dashed lines appear to "flow" in one direction using SVG native `<animate>`:

```svg
<path d="M 830 350 C 880 330, 893 316, 923 256"
      fill="none" stroke="#A8A29E" stroke-opacity="0.85"
      stroke-width="1.4" stroke-dasharray="6 5" stroke-linecap="round">
  <animate attributeName="stroke-dashoffset"
           values="22;0" dur="1.2s" repeatCount="indefinite"/>
</path>
```

This creates the illusion of movement along the path. The `values="22;0"` should be
the sum of dash + gap (6+5=11) times 2, giving a smooth loop. `dur="1.2s"` is a
good default speed.

This technique is zero-JS — the browser's SVG engine handles it natively.

---

## Data Particle Animation

For showing data flowing along a path (like messages or requests moving through a
system), use JS-driven circle elements.

### Particle Visual Structure

Each particle consists of 3-4 SVG elements:

```svg
<!-- 1. Glow halo -->
<circle cx="500" cy="364" r="10" fill="#0891B2" opacity="0.06"/>
<!-- 2. Solid dot -->
<circle cx="500" cy="364" r="5.5" fill="#0891B2" opacity="1"
        stroke="#FFFFFF" stroke-width="1"/>
<!-- 3. Trail dot (offset behind movement direction) -->
<circle cx="496" cy="364" r="2.5" fill="#0891B2" opacity="0.36"/>
<!-- 4. Label (optional) -->
<g transform="translate(500, 364)" opacity="0.8">
  <rect x="-40" y="-22" width="80" height="16" rx="4"
        fill="#FFFFFF" stroke="#0891B2" stroke-opacity="0.5"/>
  <text text-anchor="middle" y="-10" font-size="10"
        fill="#1F1B16" font-family="monospace">POST /turn</text>
</g>
```

### Movement Along a Path

Use `SVGPathElement.getTotalLength()` and `getPointAtLength()`:

```js
function animateParticle(pathId, color, label, duration) {
  const path = document.getElementById(pathId);
  const totalLen = path.getTotalLength();
  const particleGroup = document.getElementById('particles');

  // Create particle elements
  const halo = createCircle(0, 0, 10, color, 0.06);
  const dot = createCircle(0, 0, 5.5, color, 1);
  dot.setAttribute('stroke', '#FFF');
  dot.setAttribute('stroke-width', '1');
  particleGroup.append(halo, dot);

  let start = null;
  function step(timestamp) {
    if (!start) start = timestamp;
    const progress = Math.min((timestamp - start) / duration, 1);

    // Ease in-out
    const t = progress < 0.5
      ? 2 * progress * progress
      : 1 - Math.pow(-2 * progress + 2, 2) / 2;

    const point = path.getPointAtLength(t * totalLen);
    halo.setAttribute('cx', point.x);
    halo.setAttribute('cy', point.y);
    dot.setAttribute('cx', point.x);
    dot.setAttribute('cy', point.y);

    // Fade in at start, fade out at end
    const opacity = progress < 0.1 ? progress / 0.1
                  : progress > 0.9 ? (1 - progress) / 0.1 : 1;
    dot.setAttribute('opacity', opacity);
    halo.setAttribute('opacity', opacity * 0.06);

    if (progress < 1) {
      requestAnimationFrame(step);
    } else {
      halo.remove();
      dot.remove();
    }
  }
  requestAnimationFrame(step);
}

function createCircle(cx, cy, r, fill, opacity) {
  const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
  c.setAttribute('cx', cx);
  c.setAttribute('cy', cy);
  c.setAttribute('r', r);
  c.setAttribute('fill', fill);
  c.setAttribute('opacity', opacity);
  return c;
}
```

### Color by Data Type

Assign particle colors by the type of data flowing:

| Data Type | Color | Example |
|---|---|---|
| Request / entry | `#0891B2` (cyan) | POST /turn, HTTP request |
| Messages / context | `#7C3AED` (purple) | LLM messages, conversation |
| Tool call | `#BE185D` (pink) | tool_use, function call |
| Server result | `#15803D` (green) | API response, tool result |
| Sandbox result | `#D97706` (orange) | Execution output |

---

## Node State Management

Use `data-*` attributes on node rects to track state, and JS to update visuals:

```js
function activateNode(nodeId, roleColor) {
  // Reset all nodes
  document.querySelectorAll('[data-node-id]').forEach(n => {
    n.setAttribute('stroke', '#E5E0D8');
    n.setAttribute('stroke-width', '1');
    n.setAttribute('data-active', '0');
  });
  // Highlight target
  const node = document.querySelector(`[data-node-id="${nodeId}"]`);
  if (node) {
    node.setAttribute('stroke', roleColor);
    node.setAttribute('stroke-width', '2');
    node.setAttribute('data-active', '1');
  }
}
```

---

## Grid Background Pattern

```svg
<defs>
  <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
    <path d="M 40 0 L 0 0 0 40" fill="none"
          stroke="#E5E0D8" stroke-opacity="0.5" stroke-width="1"/>
  </pattern>
</defs>
<rect width="1400" height="760" fill="#FAF7F2"/>
<rect width="1400" height="760" fill="url(#grid)"/>
```

The `L`-shaped path creates a grid of squares. Two rects: one for background color,
one for the pattern overlay.

---

## Region Grouping

Large dashed-border rects to visually group related nodes:

```svg
<g>
  <rect x="190" y="120" width="890" height="620" rx="20"
        fill="#FFFFFF" fill-opacity="0.5"
        stroke="#D6D0C4" stroke-dasharray="6 6" stroke-width="1.5"/>
  <foreignObject x="206" y="132" width="600" height="24">
    <div style="display:flex; align-items:center; gap:6px;
                font-family:ui-monospace,monospace; font-size:11px;
                letter-spacing:0.05em; color:#1F1B16;">
      <!-- icon SVG -->
      <span>Region Label · Description</span>
    </div>
  </foreignObject>
</g>
```

Different regions use different fill tints:
- Default: `fill="#FFFFFF" fill-opacity="0.5"` with `stroke="#D6D0C4"`
- Warm/orange region: `fill="#FFF7ED" fill-opacity="0.55"` with `stroke="#FED7AA"`

---

## Legend Pattern

Always include at the bottom, outside the SVG:

```html
<div style="margin-top:16px; display:flex; flex-wrap:wrap;
            gap:24px; padding:0 8px; align-items:center;">
  <div style="display:flex; align-items:center; gap:8px;">
    <svg width="32" height="8" viewBox="0 0 32 8">
      <line x1="0" y1="4" x2="32" y2="4"
            stroke="#78716C" stroke-width="1.6"/>
    </svg>
    <span style="font-family:monospace; font-size:11px; color:#6B6258;">
      实线 = 同步调用
    </span>
  </div>
  <div style="display:flex; align-items:center; gap:8px;">
    <svg width="32" height="8" viewBox="0 0 32 8">
      <line x1="0" y1="4" x2="32" y2="4"
            stroke="#78716C" stroke-width="1.6" stroke-dasharray="5 4"/>
    </svg>
    <span style="font-family:monospace; font-size:11px; color:#6B6258;">
      虚线 = 异步调用
    </span>
  </div>
  <!-- Color dots for data types -->
  <div style="display:flex; align-items:center; gap:8px;">
    <span style="width:10px; height:10px; border-radius:50%; background:#0891B2;"></span>
    <span style="font-family:monospace; font-size:11px; color:#6B6258;">request</span>
  </div>
</div>
```

---

## Playback Controls

For animated diagrams with multi-stage flows, include control buttons:

```html
<div style="display:flex; gap:8px; align-items:center;">
  <button onclick="reset()">⟲ 重置</button>
  <button onclick="nextStage()">下一阶段</button>
  <button onclick="togglePlay()" id="playBtn">▶ 播放</button>
</div>

<script>
let currentStage = 0;
const stages = [
  { name: "Client → Server", activeNodes: ["client","server"],
    particles: [{ path: "p_client_server", color: "#0891B2", label: "request" }] },
  // ...more stages
];

function nextStage() {
  if (currentStage >= stages.length) return;
  const stage = stages[currentStage];
  stage.activeNodes.forEach(id => activateNode(id));
  stage.particles.forEach(p => animateParticle(p.path, p.color, p.label, 1500));
  currentStage++;
}

function reset() {
  currentStage = 0;
  document.querySelectorAll('[data-node-id]').forEach(n => {
    n.setAttribute('stroke', '#E5E0D8');
    n.setAttribute('stroke-width', '1');
  });
  document.getElementById('particles').innerHTML = '';
}
</script>
```

---

## Complete Minimal Example

A bare-bones animated two-node diagram with one connection and particle animation.
Use this as a starting scaffold:

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Architecture Demo</title>
  <style>
    :root {
      --ivory:#FAF9F5; --paper:#FFFFFF; --slate:#141413;
      --clay:#D97757; --g300:#D1CFC5; --g500:#87867F; --g700:#3D3D3A;
      --serif: ui-serif, Georgia, serif;
      --sans: system-ui, sans-serif;
      --mono: ui-monospace, "SF Mono", Menlo, monospace;
    }
    * { box-sizing: border-box; margin: 0; }
    body { background: var(--ivory); color: var(--slate);
           font-family: var(--sans); padding: 40px 24px; }
    .wrap { max-width: 1000px; margin: 0 auto; }
    h1 { font-family: var(--serif); font-size: 28px; font-weight: 500;
         margin-bottom: 20px; }
    .canvas { border: 1.5px solid var(--g300); border-radius: 14px;
              background: var(--paper); overflow: hidden; }
    button { font-family: var(--mono); font-size: 12px; padding: 8px 16px;
             background: var(--paper); border: 1.5px solid var(--g300);
             border-radius: 8px; cursor: pointer; }
    button:hover { border-color: var(--slate); }
    button.primary { background: var(--clay); color: white;
                     border-color: var(--clay); }
  </style>
</head>
<body>
  <div class="wrap">
    <h1>System Architecture</h1>
    <div style="display:flex; gap:8px; margin-bottom:16px;">
      <button onclick="reset()">重置</button>
      <button class="primary" onclick="runDemo()">▶ 播放</button>
    </div>
    <div class="canvas">
      <div style="aspect-ratio:800/400; width:100%;">
        <svg viewBox="0 0 800 400" style="width:100%;height:100%;"
             preserveAspectRatio="xMidYMid meet">
          <defs>
            <pattern id="grid" width="40" height="40"
                     patternUnits="userSpaceOnUse">
              <path d="M 40 0 L 0 0 0 40" fill="none"
                    stroke="#E5E0D8" stroke-opacity="0.5" stroke-width="1"/>
            </pattern>
          </defs>
          <rect width="800" height="400" fill="#FAF7F2"/>
          <rect width="800" height="400" fill="url(#grid)"/>

          <!-- Connection -->
          <path id="p1" d="M 270 200 C 350 200, 450 200, 530 200"
                fill="none" stroke="#A8A29E" stroke-width="1.4"
                stroke-dasharray="6 5" stroke-linecap="round">
            <animate attributeName="stroke-dashoffset"
                     values="22;0" dur="1.2s" repeatCount="indefinite"/>
          </path>

          <!-- Node A -->
          <g>
            <rect data-node-id="a" x="100" y="156" width="170" height="88"
                  rx="12" fill="#FFF" stroke="#E5E0D8" stroke-width="1"/>
            <foreignObject x="100" y="156" width="170" height="88">
              <div xmlns="http://www.w3.org/1999/xhtml"
                   style="width:100%;height:100%;display:flex;align-items:center;
                          gap:10px;padding:8px 12px;font-family:system-ui;">
                <div style="font-size:13px;font-weight:600;color:#1F1B16;">
                  Service A</div>
              </div>
            </foreignObject>
          </g>

          <!-- Node B -->
          <g>
            <rect data-node-id="b" x="530" y="156" width="170" height="88"
                  rx="12" fill="#FFF" stroke="#E5E0D8" stroke-width="1"/>
            <foreignObject x="530" y="156" width="170" height="88">
              <div xmlns="http://www.w3.org/1999/xhtml"
                   style="width:100%;height:100%;display:flex;align-items:center;
                          gap:10px;padding:8px 12px;font-family:system-ui;">
                <div style="font-size:13px;font-weight:600;color:#1F1B16;">
                  Service B</div>
              </div>
            </foreignObject>
          </g>

          <!-- Particle layer -->
          <g id="particles"></g>
        </svg>
      </div>
    </div>
  </div>
  <script>
  function createCircle(cx,cy,r,fill,opacity){
    const c=document.createElementNS('http://www.w3.org/2000/svg','circle');
    c.setAttribute('cx',cx);c.setAttribute('cy',cy);
    c.setAttribute('r',r);c.setAttribute('fill',fill);
    c.setAttribute('opacity',opacity);return c;
  }
  function animateParticle(pathId,color,dur){
    const path=document.getElementById(pathId);
    const len=path.getTotalLength();
    const g=document.getElementById('particles');
    const halo=createCircle(0,0,10,color,0.06);
    const dot=createCircle(0,0,5.5,color,1);
    dot.setAttribute('stroke','#FFF');dot.setAttribute('stroke-width','1');
    g.append(halo,dot);
    let start=null;
    function step(ts){
      if(!start)start=ts;
      const p=Math.min((ts-start)/dur,1);
      const t=p<.5?2*p*p:1-Math.pow(-2*p+2,2)/2;
      const pt=path.getPointAtLength(t*len);
      halo.setAttribute('cx',pt.x);halo.setAttribute('cy',pt.y);
      dot.setAttribute('cx',pt.x);dot.setAttribute('cy',pt.y);
      const op=p<.1?p/.1:p>.9?(1-p)/.1:1;
      dot.setAttribute('opacity',op);halo.setAttribute('opacity',op*.06);
      if(p<1)requestAnimationFrame(step);
      else{halo.remove();dot.remove();}
    }
    requestAnimationFrame(step);
  }
  function activateNode(id,color){
    const n=document.querySelector(`[data-node-id="${id}"]`);
    if(n){n.setAttribute('stroke',color);n.setAttribute('stroke-width','2');}
  }
  function reset(){
    document.querySelectorAll('[data-node-id]').forEach(n=>{
      n.setAttribute('stroke','#E5E0D8');n.setAttribute('stroke-width','1');
    });
    document.getElementById('particles').innerHTML='';
  }
  function runDemo(){
    reset();
    activateNode('a','#0891B2');
    setTimeout(()=>animateParticle('p1','#0891B2',1500),300);
    setTimeout(()=>activateNode('b','#15803D'),1800);
  }
  </script>
</body>
</html>
```

总结

html-artifact 这个 Skill 的价值在于它系统性地解决了“AI 输出的可视化天花板”问题。它不是简单地“生成一个网页”,而是沉淀了一整套经过验证的设计系统和工程实践:统一的色彩语言、严格的字体层级、标准化的组件模式、可复现的 SVG 动画技术。

如果你在用 AI 辅助日常工作,下次需要画架构图、做方案对比、写状态报告、甚至做会议 PPT 的时候,不妨试试让 AI 生成一个 HTML Artifact。双击打开的那一刻,你会理解为什么纯文本不够用。