# Fun Toolkit · 空态、加载与页面动效规范

适用范围：新工具页面、列表与结果区的统一反馈。作为设计规范目录 v1.4.1 的增补，配合工具页框架使用。当前三个生产工具已通过 `tool-feedback.css` / `tool-feedback.js` 接入共用空态、占位、加载与轻淡入；装饰视差等扩展保留为可选示例。

[团队 Skill v1.0.1](downloads/fun-toolkit-ui-1.0.1.zip) 已合并最新模板、8 个 SVG、反馈动效资源、接入说明与验收清单；解压添加并确认 Agent 识别 `SKILL.md` 后，用 `$fun-toolkit-ui` 调用。ZIP 是自包含交付包，下载不等于安装。在线规范及团队包为当前交付；PDF v1.2 是历史快照。

## 1. 先判断状态，再选图

空态回答“这里为什么没有内容、下一步做什么”。加载说明任务仍在进行；读取失败和暂不可用属于反馈缺省态，不能用“暂无内容”掩盖错误。已有有效结果时，局部更新优先保留结果，不把整个工作台清空成插画。

| 场景 / 图形文件 | 标题模板 | 一句说明 | 操作层级 |
| --- | --- | --- | --- |
| 首次上传 · `empty-upload.svg` | 上传素材，开始制作 | 支持 [格式]，单个文件不超过 [限制]。 | 工作区主按钮“上传[素材]”；拖放只是补充入口。格式与限制必须真实校验。 |
| 等待生成 · `empty-output.svg` | 结果将在这里显示 | 调整完成后，点击“生成 [格式]”。 | 复用已有生成按钮，结果卡不重复放主按钮；自动处理工具改为“上传后自动生成”。下载暂不可用。 |
| 无匹配 · `empty-search.svg` | 没有找到匹配内容 | 试试其他关键词，或清除筛选。 | 次按钮“清除筛选”；仅在确实可清除时显示。保留搜索词和筛选条件。 |
| 暂无记录 · `empty-data.svg` | 还没有[记录名称] | 完成首次[操作]后，会在这里显示。 | 有明确创建流程时，主按钮“开始[操作]”；只读统计无记录时只说明，不制造无效按钮。 |
| 读取失败 · `empty-error.svg` | 文件暂时无法读取 | 请确认文件完整，并选择支持的 [格式]。 | 能重试时给次按钮“重试”；替换文件用现有绿色按钮。保留上一份有效素材，不显示虚假成功。 |
| 暂不可用 · `empty-offline.svg` | [功能]暂时无法连接 | 网络恢复后可以重试；当前设置已保留。 | 确实依赖网络时给次按钮“重新连接”。本地可用的工具继续使用，不能因离线封锁本地处理。 |

说明只写已经确认的原因。“网络异常”“格式不支持”“权限不足”不能互相替代；未确认原因时写“暂时无法读取，请重试或替换文件”。“设置已保留”只在实际保留时使用。

### 插图与排版

| 项目 | 统一规则 |
| --- | --- |
| 画布 | SVG 原始画布 160×120；不在图内放文字，蓝色线性轮廓、浅蓝面和少量品牌黄。六张保持同一线宽、圆角和视觉重量。 |
| 展示尺寸 | 常规 120×90 px；手机 ≤540 px 为 96×72 px；窄结果卡可用 80×60 px。始终 4:3 等比缩放，明确 width / height 预留位置。 |
| 层级 | 插图 → 标题 → 一句说明 → 必要操作。插图到标题 12 px；说明间隔 6 px；操作距说明 16 px。标题 16/24 px、说明 12/18 px，文本建议不超过 32ch。 |
| 容器 | 插图居中在已有预览或结果区；不为每种状态增加一张套娃卡。字段错误只用 16–20 px 图标与文字，不放整幅插画。 |
| 操作 | 每个工作区域只突出一个主动作；页面已有对应动作时，空结果不重复展示。手机命中区至少 44×44 px，英文允许合理换行。 |
| 可访问性 | 相邻文字已经说明状态时，图用 `alt=""`；独立传达信息的图使用简短描述。装饰碎片和图标 `aria-hidden="true"`，不重复播报“空状态插画”。 |

## 2. 加载：未知进度就不编百分比

| 形式 | 何时用 | 参数与行为 |
| --- | --- | --- |
| 按钮 Spinner | 单一动作，不能获得真实进度 | 16×16 px、2 px 线宽、800 ms 线性旋转；伴随“正在生成…”等阶段文字，按钮保持原宽并防重复提交。 |
| 区域 Spinner | 区域读取、解码或请求中 | 常规 24×24 px；大预览区可用 32×32 px，16 / 24 用 2 px 线宽，32 用 2.5 px。只占相关区域，不遮挡整个页面。 |
| 确定进度 | 已知总任务量，能读取实际完成量 | 用原生 `<progress>` 或有名称的 `role="progressbar"`；值来自真实任务。批量用“已完成 2 / 5”，未知剩余时间不估算倒计时。 |
| 不确定进度 | 任务量或耗时未知 | Spinner + 当前阶段；使用 progressbar 时省略 `aria-valuenow`。禁止随时间自动涨到 90%，禁止模拟完成冒充真实结果。 |
| Skeleton | 等待已知结构的远程列表、卡片或图文 | 占位结构与最终内容一致；1600 ms 低对比浅蓝脉冲，不同时叠加大 Spinner。非首次更新优先保留原内容。 |

点击后立即改变业务状态、设置 `aria-busy` 并阻止重复操作。为减少快速任务闪烁，Spinner / Skeleton 可在等待超过 **150 ms** 后再挂载；任务提前完成就直接呈现结果。这个延迟由业务控制，基础样式不创建计时器，也不强制最短等待；真实进度、错误、取消和已完成结果不额外延迟。

加载提示写阶段：“正在读取文件…”“正在生成 WebP…”“正在打包…”。有取消能力才显示“取消”，取消后立即反馈去向。保留现有素材、布局和按钮宽度；局部处理不抢焦点。后台或离屏暂停视觉循环，实际任务是否暂停由业务决定，不能悄悄取消。

## 3. 占位：预留位置，不假装结果

| 类型 | 视觉与尺寸 | 结束条件 |
| --- | --- | --- |
| 文本 Skeleton | 按最终排版预留行高；普通文本条 12 px，标题条 16 px；行容器保留 18 / 24 px 高度，行间 8 px。宽度固定组合，如 72% / 100% / 52%，不每次随机。 | 实际内容可用后替换；无内容切到对应空态，失败切到真实错误。 |
| 图片 Placeholder | 使用 `placeholder-image.svg`（160×120）；头像使用独立方形 `placeholder-avatar.svg`（120×120）。沿用目标图片比例和圆角；浅灰 / 浅蓝底、居中轮廓图形；未有图片来源时静态占位，真实读取时切换骨架。始终声明尺寸或 `aspect-ratio`。 | `load` / 解码成功后显示真实图；不能把占位图当成可下载文件。 |
| 图片损坏 | 保留原图位置，静态图片图标 + “图片暂时无法显示”。可恢复时给“重试”，另保留真实文件信息。 | 只在实际失败后显示；不继续脉冲，不无限重试，不暴露断图图标。 |
| 透明棋盘 | 表示真实图像的透明区域。 | 属于预览背景，既不是加载占位，也不导出到透明文件里。 |

Skeleton 内部形状 `aria-hidden="true"`，由区域名称与一个状态文字说明加载，不逐条播报。用户未上传素材时用空态，不用 Skeleton 假装后台正在生成。

## 4. 出入场：轻、短、不耽误操作

| 场景 | 统一参数 | 使用边界 |
| --- | --- | --- |
| 内容出现 | 180 ms；opacity 0→1，Y 6→0 px；沿用 `cubic-bezier(.16,1,.3,1)`。 | 标题辅助内容、说明、卡片或新结果的视觉出现；只做 opacity / transform。 |
| 内容退场 | 120 ms；opacity 1→0，Y 0→3 px。 | 结束后隐藏 / 移除；先处理焦点与交互，不让透明按钮仍可点击或 Tab 到达。 |
| 少量错峰 | 相邻启动间隔 ≤40 ms；最多前三项错峰，延迟总预算 ≤120 ms。 | 同一屏卡片或装饰；更多项目直接出现，不给长列表逐项排队。 |
| 业务反馈 | 状态、进度、错误和按钮可用性立即更新。 | 不等待动画后才显示错误、接受点击或启用真实下载。结果区可轻入场，业务状态不等待它结束。 |
| 退场被打断 | 新的出现操作取消旧退场及旧回调。 | 快速切换不会让新内容被旧动画回调隐藏；任务状态仍由业务驱动。 |

普通内容使用这套轻位移；生产工具的 Canvas 区域采用 160 ms、opacity .35→1 的纯淡入，不使用 transform，忙碌提示 120 ms 淡出。弹窗继续使用已有 260 ms 轻弹性和 7 px 玻璃背景规范，不把弹窗弹性复制到每一张卡片。显式切换或新内容出现可触发，避免每次滚回视口都重复播放。页面仍能在无脚本与减少动画时阅读，禁止给整页正文设初始透明而等待 JS 解锁。

## 5. 视差：只给装饰一点层次

| 类型 | 上限与触发 | 停止与降级 |
| --- | --- | --- |
| Pointer 视差 | 单个装饰层 X / Y 各不超过 6 px；仅 hover:hover + pointer:fine，围绕组件中心计算。 | 指针离开回到原位；触屏、粗指针、≤540 px 和减少动画静态。 |
| Scroll 视差 | 装饰层 Y 位移不超过 12 px；深度 0–1，整体幅度受上限约束。 | 离屏 / 页面后台停止更新；重新可见时按当前位置恢复。 |
| 执行方式 | 在动画帧中合并输入，用 transform 更新；只修改专用装饰层。 | 不在每个滚动事件中同步读写布局；无动画帧任务时不维持空循环，销毁时移除监听和观察。 |

可用于规范页头、首页品牌插画和空态的独立装饰碎片。**不作用于工具 Canvas、裁切框、图片坐标、输入框、按钮文字、时间轴或导出层**；不改素材像素、不产生位移后的命中偏差。装饰层 `pointer-events:none`、`aria-hidden="true"`，不得承担唯一信息。

## 6. 减少动画与可访问性

- `prefers-reduced-motion:reduce`：入退场立即完成，视差归零，脉冲与旋转停止，Skeleton 为静态浅色块；继续显示“正在处理…”和实际进度，不能冻结成“看起来已完成”。
- 视觉循环在离屏和页面后台暂停。业务状态、可访问文字和任务结果继续按实际情况更新；动画暂停不等于取消生成。
- 加载区域有可访问名称并在真实任务期间 `aria-busy="true"`；保留一个外部或不受 busy 延迟的 `role="status"` / `aria-live="polite"` 状态消息，按阶段更新，不每帧重复播报。
- 只有需要立即处理的阻断错误才使用 `role="alert"`；同一提示不同时重复放在多个 live region。不要把整个卡片设成 live region。
- 原生进度有 label；自定义进度设置名称、范围与真实值。图形、颜色、转圈和百分比都不能替代文字说明。
- 状态切换不抢焦点。手动删除 / 关闭含焦点的内容前，移到合适的存续入口；退出交互区及时不可交互。弹窗仍按原规范进入、圈定并返回焦点。
- 切语言同步标题、说明、恢复动作、进度与 ARIA 名称；文件名、格式和 px 不翻译。320 px、长英文、200% 缩放仍可读可操作。

## 7. 可复用资源与接入

- `feedback-motion.css` / `feedback-motion.js` 为共用原生反馈基础，脚本 defer 后自动初始化。当前三个生产工具由根目录 `tool-feedback.css` / `tool-feedback.js` 映射业务状态，模板已复用相同基础；单独引入基础文件不会自动提供任务状态、编码或统计。
- 公共对象 `window.FunToolkitFeedback`：`init(root)` 幂等扫描新节点；`refresh()` 重算环境；`setPaused(true|false)` 暂停持续装饰，不阻止主动入退场；`enter(el)` / `exit(el)` / `replay(el)` 返回 Promise；`destroy()` 释放监听、观察与动画帧。
- `data-ft-activity` 标记需要可见性管理的 Spinner / Skeleton；`data-ft-reveal="fade"` 仅淡入淡出，`rise` 采用 6 / 3 px 位移。`data-ft-parallax="pointer|scroll"` 容器内以 `data-ft-layer` 标记专用装饰层，可用 `data-ft-depth="0..1"` 控制深度。
- 150 ms 加载展示延迟、任务状态、进度和焦点移动由业务实现；演示按钮不提供编码、网络请求或统计。

## 8. 给开发 / Agent 的复制需求

> 使用 `$fun-toolkit-ui`，请按 Fun Toolkit 反馈与动效规范实现「[页面 / 模块]」。从首次上传、待产出、无匹配、暂无记录、读取失败、暂不可用六类选择需要的状态；使用配套 SVG，常规展示 120×90，手机 96×72，文字与真实动作配套。未知进度用 16 / 24 / 32 px Spinner，800 ms；有实际完成量才显示进度。加载立即防重并声明 busy，视觉指示可延迟 150 ms，完成与错误不等动画。Skeleton 固定最终结构，1600 ms 轻脉冲；图片区分待载入、损坏和透明棋盘。普通内容入场 180 ms / 6 px、退场 120 ms / 3 px；生产 Canvas 区域 160 ms 纯透明度淡入、忙碌提示 120 ms 淡出，不修改精确坐标。错峰间隔不超过 40 ms、最多三项、总延迟不超过 120 ms。视差只用于装饰，Pointer ≤6 px、Scroll ≤12 px，离屏 / 后台暂停，手机 / 减动静态；不得变换 Canvas、裁切坐标、控件或导出图层。补齐可访问名称、状态播报、焦点和中英；业务事件真实驱动，未接能力清楚标为示例。

### 验收清单

1. 六类只呈现实际需要的状态；没有内容、加载、失败可一眼区分；每个恢复入口可用且与原因对应。
2. 配套插图等比、无图中文字；空预览与结果卡无高度跳动，文件失败不清空上一份有效素材。
3. 快速任务不闪 Skeleton；慢任务持续有文字；百分比只来自真实进度；未实现取消、重试或下载不显示可用。
4. 图片成功、损坏、缺失三种场景可验证；透明棋盘不进入实际导出。
5. 入场、退场与快速往返不残留透明点击区；动画不会延迟错误、成功或下载；长列表不逐项排队。
6. 移动、粗指针与减动无视差；离屏 / 后台视觉循环暂停；切工具 / 销毁后监听、Object URL 和动画释放。
7. 320 / 390 / 768 / 1440 px、长英文、200% 缩放与键盘流程可用；空态图不重复播报，任务完成后 busy 恢复 false。
8. 真实生成前后输出尺寸、像素、透明度与内容一致；规范演示不调用生成 / 下载统计接口。

本增补统一的是新组件的接入标准与可复用示例；三个生产工具保留各自真实业务流程。现有弹窗、主按钮扫光、工具框架仍沿用原规范，避免另起一套动效语言。


---

# Fun Toolkit · Feedback / Motion primitives

这两个文件是规范演示、最新模板与三个生产工具共用的原生反馈基础。生产接入由 `tool-feedback.css` / `tool-feedback.js` 适配业务状态；基础本身不提供生成任务、文件下载、统计、存储或网络请求。

## 直接接入

```html
<link rel="stylesheet" href="feedback-motion.css">
<script src="feedback-motion.js" defer></script>
```

脚本在 DOM 就绪时自动 `init(document)`。重复 `init(root)` 幂等，仅追加扫描新元素。移除整个示例前调用 `destroy()`；它清理监听、观察器、排队的 RAF、当前进入退出动画，并将持续装饰暂停。多个小示例共用一个控制器，暂停是全局的。

## Loader / Progress

```html
<div data-ft-activity role="status">
  <span class="ft-spinner" data-size="24" aria-hidden="true"></span>
  <span>正在处理文件…</span>
</div>
<label for="export-progress">已处理 2 / 4 个文件</label>
<progress id="export-progress" class="ft-progress" value="2" max="4">2 / 4</progress>
```

- `.ft-spinner[data-size="16|24|32"]`，默认 24px、2px 蓝色圆弧（32px 用 2.5px），800ms 线性旋转。
- 每个持续活动容器使用 `data-ft-activity`。JS 自动写 `data-ft-running="true|false"`，CSS 默认暂停；无 JS 时保留静态加载符号和可见状态文字。
- `role=status` 放稳定且有文字的状态容器；旋转图形 `aria-hidden=true`。不要每帧播报、不把整页设为 live region。
- 未知进度使用 spinner + 具体过程文字。只有真实可测的任务比例才更新原生 `progress` 的 `value/max`。本库不模拟百分比，不对未知 progress 添加自定义动画。
- 立即记录 busy 和阻止重复提交。视觉 spinner 可以由业务延后约 150ms 出现以防闪烁；任务先完成就不显示。此库不设置展示计时器、最短等待或业务取消逻辑。

## Skeleton / 稳定图片占位

```html
<div data-ft-activity aria-hidden="true">
  <div class="ft-skeleton" style="aspect-ratio:16/9"></div>
  <div class="ft-skeleton-line" style="width:65%;margin-top:12px"></div>
</div>
<div class="ft-media-placeholder" data-ft-image-state="loading"
     data-ft-activity style="--ft-placeholder-ratio:772 / 200">
  <img src="banner.webp" width="772" height="200" alt="生成的房间 Banner">
  <span class="ft-skeleton" aria-hidden="true"></span>
  <span data-ft-image-fallback>图片无法载入，请重试。</span>
</div>
```

- 1600ms `ease-in-out` 低幅浅蓝脉冲；没有扫光。减少动画时只剩静态底色。
- `aspect-ratio` 与真实 `width/height` 预留布局；加载完成替换内容，不插入新高度。缺少 `aspect-ratio` 的旧浏览器需由业务设置确定高度。
- `data-ft-image-state="loading|loaded|error"` 仅是 CSS 状态契约。图片 `load/error` 事件及竞态处理由接入方负责；本库不读取图片，也不假设已载入。也可以像规范演示页一样用自己的图片状态属性。
- Skeleton 不表达文件成功；不要放假缩略图或假下载。aria-hidden 的骨架旁应有一处有意义的状态说明。

## 进入 / 退出 / 重播

```html
<button data-ft-action="enter" data-ft-target="demo-card">显示</button>
<button data-ft-action="exit" data-ft-target="demo-card">隐藏</button>
<button data-ft-action="replay" data-ft-target="demo-card">重播</button>
<article id="demo-card" data-ft-reveal="rise">内容</article>
```

- `data-ft-reveal="rise"`：进入 180ms，从 opacity 0 + Y 6px 到 opacity 1 + Y 0；退出 120ms，从 opacity 1 到 0 + Y 3px。
- `data-ft-reveal="fade"`：时间相同，只变化透明度。
- 原生 WAAPI，缓动 `cubic-bezier(.16,1,.3,1)`；完成退出后才设置 `hidden`，真正退出布局和辅助技术树。
- 开始退出时立即设为 `inert`，透明过程中不能点击或通过 Tab 进入。进入取消、动画完成、减少动画和销毁都会恢复动画前的 inert；业务本来设为 inert=true 的内容不会被强行启用。
- 快速退出后再进入会取消旧动画，旧 promise 完成不会隐藏新内容。重播也是用户触发的一次动画，不会无限循环。
- 使用单独的外层包装作为动画目标，避免覆盖已有 transform。目标内如有当前焦点，调用方应先将焦点移到仍可见的触发器，再退出；库不猜测焦点去向。
- 默认标记不自动隐藏内容，无 JS 可直接阅读。没有 `Element.animate` 或开启减少动画时立即切换最终状态。不支持原生 inert 的旧浏览器直接隐藏退出内容，避免保留可操作的透明元素。

```js
const feedback = window.FunToolkitFeedback;
await feedback.enter(document.getElementById('demo-card'));
await feedback.exit(document.getElementById('demo-card'));
await feedback.replay(document.getElementById('demo-card'));
feedback.init(newSection); // 追加组件后扫描
feedback.refresh(); // 状态/布局发生外部变化后重新评估
feedback.setPaused(true); // 暂停所有持续装饰，视差回到零位
feedback.setPaused(false);
feedback.destroy();
```

`enter/exit/replay` 返回 `Promise<boolean>`：当前请求完成为 true，因新请求取消为 false。减少动画时立即得到 true。运行中将系统改为减少动画，会取消动画并直接达到最新请求的最终状态（被取消动画的 promise 为 false）。

## 可选视差

```html
<div data-ft-parallax="pointer">
  <div data-ft-layer data-ft-depth="1">纯装饰，不承载关键操作</div>
</div>
<div data-ft-parallax="scroll">
  <div data-ft-layer data-ft-depth="0.7">另一层装饰</div>
</div>
```

- Pointer 最大 X/Y 各 6px；scroll 最大 Y 12px；`data-ft-depth` 限在 0–1，未设置默认 1。内部层只接位移、设置 pointer-events:none，外层负责裁切和原有布局，装饰层不阻挡输入。
- Scroll 依据目标与视口中心的相对距离计算，不锁滚动，不主动 `scrollTo`，不做 sticky 滚动叙事。
- 只在 `hover:hover`、`pointer:fine`、视口 > 540px、未减少动画、页面可见且元素在屏幕内时启用。触摸/粗指针/手机/减少动画时直接零位。
- Pointermove/scroll 共享最多一个排队的 RAF，只在事件发生时更新，没有常驻 RAF 循环。后台会取消排队帧并复位；离屏加载暂停、视差复位。恢复后按真实位置重新计算，指针视差需再次移动触发。
- `IntersectionObserver` 不可用时，在滚动/resize/refresh 时用几何范围检测降级；无 matchMedia 时关闭视差；无 RAF 时事件内同步更新，不丢失基本内容。
- 视差不能用于文字、表单、主要按钮、精确裁切手柄、播放器控制条等需要稳定位置的内容。

## 自检与支持边界

运行：

```sh
node check.cjs
```

测试以 Node 标准库 `vm` 执行真实 `feedback-motion.js`，用最小 DOM/WAAPI/IO/RAF 平台替身验证：显示与离屏、后台取消、减少动画、全局暂停、指针/滚动限幅、单帧调度、快速 exit→enter 竞争、退出即时 inert 和正确恢复、最终 hidden、清理、无 IO/RAF/WAAPI 的降级。

这不等同于真实浏览器视觉验收；根代理还需检查桌面/手机渲染、系统减少动画、实际控制按钮与控制台。此基础不提供图片预加载器、数据获取、完整任务状态机、焦点路由、路由过渡、拖拽或业务进度算法。


---

# Fun Toolkit 空态与占位图资产

## 交付与用途

源文件位于 `outputs/spreadfun-room-studio/design-system/assets/feedback/`。原生 SVG、可编辑、无文字、无 Emoji、无外部图片 / 字体 / 脚本 / 滤镜，不依赖库，不带内置动画。

| 文件 | 固有尺寸 / viewBox | 使用场景 | 相邻文案示例 |
| --- | --- | --- | --- |
| `empty-upload.svg` | 160 × 120 | 尚未选择或拖入素材 | 上传素材，开始制作 |
| `empty-output.svg` | 160 × 120 | 尚未生成、尚无可下载结果 | 生成后，结果会显示在这里 |
| `empty-search.svg` | 160 × 120 | 有筛选条件但没有匹配项 | 没有找到匹配结果 |
| `empty-data.svg` | 160 × 120 | 无历史记录、统计数据、列表项 | 暂无记录 |
| `empty-error.svg` | 160 × 120 | 无法解码、素材读取或处理失败 | 素材读取失败，请重新选择 |
| `empty-offline.svg` | 160 × 120 | 需要网络的内容断连、服务暂不可用 | 暂时无法连接，请稍后重试 |
| `placeholder-image.svg` | 160 × 120 | 已知需要图片但当前没有图片来源的静态占位 | 由容器的相邻标签描述 |
| `placeholder-avatar.svg` | 120 × 120 | 缺少作者或用户头像 | 由相邻姓名描述 |

空态不是错误码，也不是操作入口。文案应说明真实原因与可执行下一步。读取失败与网络不可用使用同一套蓝灰图形；错误严重性由明确的相邻文案与语义状态承担，不因大面积红图形打断工具工作区。纯本地工具不应仅因浏览器离线显示不可用状态。

## 视觉规则

- 空态采用同一 160 × 120 画板，中心浅蓝圆形底块半径 44，位置 `(80, 60)`；相同地线位于 `y=107`。不同图形保持相近的主体面积与视觉重心。
- 线宽默认 2，关键动作符号 2.5，圆角端点与连接；不用立体阴影、发光、渐变或复杂纹理。
- 品牌蓝 `#136BFF` 只强调关键符号；浅蓝 `#EAF3FF`、`#DCEBFF` 和 `#CFE5FF` 建立背景与层级；黄色 `#FFC928` 只做小点缀；白色 `#FFFFFF` 作为图形表面。
- 插画描边使用蓝灰 `#6A91C2`，辅助线 `#C4D9F3` / `#A8C4E8`。这些是低对比装饰色，不能用作交互边线、正文色或错误文本色。
- 统一保留透明画布，让图形适配白色卡片与 `#F5F5F7` 页面背景；不要给每张空态增加独立底卡、外框或阴影。
- 图形保持静止；需要状态切换时由外层容器按统一淡入规范处理，不让图中箭头或装饰点单独无休止循环。

## 展示尺寸与容器

常规空态默认展示 120 × 90；手机 96 × 72；窄输出区域可用 80 × 60。160 × 120 是画板尺寸，不是要求每个区域都显示这么大。使用 `display:block; object-fit:contain; flex:none`，按宽高比完整显示，不能裁切、拉伸、改变色相或旋转整张图。

图片占位图采用 4:3 画板；容器可按具体产品目标比例预留尺寸，图形完整居中。建议容器使用相同浅蓝底，图形使用 `object-fit:contain`。头像占位图正圆显示，不能用作用户已上传头像的默认蒙版内容。

占位图与加载骨架职责不同：占位图表示“当前无图片来源”，骨架表示“内容正在读取、即将替换”。上传素材已有有效预览时，不应在后台重新编码期间将有效预览替换为空态。

## 无障碍与接入

当同一区域已有标题 / 说明文字时，使用 `<img src="…/empty-upload.svg" width="120" height="90" alt="">`，让图形作为装饰，避免读屏重复播报。不要让图形进入 Tab 顺序。

若图形是唯一传达状态的信息，必须在外层补上可读文本或精确替代文本；不能只靠图形、颜色区分状态。占位头像已有相邻姓名时 `alt=""`；真实头像有语义时使用人物姓名作为替代文本。

加载 / 错误语义由组件负责：例如处理区域 `aria-busy="true"`，稳定的提示节点使用 `role="status"`；只有即时失败且需要立即获知时使用 `role="alert"`。不要给整张图使用 live region，也不要因动画重复播报。

以 `<img>` 引入源文件可避免现有网站通用 `svg { width:20px; … }` 图标样式误伤插画。若必须内联，需对插画容器显式设置尺寸和 `fill / stroke`，保持其不同于 20 px 的功能图标。

## 交付展示建议

规范页用 3 × 2 卡片展示六个空态，手机单列或双列；每张卡片依次为图、场景名称、精简场景说明，操作入口用网站现有按钮组件。两个占位图单独并排展示，并明确与骨架屏的区别。

各场景的最小成品结构是：图形 → 12 px 间距 → 一行标题 → 6–8 px 间距 → 1–2 行原因或下一步 → 16 px 间距 → 至多一个主操作。图形内容不能替代按钮文字，也不能暗示已经上传或已经生成。

当前三个工具已将所需图形、占位、加载与轻淡入接入真实流程；未使用的无匹配、离线与装饰视差等资源保留供后续场景选择。新工具仍需按实际任务接入状态、恢复操作与统计，不能仅凭引用图形宣称业务已经完成。

## 本次检查

- 8 个 SVG 均可被严格 XML 解析。
- 6 个空态均为 `viewBox="0 0 160 120"`；两个占位图分别为 160 × 120、120 × 120。
- 无外链、脚本、字体、`foreignObject`、`image`、动画元素或滤镜；无 SVG ID，因此多个内联实例不会产生 ID 冲突。
- 所有空态 alpha 边界均位于画板内部，最接近边缘的图形留有至少 12 px 外侧空白；地线最下像素在 108 px 以内，未裁切描边。
- 通过本地 Sharp 以 3 倍倍率渲染，并检查组合图板：`work/design-spec/feedback-assets-board.png`。PNG 仅用于 QA，不替代 SVG 交付源文件。


## 三工具生产接入记录 · 2026-10-23

房间背景、头像框与Banner已接入对应的空态图、头像/缩略图占位、统一Spinner与Skeleton。实际业务区采用160ms、opacity .35→1的轻淡入，忙碌提示120ms淡出，不对Canvas、裁切或导出做transform。房间背景显示真实文件完成量，SVGA逐帧和语言切换不重播动画。共享生产适配层为站点根目录tool-feedback.css / tool-feedback.js；业务状态由各工具调用，错误提示与旧有效结果仍由原工具维护。视差、错峰和默认180ms/6px模式保留为后续可选规范，不自动用于工作画布。
