# DSH 固定气泡样式 · 内部素材规范（v3.2）

> **用途**：插件作者内部设计、生成、验收「固定气泡样式」。
> **不是**用户自定义接口的规范；用户自定义功能后续再开放，并复用本规范的素材包格式。
> **变更**：v2「用户上传背景 + 装饰」分层实验暂缓；M2 先做**固定气泡样式**（每套素材独立切片/内边距/文字色），发布可用样式后，再开放自定义。
> **v3.1 重点**：设计基准改为 DSH 原版 **逻辑 525×42（@2x 源图 1050×84）**；单行文字目标 42px，不因装饰加高；允许文字覆盖图案，靠文字色/描边保证可读；装饰可分布在顶部/两侧/四角/底部。
> **v3.2 重点**：装饰不再一刀切“全部固定”。按功能分区：脸/头/logo/尾尖等**身份部件固定**；身体/轮廓/条纹/海水等**连续装饰允许拉伸**；重复纹理可平铺；只有越界/动画/刚性部件才拆挂件。

---

## 0. 核心原则

| 原则 | 说明 |
| --- | --- |
| 固定样式优先 | 每套气泡由作者调好参数，用户只负责选，不碰素材 |
| 每套独立参数 | 每套素材有自己的 `slice / borderWidth / padding / textColor`，不再全局统一 |
| 效果图只做预览 | `preview.png` 用于文档/验收，**不参与运行时** |
| 挂件按需拆分 | 简单装饰直接烘焙进背景；只有越界/大挂件/动画才拆成独立素材 |
| 先 ninepatch，后 hybrid | 第一批只做九宫格拉伸；固定挂件组装作为增强能力 |

---

## 1. 素材包目录

每套固定样式一个目录：

```text
bubble-<id>/
├── manifest.json          # 必需：样式配置（运行时读取）
├── background.svg         # 必需：九宫格背景（运行时使用）
├── accessories/           # 可选：固定挂件（运行时使用）
│   ├── tl-star@2x.png
│   └── br-character@2x.png
├── preview.png            # 必需：整体效果图（文档/验收，不运行时）
├── README.md              # 必需：来源、授权、制作说明
└── source/                # 可选：分层设计源文件，不随插件发布
```

| 文件 | 运行时 | 用途 |
| --- | --- | --- |
| `manifest.json` | ✅ | 决定背景、切片、内边距、文字色、尾巴色、挂件组装 |
| `background.svg/png` | ✅ | 九宫格背景；没有挂件时整张气泡本身 |
| `accessories/*` | ✅（可选） | 固定尺寸挂件，可越界；按 manifest 锚点组装 |
| `preview.png` | ❌ | 整体效果图；用于文档、商店、验收对比 |
| `README.md` | ❌ | 素材来源/授权/切片调参记录 |
| `source/` | ❌ | PSD/Figma/SVG 分层源文件，不发布 |

---

## 2. 整体效果图 `preview.png`

### 2.1 作用

- 让作者/用户一眼看到**最终组装后的完整气泡**；
- 用于 README、插件市场、版本对比；
- **禁止**把它当作运行时素材（运行时只认 manifest + background + accessories）。

### 2.2 规格

| 项 | 要求 |
| --- | --- |
| 画布 | 逻辑显示 **525 × 42**；预览/导出按 @2x **1050 × 84**（有边框样式按实际显示高度 ×2，透明背景） |
| 内容 | 背景 + 全部挂件 + 文字示意 + 尾巴示意（可选） |
| 文字 | 用占位文字；不要烘焙成素材 |
| 来源 | **必须由 manifest/组装器生成**，不能手工画一张和实际不一致的图 |
| 附加图 | 可另存 `preview-chat.png`（DSH 真机截图）作为验收图，不强制 |

> 建议做一个本地 HTML 组装器：读取 manifest + background + accessories，按 DSH 气泡尺寸渲染，Chrome 截图直接生成 `preview.png`。这样效果图和运行时一致。

---

## 3. 背景层 `background`

### 3.1 三种制作模式

| 模式 | 适用 | 做法 |
| --- | --- | --- |
| **A. 平铺九宫格**（默认） | 简单气泡：圆角、边框、渐变、小角花 | 整张图烘焙完整气泡；四角固定，中间可拉伸 |
| **B. 混合壳体**（复杂款推荐） | 猫猫/鲨鱼/动物身体：头固定 + 身体拉伸 + 尾巴固定 | 同一张 9-patch 背景里分固定部件与可拉伸身体，不拆挂件 |
| **C. 分层组装**（按需） | 大挂件、越界挂件、动画、必须完全刚性的 logo | 背景只画壳体；挂件放 `accessories/` 单独导出 |

> 复杂款先考虑 **B 混合壳体**：身体、轮廓、条纹、海水可以跟着文字拉伸；只有脸、头、五官、尾巴尖、logo 等“不能变形”的部件才需要固定。**不是所有装饰都要拆挂件，也不是所有装饰都不能拉伸。**

### 3.2 画布基准：按 DSH 原版气泡尺寸

| 项 | 要求 |
| --- | --- |
| **标准画布（无边框）** | 逻辑显示 **525 × 42**；源图按 @2x **1050 × 84** 导出 |
| **实际气泡宽度** | **内容驱动，最大约 525px**；1050px 只是源图 @2x 设计基准，不是固定气泡宽度 |
| **有边框/外框** | 允许更高：显示高度 = `42 + 2 × 边框厚度`，源图高度 = 显示高度 × 2（例：2px 边框 → 525×46 显示 / 1050×92 源图；4px → 1050×100） |
| 透明背景 | 必需（SVG/PNG 带 Alpha） |
| 文字区域 | 固定按 DSH 原版：单行 42px、`padding 10/16`；**不因装饰加高** |
| 固定四角 | 脸/头/五官/logo/爪子/尾尖等身份部件必须放固定切片或挂件，不能拉伸 |
| 中间区域 | 允许放**连续装饰**（身体/轮廓/条纹/海水/云/渐变）；这些图案本来就应随文字自然拉伸，不需要拆挂件 |
| 文字 | 不烘焙；由运行时渲染 |
| 尾巴 | 不烘焙；由 `::after` 独立绘制，颜色由 manifest 指定 |
| 外轮廓 | 可以画圆角/描边/阴影；有真实边框时才允许气泡整体更高 |

#### 为什么不再用 720×240？

- 720×240 是 3:1，高 240px，是旧实验画布；它会诱导作者把下/上装饰画成大面积固定带，最终把 42px 的文字气泡撑到 70–96px；
- DSH 原版气泡是**宽而矮**的：单行 42px、最大宽 525px；画布高度应直接对齐这个比例；
- 标准设计逻辑改为 **525×42**（@2x 源图 **1050×84**）后，单行气泡设计高度就是 42px，不再天然偏大；有边框的样式才允许额外高度。

#### 尺寸对照

| 样式 | 显示尺寸 | 源图 @2x |
| --- | --- | --- |
| 无边框标准 | 525 × 42 | **1050 × 84** |
| 2px 外框 | 525 × 46 | 1050 × 92 |
| 4px 外框 | 525 × 50 | 1050 × 100 |

> 源图尺寸不直接决定运行时气泡高度，但**设计基准**会直接影响作者留白/装饰比例。先用 DSH 尺寸设计，再通过 `slice/borderWidth/padding` 映射，才能避免“单行被装饰撑得很大”。

> **1050×84 是 @2x 源图，不是运行时气泡尺寸。** 运行时单行气泡高 **42px**，宽度由内容决定、最大约 **525px**；短消息不会被强制拉成 525 宽或 84 高。源图尺寸只是设计/导出基准，不应把它误解为固定气泡大小。

> `bubble-clover-meadow` / `bubble-koi-rising` 已按本节重做为 1050×84 @2x 素材；旧 720×240 版本已从插件与素材目录移除。

### 3.3 参数（由 manifest/调参确定）

```json
{
  "background": "background.svg",
  "slice": [24, 32, 24, 32],
  "borderWidth": [10, 16, 10, 16],
  "borderImageWidth": [10, 16, 10, 16],
  "padding": [10, 16, 10, 16]
}
```

| 字段 | 单位 | 作用 |
| --- | --- | --- |
| `slice` | **源图像素**（上 右 下 左） | `border-image-slice`；决定从源图切多少作为固定/边缘区 |
| `borderWidth` | CSS px（上 右 下 左） | `::before` 的边框宽度，决定固定角在屏幕上的尺寸 |
| `borderImageWidth` | CSS px | `border-image-width`；第一批建议等于 `borderWidth` |
| `padding` | CSS px | 气泡文字内边距；由 DSH 气泡本体使用 |

### 3.4 调参原则（DSH 0.2.1-rc.2）

DSH 官方气泡：

| 项 | 默认值 |
| --- | --- |
| 字号 / 行高 | 14px / 22px |
| 内边距 | 10px（上/下）、16px（左/右） |
| 单行高度 | 42px |
| 宽度 | 内容自适应，最大约 525px |

固定样式调参时：

1. **上下固定区不宜过大**：单行气泡只有 42px；`borderWidth` 上下建议先试 10–14px；
2. **文字区域优先上下对称**：`padding-top ≈ padding-bottom`（差 ≤2px 为宜），不要因为底部装饰带大就把文字顶到上方；左右 padding 可按固定装饰调整，但文字块要视觉居中；
3. **左右固定区可更大**：左右角花/边框可做到 16–24px；
4. **先做单行，再做多行**：多行时上下固定区不变、中间纵向拉伸；检查四角/角花是否变形；
5. **每套独立调参**：不要用一套 slice 套所有素材；调好后把值写进 manifest；
6. **允许文字覆盖装饰**：不要求“文字完全不碰图案”；只要文字区域对比度达标、正文可读即可（见 §3.5）。

> 具体数值必须在 DSH 真机/本地 mock 上验证；不同素材的固定区不同，不能拍脑袋复用。

### 3.5 文字区域与可读性（QQ 经验）

从 QQ 个性气泡的实际设计看：

- 气泡中间有一个相对稳定的**文字区域**；
- 文字区域上下留白基本一致；
- 装饰围绕四周，允许进入文字区域后方；
- 文字通过颜色/描边/阴影/底板保证可读，而不是要求图案必须避开文字。

我们的固定样式按这个原则调整：

| 规则 | 说明 |
| --- | --- |
| **上下留白对称** | `padding-top ≈ padding-bottom`；QQ 式气泡不会“文字贴顶、底部一大片装饰” |
| **文字区域固定** | 用 padding 定义文字盒，而不是让装饰带把文字挤走 |
| **允许图案进入文字区** | 只要对比度可读，文字可以盖在渐变/装饰上半部分；不要求完全避开 |
| **必须检查真实对比度** | 在文字实际覆盖的区域取最差背景，按 WCAG AA 4.5:1 验证；图案复杂时加 text-shadow/描边或半透明底板 |
| **刚性装饰带才拆挂件** | 只有脸/头/logo/越界/动画等“不能拉伸”的大装饰才拆挂件；身体/轮廓/条纹/海水可留在中间区自然拉伸 |

### 3.6 装饰位置（不要只做底部）

装饰不限于气泡底部；QQ 个性气泡里常见的位置都可以用：

| 位置 | 适合的装饰 |
| --- | --- |
| 顶部 | 耳朵/帽子/顶部花边/云朵 |
| 四角 | 小猫、人物、星星、四叶草 |
| 左右两侧 | 翅膀/飘带/侧边花纹 |
| 底部 | 海浪、草地、小动物、尾巴托底 |
| 文字后方 | 渐变、花纹、大图案；可被文字覆盖，靠文字色/描边保证可读 |
| 气泡外侧 | 需要越界的挂件（后续用 accessories 层） |

规则：

- **不要默认把所有装饰放到底部**；底部只放需要“托住”气泡的图案；
- 顶部/两侧装饰可以进入文字区，不需要因此增加 padding 或缩小图案；
- 只有在“真实边框/外框”时才允许整体加高，装饰本身不允许把气泡撑高；
- 固定装饰放在四角/边缘的固定切片内；可拉伸的连续装饰放中间区，允许自然拉伸。

### 3.7 装饰的拉伸策略（v3.2 核心）

一个气泡里可以**同时存在固定装饰和可拉伸装饰**。判断标准不是“装饰大不大”，而是“这个部件变形后是否还能认出来”。

| 装饰类型 | 能否拉伸 | 放置方式 | 例子 |
| --- | --- | --- | --- |
| 脸、头、五官、爪子、尾尖、logo、文字 | ❌ 不能 | 固定角/固定切片/挂件 | 猫头、鲨鱼头、角色脸 |
| 身体、轮廓、边框、条纹、海水、云、渐变色带 | ✅ 可以 | 9-patch 中间区，随文字横向/纵向拉伸 | 猫身体、鲨鱼身体、海浪 |
| 重复纹理：波点、格纹、小图案 | ✅ 可以 | `border-image-repeat: repeat/round` 或可重复切片 | 小星星底纹、格子布 |
| 需要越界/动画/必须完全刚性的部件 | ❌ 不能 | `accessories/` 挂件 | 越界耳朵、角色立绘、Lottie |

QQ 猫猫/鲨鱼气泡的典型结构：

```text
[固定头] [可拉伸身体/轮廓/底纹 ......... 随文字变长] [固定尾/爪]
   ↑ 不拉伸                                              ↑ 不拉伸
```

规则：

- **按部件决定**能否拉伸，不要按整张素材一刀切；
- 固定身份部件不能进入中间拉伸区，否则脸会被拉宽/拉扁；
- 身体和轮廓进入中间区后，拉伸是预期行为，不要为了“保持原比例”把它改成挂件；
- 短/长/多行都要检查：固定头尾不变形，中间身体/纹理自然延长；
- 如果部件既需要固定、又会跨切片线，才拆成挂件。

示例（目标值）：

- 标准无边框样式：显示 **525×42**，文字上下 padding **10/10**；
- 有 2px 外框：显示 **525×46**，文字仍 10/10，额外高度只来自边框；
- 草坡/锦鲤已按 1050×84 @2x 重做，作为 v3.2 示例。

### 3.8 点九图机制对照（Android .9.png → CSS border-image）

参考文章：

- [聊天气泡图片的动态拉伸、适配与镜像（CSDN）](https://blog.csdn.net/u010976213/article/details/128692382)
- [Android NinePatch 机制介绍（CSDN）](https://blog.csdn.net/smile_watermelon/article/details/52449266)
- [Android .9.png 使用与原理（CSDN）](https://blog.csdn.net/weixin_34248705/article/details/91857147)

Android 点九图的核心机制：

| Android .9.png | 作用 | 本项目的对应 |
| --- | --- | --- |
| 上/左侧 1px 黑线 | 标记**横向/纵向可拉伸区**，可以是一条或多条线段 | `border-image-slice` 划分固定角与中间拉伸区（**只有单一中心区**） |
| 下/右侧 1px 黑线 | 标记**内容 padding**，与拉伸区独立 | manifest 的 `padding` / CSS padding；不读取标记 |
| 未被标记覆盖的区域 | 固定不拉伸（角、头、尾、logo） | 固定切片区或 `accessories/` 挂件 |
| 多条拉伸线段 | 允许中间夹一个固定元素（例：财神爷不被拉宽） | CSS `border-image` 不支持多段；中间固定元素需拆挂件或多层背景 |
| 拉伸区内的装饰 | 可以随图片拉伸 | 允许放在中间区，按 v3.2 的“身体/轮廓可拉伸”处理 |
| 1px 标记边框 | 只是编辑信息，**不显示** | 本项目**不要**在源图边缘画黑线；用 manifest 参数表达 |

映射到我们的制作流程时要注意：

1. **源图不要画 1px 黑线**：`background.svg/png` 只保留可见气泡，边缘黑线会让它被当作画面内容显示；
2. **padding 独立设置**：不要期待 CSS 从底部/右侧黑线自动读内容区，直接写 manifest `padding`；
3. **固定中间元素必须拆**：如果像“财神爷”一样固定图案正好在中间拉伸轴上，CSS 单层 `border-image` 会把它拉变形；这时应拆成挂件（或未来用多层背景）；
4. **猫猫/鲨鱼可用单层实现**：头在左固定切片、身体在中间拉伸、尾巴在右固定切片，只涉及一个中心拉伸区，正好适配 `border-image`；
5. **拉伸 ≠ 平铺**：Android 默认是拉伸；需要波点/格纹重复时，使用 `border-image-repeat: repeat/round`，不要依赖普通拉伸。

> 结论：点九图的“角固定 + 中间拉伸 + padding 独立”与我们的 `border-image` 方案一致；主要差异是 **Android 支持多条拉伸线段，而 CSS border-image 只有单一中心区**。复杂固定装饰优先拆挂件，不要硬塞进中间拉伸区。

---

## 4. 挂件层 `accessories`（可选）

### 4.1 什么时候需要拆分

| 情况 | 处理 |
| --- | --- |
| 脸部/头部/logo/尾尖等身份部件，且完全在固定切片区内 | 直接烘焙进背景固定区 |
| 连续身体/轮廓/条纹/海水，需要随文字变长 | **留在背景中间区拉伸，不要拆挂件** |
| 重复纹理/波点/格纹 | 用可重复切片或 `border-image-repeat` |
| 装饰需要**伸出气泡轮廓** | 拆成挂件，独立图层 |
| 刚性身份部件跨切片线、固定区内放不下 | 拆成挂件 |
| 固定部件正好位于中间拉伸轴（类似点九图的“财神爷”多段拉伸） | CSS 单层 `border-image` 不支持多段；拆成挂件或改用多层背景（后续能力） |
| 装饰需要动画（Lottie/SVGA） | 拆成动画挂件（后续） |
| 同装饰要被多套样式复用 | 拆成挂件 |

### 4.2 挂件素材规格

| 项 | 要求 |
| --- | --- |
| 格式 | 透明 **PNG/WebP/SVG** |
| 倍率 | 推荐 @2x；manifest 明确写显示尺寸，不依赖压缩后像素 |
| 画布 | 贴合内容；如需偏移，用透明留白 |
| 内容 | 一个完整挂件；不带文字（除非就是装饰文字） |
| 体积 | 单张建议 ≤200KB |

### 4.3 挂件定位

| 锚点 | 含义 |
| --- | --- |
| `tl` | 左上 |
| `tc` | 顶部居中 |
| `tr` | 右上 |
| `lc` | 左侧居中 |
| `rc` | 右侧居中 |
| `bl` | 左下 |
| `bc` | 底部居中 |
| `br` | 右下 |

> 装饰不限于底部：顶部、左右两侧、四角、文字后方都可以。`zIndex < 0` 表示放到文字后方（文字色/描边负责可读），`zIndex > 0` 表示前景挂件；越界挂件需检查 DSH 容器 `overflow`。
> **当前固定样式引擎**只实现了 `tl/tr/bl/br/bc` 五个锚点；`tc/lc/rc` 是规范预留，代码扩展后再正式使用。

每个挂件由 manifest 指定：

```json
{
  "image": "accessories/tl-star@2x.png",
  "anchor": "tl",
  "width": 24,
  "height": 24,
  "offsetX": 0,
  "offsetY": 0,
  "zIndex": 1
}
```

| 字段 | 说明 |
| --- | --- |
| `anchor` | 相对气泡哪个角/底边定位 |
| `width/height` | 屏幕显示尺寸（CSS px），**明确写死**，不要运行时按图片压缩尺寸推算 |
| `offsetX/offsetY` | 相对锚点的偏移，支持负值（越界） |
| `zIndex` | 挂件之间层级；建议 1–10 |

### 4.4 文字区域与挂件

- 挂件可以放在文字下方、边缘或前景；**不要求文字完全避开挂件**；
- 但必须保证文字实际覆盖区域的对比度（WCAG AA 4.5:1）；复杂挂件用文字色/描边/阴影或半透明底板解决；
- **不要为了避开挂件加高气泡或缩小图案**；优先调整文字颜色和 z-index；
- 需要越界/前景时，通过 `zIndex` 控制层级；首批固定样式优先把大型挂件放角/外侧，降低适配风险。

---

## 5. `manifest.json` 完整示例

```json
{
  "version": 1,
  "id": "bubble-galaxy",
  "name": { "zh": "星海", "en": "Galaxy" },
  "description": { "zh": "深色渐变 + 左上星标", "en": "Dark gradient with a star badge" },
  "preview": "preview.png",
  "background": "background.svg",

  "slice": [24, 32, 24, 32],
  "borderWidth": [10, 16, 10, 16],
  "borderImageWidth": [10, 16, 10, 16],
  "padding": [10, 16, 10, 16],

  "textColor": "#ffffff",
  "textShadow": "0 1px 2px rgba(0, 0, 0, 0.35)",
  "tailColor": "#0A267A",

  "accessories": [
    {
      "image": "accessories/tl-star@2x.png",
      "anchor": "tl",
      "width": 24,
      "height": 24,
      "offsetX": 0,
      "offsetY": 0,
      "zIndex": 1
    }
  ]
}
```

无挂件时：

```json
{ "accessories": [] }
```

> `slice` 用**源图像素**而不是百分比，避免裁边/压缩后换算偏移；`width/height/padding` 用 CSS px。

---

## 6. 内部制作与组装流程

```text
1. 设计分层源稿（PS/Figma/SVG）
   ├─ 气泡壳体（可拉伸）
   └─ 挂件（可选，独立图层）
        ↓
2. 决定模式
   A 平铺九宫格：整张图烘焙
   B 分层组装：壳体 + 挂件分开
        ↓
3. 导出
   ├─ background.svg/png（透明；逻辑 525×42，源图按 @2x 1050×84 导出；有真实边框时按显示高度 ×2）
   └─ accessories/*@2x.png（透明）
        ↓
4. 调参：slice / borderWidth / padding / textColor / tailColor
        ↓
5. 写 manifest.json
        ↓
6. 生成 preview.png（用组装器/HTML + Chrome，按 DSH 气泡尺寸渲染）
        ↓
7. QA：短/长/多行、明暗、尾巴、附件行
        ↓
8. 打包进插件：素材转 data URI 内联 client.js（固定样式不走用户上传）
```

### 6.1 组装顺序

```text
① 背景层（九宫格，负责壳体）
② 挂件层（按锚点/偏移，可越界）
③ 文字（DSH 渲染，按 manifest padding）
④ 尾巴（::after，独立）
```

### 6.2 预览生成

建议做一个内部 `composer` 页面：

- 读取 manifest；
- 按 DSH 气泡宽高（42px 单行 / 多行）渲染；
- 输出短消息、长消息、多行三种 `preview.png`；
- 保证预览和运行时用同一套 CSS。

---

## 7. DSH 适配约束

| 项 | 处理 |
| --- | --- |
| 气泡 DOM | 仍是 `[data-chat-flow-kind="user"] [class$="_bubble"]` 等选择器 |
| 背景层 | 建议画在 `::before` 上（`inset:0; border-image`），避免改动 DSH 文字布局 |
| 文字布局 | 气泡本体 `padding` 由 manifest 决定；DSH 行高/字体保持 |
| 尾巴 | `::after`；颜色来自 manifest `tailColor` |
| 圆角 | 由素材/`border-image` 决定；不再强行用 DSH 20px 圆角 |
| 包装载 | 固定样式素材内联 data URI，随 client.js 发布；**不经过用户上传/压缩** |
| 暗色主题 | 第一批同一套素材；必要时后续做 `dark` 变体 |
| 越界挂件 | 需要确认 DSH 气泡/父容器 `overflow`；第一批先避免依赖越界 |

---

## 8. 验收清单

- [ ] 素材包目录完整，`manifest.json` 可解析；
- [ ] `preview.png` 与运行时组装结果一致；
- [ ] 逻辑画布按 DSH 基准 525×42；源图 @2x 1050×84；有边框样式仅因真实边框加高；
- [ ] 背景固定身份部件（脸/头/logo/尾尖）不变形；连续身体/轮廓/纹理允许自然拉伸；
- [ ] 源图没有 1px 点九标记黑线；拉伸/内容区由 slice + padding 表达；
- [ ] 固定部件没有落在中间拉伸轴上；多段拉伸需求已拆挂件/多层实现；
- [ ] 装饰拉伸策略按部件拆分：可拉伸的放中间区，刚性/越界的才拆挂件；
- [ ] `slice/borderWidth/padding` 每套独立；文字区域上下 padding 基本相等（差 ≤2px）；
- [ ] 单行目标高度 42px（有边框按边框额外加高），不因装饰加高；
- [ ] 文字允许覆盖装饰，但实际文字区域对比度 ≥4.5:1（必要时用描边/阴影/底板）；
- [ ] 装饰位置覆盖顶部/两侧/四角/底部等，不只堆在底部；
- [ ] 长消息/多行：四角不变形，挂件尺寸/位置稳定；
- [ ] 文字对比度达标（WCAG AA 4.5:1）；
- [ ] 尾巴颜色正确，开关正常；
- [ ] 明暗主题可读；
- [ ] 附件/引用/pending/回声不误伤；
- [ ] 素材来源/授权记录齐全；
- [ ] 素材体积可控（内联 data URI 后 client.js 不爆炸）。

---

## 9. 版本与旧模型

| 版本 | 状态 |
| --- | --- |
| v1 九宫格 | 历史实验；完整图切片思路保留参考 |
| v2 用户自定义分层 | **暂缓**；代码保留，M3 再开放；不进入 M2 发布 |
| **v3 固定气泡样式** | **当前路线**；每套独立参数 + 可选挂件拆分 |
| **v3.1 画布/文字区修正** | 画布按 DSH **1050×84 @2x**；单行 42px；文字可覆盖图案；装饰不限于底部 |
| **v3.2 装饰拉伸策略** | 按部件分区：身份部件固定；身体/轮廓/纹理可拉伸；重复纹理可平铺；不是所有装饰都拆挂件 |

- v2 素材已归档到 `assets/recycle/bubbles/`（用户定制测试图、v2 分层背景/装饰、SOURCES）；不作为 M2 发布素材，也不随插件发布；
- 固定样式素材放在 `assets/styles/<id>/`（或最终打包目录），与 v2 实验素材分开；
- `bubble-clover-meadow` / `bubble-koi-rising` 已替换为 v3.2 / 1050×84 重做版；旧 v3.0 素材已删除。

---

## 10. 配套模板

| 文件 | 用途 |
| --- | --- |
| `assets/templates/style-package-example/` | 内部素材包示例：manifest + background + accessory + preview |
| `assets/templates/style-package-example/manifest.json` | 参数示例 |
| `assets/templates/style-package-example/README.md` | 制作说明与来源记录 |
| `assets/styles/bubble-clover-meadow/` | **固定样式「幸运草坡」**：manifest + background + preview；已加入下拉（默认仍为 DSH 默认） |
| `assets/styles/bubble-koi-rising/` | **固定样式「锦鲤跃潮」**：manifest + background + preview；已加入下拉；运行时使用压缩 WebP 内联 |
| `assets/styles/bubble-ds-whale-girl/` | **固定样式「DS鲸鱼娘·白瓷蓝」**：1050×108 带尾壳体 + 右下 38×40 外挂角色；背景下探 12px、角色位于壳体后方；已随 v0.2.1 发布 |
| `assets/styles/README.md` | 正式固定样式素材目录说明 |

> 第一批固定样式建议 3 套：星海 / 奶油纸张 / 像素游戏；素材用原创 SVG，避免版权问题。

