chore: ruler files update

Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
2026-05-24 21:03:49 -04:00
parent 97b3ddd653
commit abb472c83d
303 changed files with 46670 additions and 25369 deletions

View File

@@ -0,0 +1,506 @@
# Animation Best Practices · 正向动画设计语法
> 基于 Anthropic 官方三支产品动画Claude Design / Claude Code Desktop / Claude for Word
> 的深度拆解,提炼出的"Anthropic 级"动画设计规则。
>
> 配套 `animation-pitfalls.md`(避坑清单)使用——本文件是「**应该这样做**」,
> pitfalls 是「**不要这样做**」,两者正交,都要读。
>
> **约束声明**:本文件只收录**运动逻辑和表达风格****不引入任何品牌色具体色值**。
> 色彩决策走 §1.a 核心资产协议(从品牌 spec 抽取)或「设计方向顾问」
> 20 种哲学各自的配色方案)。本 reference 讨论的是「**怎么动**」,不是「**什么色**」。
---
## §0 · 你是谁 · 身份与品味
> 在读后面任何技术规则之前,先读这一节。规则是**从身份涌现的**——
> 不是相反。
### §0.1 身份锚点
**你是一个研究过 Anthropic / Apple / Pentagram / Field.io 运动档案的 motion designer。**
做动画时,你不是在调 CSS transition——你是在用数字元素**模拟一个物理世界**
让观众的潜意识相信「这是有重量、有惯性、会溢出的物体」。
你不做 PowerPoint 式动画。你不做「fade in fade out」动画。你做的动画**让人相信屏幕
是一个可以伸手进去的空间**。
### §0.2 核心信念3 条)
1. **动画是物理学,不是动画曲线**
`linear` 是数字,`expoOut` 是物体。你相信屏幕上的像素值得被当作"物体"对待。
每一条 easing 的选择,都是在回答「这个元素有多重?摩擦系数多大?」的物理问题。
2. **时间分配比曲线形状更重要**
Slow-Fast-Boom-Stop 是你的呼吸。**均匀节奏的动画是技术演示,有节奏的动画是叙事。**
在正确的时刻慢下来——比在错误的时刻用对 easing 更重要。
3. **礼让观众,比炫技更难**
关键结果前停 0.5 秒是**技术**,不是妥协。**让人类大脑有反应时间,是动画师的最高素养。**
AI 默认会做一个没有停顿的、信息密度满格的动画——那是新手。你要做的是克制。
### §0.3 品味标准 · 什么是美
你对「好」和「great」的判断标准如下。每一条都有**识别方法**——当你看到一个候选动画时,
用这些问题判断它是否达标,而不是机械对照 14 条规则。
| 美的维度 | 识别方法(观众反应) |
|---|---|
| **物理重量感** | 动画结束时,元素"**落**"得稳——不是"**停**"在那里。观众潜意识觉得"这有重量" |
| **礼让观众** | 关键信息出现前有一个可感的 pause≥300ms——观众来得及"**看见**"再继续 |
| **留白** | 收尾是戛然而止 + hold不是 fade to black。最后一帧清晰、肯定、有决定感 |
| **克制** | 全片只有一处「120% 精致」,其余 80% 恰到好处——**到处炫技是廉价的信号** |
| **手感** | 弧线(不是直线)、不规律(不是 setInterval 的机械节奏)、有呼吸感 |
| **敬意** | 展示 tweak 的过程、展示 bug 的修复——**不藏工作、不给"魔法"**。AI 是协作者不是魔术师 |
### §0.4 自检 · 观众第一反应法
做完一支动画,**观众看完第一反应是什么?**——这是你唯一要优化的指标。
| 观众反应 | 评级 | 诊断 |
|---|---|---|
| "看起来挺流畅的" | good | 合格但无特色,你在做 PowerPoint |
| "这个动画真顺" | good+ | 技术对了,但没惊艳 |
| "这个东西看起来真的像**从桌面上浮起来的**" | great | 你触到了物理重量感 |
| "这不像是 AI 做的" | great+ | 你触到了 Anthropic 的门槛 |
| "我想**截图**发朋友圈" | great++ | 你做到了让观众主动传播 |
**great 和 good 的区别,不在于技术正确度,在于品味判断**。技术正确 + 品味对 = great。
技术正确 + 品味空 = good。技术错误 = 没入门。
### §0.5 身份和规则的关系
下面 §1-§8 的技术规则,是这套身份在具体场景的**执行手段**——不是独立规则清单。
- 遇到规则没覆盖的场景 → 回到 §0用**身份**判断,不要瞎猜
- 遇到规则之间有冲突 → 回到 §0用**品味标准**判断哪条更重要
- 想破一条规则 → 先回答:"这样做符合 §0.3 哪一条美?" 答得上就破,答不上就别破
好。继续读下去。
---
## 总览 · 动画是物理学的三层展开
大多数 AI 生成动画有廉价感的根源是——**它们表现得像「数字」不是「物体」**。
真实世界的物体有质量、有惯性、有弹性、会溢出。Anthropic 三支片子的「高级感」根源,
就在于给数字元素一套**物理世界的运动规则**。
这套规则有 3 个层次:
1. **叙事节奏层**Slow-Fast-Boom-Stop 的时间分配
2. **运动曲线层**Expo Out / Overshoot / Spring拒绝 linear
3. **表达语言层**展示过程、鼠标弧线、Logo 形变收束
---
## 1. 叙事节奏 · Slow-Fast-Boom-Stop 5 段结构
Anthropic 三支片子无一例外遵循这个结构:
| 段 | 占比 | 节奏 | 作用 |
|---|---|---|---|
| **S1 触发** | ~15% | 慢 | 给人类反应时间,建立真实感 |
| **S2 生成** | ~15% | 中 | 视觉惊艳点出现 |
| **S3 过程** | ~40% | 快 | 展示可控性/密度/细节 |
| **S4 爆发** | ~20% | Boom | 镜头拉远/3D pop-out/多面板涌现 |
| **S5 落幅** | ~10% | 静 | 品牌 Logo + 戛然而止 |
**具体时长映射**15 秒动画为例):
S1 触发 2s · S2 生成 2s · S3 过程 6s · S4 爆发 3s · S5 落幅 2s
**禁止做的事**
- ❌ 均匀节奏(每秒信息密度一样)— 观众疲劳
- ❌ 持续高密度 — 无峰值无记忆点
- ❌ 渐弱收尾fade out 到透明)— 应该**戛然而止**
**自检**:用纸笔画 5 个 thumbnail每个代表一段的高潮画面。如果 5 张图差别不大,
说明节奏没做出来。
---
## 2. Easing 哲学 · 拒绝 linear拥抱物理
Anthropic 三支片子的所有动效都用带「阻尼感」的贝塞尔曲线。默认的 cubic easeOut
`1-(1-t)³`**不够锐**——起步不够快、停顿不够稳。
### 三个核心 Easinganimations.jsx 已内置)
```js
// 1. Expo Out · 迅速启动缓慢刹车(最常用,默认主 easing
// 对应 CSS: cubic-bezier(0.16, 1, 0.3, 1)
Easing.expoOut(t) // = t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
// 2. Overshoot · 带弹性的 toggle/按钮弹出
// 对应 CSS: cubic-bezier(0.34, 1.56, 0.64, 1)
Easing.overshoot(t)
// 3. Spring 物理 · 几何体归位、自然落位
Easing.spring(t)
```
### 用法映射
| 场景 | 用哪个 Easing |
|---|---|
| 卡片 rise-in / 面板入场 / Terminal fade / focus overlay | **`expoOut`**(主 easing最常用 |
| Toggle 切换 / 按钮弹出 / 强调交互 | `overshoot` |
| Preview 几何体归位 / 物理落位 / UI 元素抖弹 | `spring` |
| 持续运动(如鼠标轨迹插值) | `easeInOut`(保留对称性) |
### 反直觉洞察
大多数产品宣传片的动画**太快太硬**。`linear` 让数字元素像机器,`easeOut` 是基础分,
`expoOut` 才是「高级感」的技术根源——它给数字元素一种**物理世界的重量感**。
---
## 3. 运动语言 · 8 条共性原则
### 3.1 底色不用纯黑纯白
Anthropic 三支片子没有一支用 `#FFFFFF``#000000` 做主底色。**带色温的中性色**
(或暖或冷)有"纸张 / 画布 / 桌面"的物质感,削弱机器感。
**具体色值决策**走 §1.a 核心资产协议(从品牌 spec 抽取)或「设计方向顾问」
20 种哲学各自的底色方案)。本 reference 不给具体色值——那是**品牌决策**,不是运动规则。
### 3.2 Easing 绝不是 linear
见 §2。
### 3.3 Slow-Fast-Boom-Stop 叙事
见 §1。
### 3.4 展示「过程」而非「魔法结果」
- Claude Design 展示 tweak 参数、拖滑块(不是一键生成完美结果)
- Claude Code 展示代码报错 + AI 修复(不是一次成功)
- Claude for Word 展示 Redline 红删绿增的修改过程(不是直接给最终稿)
**共同潜台词**:产品是**协作者、结对工程师、资深编辑**——不是一键魔术师。
这精准打击专业用户对「可控性」和「真实性」的痛点。
**反 AI slop**AI 默认会做「魔法一键成功」的动画(一键生成 → 完美结果),
这是通用公约数。**反过来做**——展示过程、展示 tweak、展示 bug 和修复——
是品牌识别度的来源。
### 3.5 鼠标轨迹人工绘制(弧线 + Perlin Noise
真人鼠标运动不是直线,是「起步加速 → 弧线 → 减速修正 → 点击」。
AI 直接直线插值的鼠标轨迹**有潜意识排斥感**。
```js
// 二次贝塞尔曲线插值(起点 → 控制点 → 终点)
function bezierQuadratic(p0, p1, p2, t) {
const x = (1-t)*(1-t)*p0[0] + 2*(1-t)*t*p1[0] + t*t*p2[0];
const y = (1-t)*(1-t)*p0[1] + 2*(1-t)*t*p1[1] + t*t*p2[1];
return [x, y];
}
// 路径:起点 → 偏离中点 → 终点(做弧线)
const path = [[100, 100], [targetX - 200, targetY + 80], [targetX, targetY]];
// 再叠加极小的 Perlin Noise±2px制造「手抖」
const jitterX = (simpleNoise(t * 10) - 0.5) * 4;
const jitterY = (simpleNoise(t * 10 + 100) - 0.5) * 4;
```
### 3.6 Logo「形变收束」(Morph)
Anthropic 三支片子的 Logo 出场**都不是简单 fade-in**,是**前一个视觉元素形变而来**。
**共同模式**:倒数 1-2 秒做 Morph / Rotate / Converge让整个叙事在品牌点上「坍缩」。
**低成本实现**(不用真 morph
让前一个视觉元素「坍缩」成一个色块scale → 0.1,向中心 translate
色块再「膨胀」展开成 wordmark。过渡用 150ms 快切 + motion blur
`filter: blur(6px)``0`)。
```js
<Sprite start={13} end={14}>
{/* 坍缩:前一个元素 scale 0.1opacity 保持filter blur 增加 */}
const scale = interpolate(t, [0, 0.5], [1, 0.1], Easing.expoOut);
const blur = interpolate(t, [0, 0.5], [0, 6]);
</Sprite>
<Sprite start={13.5} end={15}>
{/* 膨胀Logo 从色块中心 scale 0.1 → 1blur 6 → 0 */}
const scale = interpolate(t, [0, 0.6], [0.1, 1], Easing.overshoot);
const blur = interpolate(t, [0, 0.6], [6, 0]);
</Sprite>
```
### 3.7 衬线 + 无衬线双字体
- **品牌 / 旁白**:衬线(有「学术感 / 出版物感 / 品位」)
- **UI / 代码 / 数据**:无衬线 + 等宽
**单一字体都是不对的**。衬线给「品位」,无衬线给「功能」。
具体字体选择走品牌 specbrand-spec.md 的 Display / Body / Mono 三栈)或设计方向
顾问的 20 种哲学。本 reference 不给具体字体——那是**品牌决策**。
### 3.8 焦点切换 = 背景减弱 + 前景锐化 + Flash 引导
焦点切换**不只是**降低 opacity。完整配方是
```js
// 非焦点元素的滤镜组合
tile.style.filter = `
brightness(${1 - 0.5 * focusIntensity})
saturate(${1 - 0.3 * focusIntensity})
blur(${focusIntensity * 4}px) // ← 关键:加 blur 才真的"退后"
`;
tile.style.opacity = 0.4 + 0.6 * (1 - focusIntensity);
// 焦点完成后在焦点位置做 150ms Flash highlight 引导视线回流
focusOverlay.animate([
{ background: 'rgba(255,255,255,0.3)' },
{ background: 'rgba(255,255,255,0)' }
], { duration: 150, easing: 'ease-out' });
```
**为什么 blur 是必须的**:只靠 opacity + brightness焦点外的元素还是「锐利」的
视觉上没有「退到后景」的效果。blur(4-8px) 让非焦点真的退一层景深。
---
## 4. 具体运动技巧(可直接抄的代码片段)
### 4.1 FLIP / Shared Element Transition
按钮「膨胀」成输入框,**不是**按钮消失 + 新面板出现。核心是**同一个 DOM 元素**在
两种状态间 transition不是两个元素 cross-fade。
```jsx
// 用 Framer Motion layoutId
<motion.div layoutId="design-button">Design</motion.div>
// ↓ 点击后同 layoutId
<motion.div layoutId="design-button">
<input placeholder="Describe your design..." />
</motion.div>
```
原生实现参考 https://aerotwist.com/blog/flip-your-animations/
### 4.2「呼吸式」展开width→height
面板展开**不是同时拉 width 和 height**,而是:
- 前 40% 时间:只拉 width保持 height 小)
- 后 60% 时间width 保持,撑 height
这模拟物理世界「先展开,再注水」的感觉。
```js
const widthT = interpolate(t, [0, 0.4], [0, 1], Easing.expoOut);
const heightT = interpolate(t, [0.3, 1], [0, 1], Easing.expoOut);
style.width = `${widthT * targetW}px`;
style.height = `${heightT * targetH}px`;
```
### 4.3 Staggered Fade-up30ms stagger
表格行、卡片列、列表项入场时,**每个元素延迟 30ms**`translateY` 从 10px 回到 0。
```js
rows.forEach((row, i) => {
const localT = Math.max(0, t - i * 0.03); // 30ms stagger
row.style.opacity = interpolate(localT, [0, 0.3], [0, 1], Easing.expoOut);
row.style.transform = `translateY(${
interpolate(localT, [0, 0.3], [10, 0], Easing.expoOut)
}px)`;
});
```
### 4.4 非线性呼吸 · 关键结果前悬停 0.5s
机器执行快且连贯,但**关键结果出现前悬停 0.5 秒**,让观众大脑有反应时间。
```jsx
// 典型场景AI 生成完 → 悬停 0.5s → 结果浮现
<Sprite start={8} end={8.5}>
{/* 0.5s 停顿——什么也不动,让观众盯着加载状态 */}
<LoadingState />
</Sprite>
<Sprite start={8.5} end={10}>
<ResultAppear />
</Sprite>
```
**反例**AI 生成完立刻无缝切到结果——观众没反应时间,信息流失。
### 4.5 Chunk Reveal · 模拟 token 流式
AI 生成文字**不要用 `setInterval` 单字符蹦出**(像老电影字幕),要用 **chunk reveal**
——一次出现 2-5 个字符,间隔不规律,模拟真实 token 流式输出。
```js
// 分 chunk 而不是分字符
const chunks = text.split(/(\s+|,\s*|\.\s*|;\s*)/); // 按词 + 标点切
let i = 0;
function reveal() {
if (i >= chunks.length) return;
element.textContent += chunks[i++];
const delay = 40 + Math.random() * 80; // 不规律 40-120ms
setTimeout(reveal, delay);
}
reveal();
```
### 4.6 Anticipation → Action → Follow-through
Disney 12 原则中的 3 条。Anthropic 用得很显式:
- **Anticipation**(预备):动作开始前有小反向动作(按钮轻微缩小再弹出)
- **Action**(动作):主要动作本身
- **Follow-through**(跟随):动作结束后有余韵(卡片落位后轻微 bounce
```js
// 卡片入场的完整三段
const anticip = interpolate(t, [0, 0.2], [1, 0.95], Easing.easeIn); // 预备
const action = interpolate(t, [0.2, 0.7], [0.95, 1.05], Easing.expoOut); // 主动
const settle = interpolate(t, [0.7, 1], [1.05, 1], Easing.spring); // 回弹
// 最终 scale = 三段乘积或分段应用
```
**反例**:只有 Action 没有 Anticipation + Follow-through 的动画像「PowerPoint 动画」。
### 4.7 3D Perspective + translateZ 分层
想要「倾斜 3D + 悬浮卡片」的气质,给容器加 perspective给单个元素不同的 translateZ
```css
.stage-wrap {
perspective: 2400px;
perspective-origin: 50% 30%; /* 视线略俯视 */
}
.card-grid {
transform-style: preserve-3d;
transform: rotateX(8deg) rotateY(-4deg); /* 黄金比例 */
}
.card:nth-child(3n) { transform: translateZ(30px); }
.card:nth-child(5n) { transform: translateZ(-20px); }
.card:nth-child(7n) { transform: translateZ(60px); }
```
**为什么 rotateX 8° / rotateY -4° 是黄金比例**
- 大于 10° → 元素扭曲感过强,看起来像「倒下」
- 小于 5° → 像「错切」而不是「透视」
-× -4° 的非对称比例模拟「镜头在桌面左上角俯视」的 natural angle
### 4.8 斜向 Pan · 同时动 XY
镜头运动不是纯上下或纯左右,而是**同时动 XY** 模拟斜向移动:
```js
const panX = Math.sin(flowT * 0.22) * 40;
const panY = Math.sin(flowT * 0.35) * 30;
stage.style.transform = `
translate(-50%, -50%)
rotateX(8deg) rotateY(-4deg)
translate3d(${panX}px, ${panY}px, 0)
`;
```
**关键**X 和 Y 的频率不同0.22 vs 0.35),避免 Lissajous 循环规则化。
---
## 5. 场景配方(三种叙事模板)
参考材料里三支视频对应三种产品性格。**选一种最贴合你的产品**,不要混搭。
### 配方 A · Apple Keynote 戏剧式Claude Design 类)
**适合**大版本发布、hero 动画、视觉惊艳优先
**节奏**Slow-Fast-Boom-Stop 强弧线
**Easing**:全程 `expoOut` + 少量 `overshoot`
**SFX 密度**:高(~0.4/sSFX 音高调到 BGM 音阶
**BGM**IDM / 极简科技电子,冷静+精密
**收束**:镜头急拉远 → drop → Logo 形变 → 空灵单音 → 戛然而止
### 配方 B · 一镜到底工具式Claude Code 类)
**适合**:开发者工具、生产力 App、心流场景
**节奏**:持续稳定 flow没有明显峰值
**Easing**`spring` 物理 + `expoOut`
**SFX 密度****0**(纯靠 BGM 驱动剪辑节奏)
**BGM**Lo-fi Hip-hop / Boom-bap85-90 BPM
**核心技巧**:关键 UI 动作踩在 BGM kick/snare 瞬态上——「**音乐律动即交互音效**」
### 配方 C · 办公效率叙事式Claude for Word 类)
**适合**:企业软件、文档/表格/日历类、专业感优先
**节奏**:多 scene 硬切 + Dolly In/Out
**Easing**`overshoot`toggle+ `expoOut`(面板)
**SFX 密度**:中(~0.3/sUI click 为主
**BGM**Jazzy Instrumental小调BPM 90-95
**核心亮点**:某一幕必有「全片高光」—— 3D pop-out / 脱离平面浮起
---
## 6. 反例 · 这样做就是 AI slop
| 反 pattern | 为什么错 | 正确做法 |
|---|---|---|
| `transition: all 0.3s ease` | `ease` 是 linear 的亲戚,所有元素同速 | 用 `expoOut` + 分元素 stagger |
| 所有入场都 `opacity 0→1` | 没有运动方向感 | 配合 `translateY 10→0` + Anticipation |
| Logo 淡入 | 没有叙事收束感 | Morph / Converge / 坍缩-展开 |
| 鼠标直线移动 | 潜意识机器感 | 贝塞尔弧线 + Perlin Noise |
| 打字单字蹦出setInterval | 像老电影字幕 | Chunk Reveal随机间隔 |
| 关键结果无悬停 | 观众没反应时间 | 结果前 0.5s 悬停 |
| 焦点切换只改 opacity | 非焦点元素还锐利 | opacity + brightness + **blur** |
| 纯黑底 / 纯白底 | 赛博感 / 反光疲劳 | 带色温的中性色(走品牌 spec |
| 所有动画同样快 | 无节奏 | Slow-Fast-Boom-Stop |
| Fade out 收尾 | 无决定感 | 戛然而止hold 最后一帧) |
---
## 7. 自检清单(动画交付前 60 秒)
- [ ] 叙事结构是 Slow-Fast-Boom-Stop不是均匀节奏
- [ ] 默认 easing 是 `expoOut`,不是 `easeOut``linear`
- [ ] Toggle / 按钮弹出用了 `overshoot`
- [ ] 卡片 / 列表入场有 30ms stagger
- [ ] 关键结果前有 0.5s 悬停?
- [ ] 打字用 Chunk Reveal不是 setInterval 单字?
- [ ] 焦点切换加了 blur不只是 opacity
- [ ] Logo 是形变收束Morph不是淡入
- [ ] 底色不是纯黑 / 纯白(带色温)?
- [ ] 文字有衬线 + 无衬线层次?
- [ ] 收尾是戛然而止,不是渐弱?
- [ ] (有鼠标的话)鼠标轨迹是弧线,不是直线?
- [ ] SFX 密度符合产品性格(见配方 A/B/C
- [ ] BGM 和 SFX 有 6-8dB 响度差?(见 `audio-design-rules.md`
---
## 8. 与其他 reference 的关系
| reference | 定位 | 关系 |
|---|---|---|
| `animation-pitfalls.md` | 技术避坑16 条) | 「**不要这样做**」· 本文件的反面 |
| `animations.md` | Stage/Sprite 引擎用法 | 动画**怎么写**的基础 |
| `audio-design-rules.md` | 双轨制音频规则 | 动画**配音频**的规则 |
| `sfx-library.md` | 37 个 SFX 清单 | 音效**素材库** |
| `apple-gallery-showcase.md` | Apple 画廊展示风格 | 一种特定运动风格的专题 |
| **本文件** | 正向运动设计语法 | 「**应该这样做**」 |
**调用顺序**
1. 先看 SKILL.md 工作流程 Step 3 的位置四问(决定叙事角色和视觉温度)
2. 选定方向后读本文件确定**运动语言**(配方 A/B/C
3. 写代码时参考 `animations.md``animation-pitfalls.md`
4. 导出视频时走 `audio-design-rules.md` + `sfx-library.md`
---
## 附录 · 本文件素材来源
- Anthropic 官方动画拆解:花叔项目目录的 `参考动画/BEST-PRACTICES.md`
- Anthropic 音频拆解:同目录 `AUDIO-BEST-PRACTICES.md`
- 3 支参考视频:`ref-{1,2,3}.mp4` + 对应 `gemini-ref-*.md` / `audio-ref-*.md`
- **严格过滤**:本 reference 不收录任何具体品牌色值、字体名、产品名。
色彩/字体决策走 §1.a 核心资产协议或 20 种设计哲学。

View File

@@ -0,0 +1,380 @@
# Animation PitfallsHTML 动画踩过的坑与规则
做动画时最常踩的 bug 和如何避免。每条规则都来自真实失败案例。
写动画之前读完这篇,能省一轮迭代。
## 1. 叠层布局 —— `position: relative` 是默认义务
**踩的坑**:一个 sentence-wrap 元素包了 3 个 bracket-layer`position: absolute`)。没给 sentence-wrap 设 `position: relative`,结果 absolute 的 bracket 以 `.canvas` 为坐标系,飘到屏幕底部 200px 外。
**规则**
- 任何包含 `position: absolute` 子元素的容器,**必须**显式 `position: relative`
- 即使视觉上不需要「偏移」,也要写 `position: relative` 作为坐标系锚点
- 如果你在写 `.parent { ... }`,其子元素里有 `.child { position: absolute }`,下意识给 parent 加 relative
**快速检查**:每出现一个 `position: absolute`,往上数 ancestor确保最近的 positioned 祖先是你*想要的*坐标系。
## 2. 字符陷阱 —— 不依赖稀有 Unicode
**踩的坑**:想用 `␣` (U+2423 OPEN BOX) 可视化「空格 token」。Noto Serif SC / Cormorant Garamond 都没这个字形,渲染为空白/豆腐,观众完全看不到。
**规则**
- **动画里出现的每个字符,都必须在你选定的字体里存在**
- 常见稀有字符黑名单:`␣ ␀ ␐ ␋ ␨ ↩ ⏎ ⌘ ⌥ ⌃ ⇧ ␦ ␖ ␛`
- 要表达「空格 / 回车 / 制表符」这类元字符,用 **CSS 构造的语义盒子**
```html
<span class="space-key">Space</span>
```
```css
.space-key {
display: inline-flex;
padding: 4px 14px;
border: 1.5px solid var(--accent);
border-radius: 4px;
font-family: monospace;
font-size: 0.3em;
letter-spacing: 0.2em;
text-transform: uppercase;
}
```
- Emoji 也要验证:某些 emoji 在 Noto Emoji 以外字体会 fallback 成灰色方框,最好用 `emoji` font-family 或 SVG
## 3. 数据驱动的 Grid/Flex 模板
**踩的坑**:代码里 `const N = 6` 个 tokens但 CSS 写死 `grid-template-columns: 80px repeat(5, 1fr)`。结果第 6 个 token 没有 column整个矩阵错位。
**规则**
- 当 count 从 JS 数组来(`TOKENS.length`CSS 模板也应该数据驱动
- 方案 A用 CSS 变量从 JS 注入
```js
el.style.setProperty('--cols', N);
```
```css
.grid { grid-template-columns: 80px repeat(var(--cols), 1fr); }
```
- 方案 B用 `grid-auto-flow: column` 让浏览器自动扩展
- **禁用「固定数字 + JS 常量」的组合**N 改了 CSS 不会同步更新
## 4. 过渡断层 —— 场景切换要连续
**踩的坑**zoom1 (13-19s) → zoom2 (19.2-23s) 之间,主句子已经 hiddenzoom1 fade out0.6s+ zoom2 fade in0.6s+ stagger delay0.2s+= 约 1 秒纯空白画面。观众以为动画卡住了。
**规则**
- 连续切换场景时fade out 和 fade in 要**交叉重叠**,不是前一个完全消失再开始下一个
```js
// 差:
if (t >= 19) hideZoom('zoom1'); // 19.0s out
if (t >= 19.4) showZoom('zoom2'); // 19.4s in → 中间 0.4s 空白
// 好:
if (t >= 18.6) hideZoom('zoom1'); // 提前 0.4s 开始 fade out
if (t >= 18.6) showZoom('zoom2'); // 同时 fade incross-fade
```
- 或者用一个「锚点元素」如主句子作为场景之间的视觉连接zoom 切换期间它短暂回显
- 配 CSS transition 的 duration 算清楚,避免 transition 还没结束就触发下一个
## 5. Pure Render 原则 —— 动画状态应可 seek
**踩的坑**:用 `setTimeout` + `fireOnce(key, fn)` 链式触发动画状态。正常播放没问题,但做逐帧录制/seek到任意时间点时之前的 setTimeout 已经执行过就无法「回到过去」。
**规则**
- `render(t)` 函数理想上是 **pure function**:给定 t 输出唯一 DOM 状态
- 如果必须用副作用(如 class 切换),用 `fired` set 配合显式 reset
```js
const fired = new Set();
function fireOnce(key, fn) { if (!fired.has(key)) { fired.add(key); fn(); } }
function reset() { fired.clear(); /* 清所有 .show class */ }
```
- 暴露 `window.__seek(t)` 供 Playwright / 调试用:
```js
window.__seek = (t) => { reset(); render(t); };
```
- 动画相关的 setTimeout 不要跨越 >1 秒,否则 seek 回跳时会乱套
## 6. 字体加载前测量 = 测错
**踩的坑**:页面一 DOMContentLoaded 就调用 `charRect(idx)` 测量 bracket 位置,字体还没加载,每个字符宽度是 fallback 字体的宽度,位置全错。等字体一加载(约 500ms 后bracket 的 `left: Xpx` 还是老值,永久偏移。
**规则**
- 任何依赖 DOM 测量(`getBoundingClientRect`、`offsetWidth`)的布局代码,**必须**包在 `document.fonts.ready.then()` 里
```js
document.fonts.ready.then(() => {
requestAnimationFrame(() => {
buildBrackets(...); // 此时字体已就绪,测量准确
tick(); // 动画开始
});
});
```
- 额外的 `requestAnimationFrame` 给浏览器一帧时间提交 layout
- 如果用 Google Fonts CDN`<link rel="preconnect">` 加速首次加载
## 7. 录制准备 —— 为视频导出预留抓手
**踩的坑**Playwright `recordVideo` 默认 25fps从 context 创建就开始录。页面加载、字体加载的前 2 秒都被录进去。交付时视频前面 2 秒空白/闪白。
**规则**
- 提供 `render-video.js` 工具处理warmup navigate → reload 重启动画 → 等 duration → ffmpeg trim head + 转 H.264 MP4
- 动画的**第 0 帧**要是最终布局已就位的完整初始状态(不是空白或加载中)
- 想要 60fps用 ffmpeg `minterpolate` 后处理,不指望浏览器源帧率
- 想要 GIF两阶段 palette`palettegen` + `paletteuse`),对 30s 1080p 动画能压到 3MB
参见 `video-export.md` 获取完整脚本调用方式。
## 8. 批量导出 —— tmp 目录必须带 PID 防并发冲突
**踩的坑**:用 `render-video.js` 3 个进程并行录 3 个 HTML。因为 TMP_DIR 只用 `Date.now()` 命名3 个进程同毫秒启动时共用同一个 tmp 目录。最先完成的进程清理 tmp另外两个读目录时 `ENOENT`,全部崩溃。
**规则**
- 任何多进程可能共用的临时目录,命名必须带 **PID 或随机后缀**
```js
const TMP_DIR = path.join(DIR, '.video-tmp-' + Date.now() + '-' + process.pid);
```
- 如果确实想多文件并行,用 shell 的 `&` + `wait` 而不是在一个 node 脚本里 fork
- 批量录多个 HTML 时,保守做法:**串行**运行2 个以内可并行3 个以上老实排队)
## 9. 录屏里有进度条/重播按钮 —— Chrome 元素污染视频
**踩的坑**:动画 HTML 加了 `.progress` 进度条、`.replay` 重播按钮、`.counter` 时间戳,方便人类调试播放。录成 MP4 交付时这些元素出现在视频底部,像把开发者工具截进去了一样。
**规则**
- HTML 里给人类用的「chrome 元素」progress bar / replay button / footer / masthead / counter / phase labels和视频内容本体分开管理
- **约定 class 名** `.no-record`:任何带这个 class 的元素,录屏脚本自动隐藏
- 脚本端(`render-video.js`)默认注入 CSS 隐藏常见 chrome class 名:
```
.progress .counter .phases .replay .masthead .footer .no-record [data-role="chrome"]
```
- 用 Playwright 的 `addInitScript` 注入(会在每次 navigate 前生效reload 也稳)
- 想看原样 HTML带 chrome时加 `--keep-chrome` flag
## 10. 录屏开头几秒动画重复 —— Warmup 帧泄漏
**踩的坑**`render-video.js` 的旧流程 `goto → wait fonts 1.5s → reload → wait duration`。录制从 context 创建就开始warmup 阶段动画已经播了一段reload 后从 0 重启。结果视频前几秒是「动画中段 + 切换 + 动画从 0 开始」,重复感强。
**规则**
- **Warmup 和 Record 必须用独立的 context**
- Warmup context无 `recordVideo` 选项):只负责 load url、等字体、然后 close
- Record context有 `recordVideo`fresh 状态开始animation 从 t=0 开始录
- ffmpeg `-ss trim` 只能裁 Playwright 的一点点 startup latency~0.3s**不能**用来掩盖 warmup 帧;源头要干净
- 录制 context 关闭 = webm 文件写入磁盘,这是 Playwright 的约束
- 相关代码模式:
```js
// Phase 1: warmup (throwaway)
const warmupCtx = await browser.newContext({ viewport });
const warmupPage = await warmupCtx.newPage();
await warmupPage.goto(url, { waitUntil: 'networkidle' });
await warmupPage.waitForTimeout(1200);
await warmupCtx.close();
// Phase 2: record (fresh)
const recordCtx = await browser.newContext({ viewport, recordVideo });
const page = await recordCtx.newPage();
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(DURATION * 1000);
await page.close();
await recordCtx.close();
```
## 11. 画面内别画「伪 chrome」—— 装饰版 player UI 与真 chrome 撞车
**踩的坑**:动画用 `Stage` 组件,已经自带 scrubber + 时间码 + 暂停按钮(属于 `.no-record` chrome导出时自动隐藏。我又在画面底部画了一条「`00:60 ──── CLAUDE-DESIGN / ANATOMY`」的"杂志页码感装饰进度条",自我感觉良好。**结果**:用户看到两条进度条——一条是 Stage 控制器,一条是我画的装饰。视觉上完全撞车,认定为 bug。「视频内还有个进度条是怎么回事
**规则**
- Stage 已经提供scrubber + 时间码 + 暂停/重播按钮。**画面内不要再画**进度指示、当前时间码、版权署名条、章节计数器——它们要么和 chrome 撞车,要么就是 filler slop违反「earn its place」原则
- 「页码感」「杂志感」「底部署名条」这些**装饰诉求**,是 AI 自动加上的高频 filler。每一个出现都要警觉——它真的传达了不可替代的信息吗还是单纯填满空白
- 如果你坚信某个底部条带必须存在(例如:动画主题就是讲 player UI那它必须**叙事必要**,且**视觉上和 Stage scrubber 显著区分**(不同位置、不同形式、不同色调)。
**元素归属测试**(每个画进 canvas 的元素必须能回答):
| 它属于什么 | 处理 |
|------------|------|
| 某一幕的叙事内容 | OK留着 |
| 全局 chrome控制/调试用) | 加 `.no-record` class导出时隐藏 |
| **既不属于任何幕,又不是 chrome** | **删**。这就是无主之物,必然是 filler slop |
**自检(交付前 3 秒)**:截一张静态图,问自己——
- 画面里有没有「看起来像 video player UI 的东西」(横线进度条、时间码、控制按钮模样)?
- 如果有,删掉它叙事是否有损?无损就删。
- 同一类信息(进度/时间/署名)有没有出现两次?合并到 chrome 一处。
**反例**:底部画 `00:42 ──── PROJECT NAME`、画面右下角画"CH 03 / 06"章节计数、画面边缘画版本号"v0.3.1"——都是伪 chrome filler。
## 12. 录屏前置空白 + 录屏起点偏移 —— `__ready` × tick × lastTick 三联陷阱
**踩的坑A · 前置空白)**60 秒动画导出 MP4前 2-3 秒是空白页面。`ffmpeg --trim=0.3` 剪不掉。
**踩的坑B · 起点偏移2026-04-20 真实事故)**:导出 24 秒视频,用户观感「视频 19 秒才开始播第一帧」。实际上动画从 t=5 开始录,录到 t=24 后 loop 回 t=0再录 5 秒到 end——所以视频最后 5 秒才是动画真正的开头。
**根因**(两个坑共享一个根因):
Playwright `recordVideo` 从 `newContext()` 那一刻就开始写 WebM此时 Babel/React/字体加载共耗时 L 秒2-6s。录屏脚本等 `window.__ready = true` 作为「动画从这里开始」的锚点——它和动画 `time = 0` 必须严格 pair。有两种常见错法
| 错法 | 症状 |
|------|------|
| `__ready` 在 `useEffect` 或同步 setup 阶段设(在 tick 第一帧之前) | 录屏脚本以为动画开始了,实际 WebM 还在录空白页 → **前置空白** |
| tick 的 `lastTick = performance.now()` 在**脚本顶层**初始化 | 字体加载 L 秒被算进首帧 `dt``time` 瞬间跳到 L → 录屏全程滞后 L 秒 → **起点偏移** |
**✅ 正确的完整 starter tick 模板**(手写动画必须用这个骨架):
```js
// ━━━━━━ state ━━━━━━
let time = 0;
let playing = false; // ❗ 默认不播,等字体 ready 再启动
let lastTick = null; // ❗ sentinel——tick 首帧时 dt 强制为 0别用 performance.now()
const fired = new Set();
// ━━━━━━ tick ━━━━━━
function tick(now) {
if (lastTick === null) {
lastTick = now;
window.__ready = true; // ✅ pair「录屏起点」与「动画 t=0」同一帧
render(0); // 再渲一次确保 DOM 就绪(此时字体已 ready
requestAnimationFrame(tick);
return;
}
const dt = (now - lastTick) / 1000; // 首帧之后 dt 才开始推进
lastTick = now;
if (playing) {
let t = time + dt;
if (t >= DURATION) {
t = window.__recording ? DURATION - 0.001 : 0; // 录制时不 loop留 0.001s 保留末帧
if (!window.__recording) fired.clear();
}
time = t;
render(time);
}
requestAnimationFrame(tick);
}
// ━━━━━━ boot ━━━━━━
// 不要在顶层立即 rAF——等字体加载完才启动
document.fonts.ready.then(() => {
render(0); // 先把初始画面画出来(字体已就绪)
playing = true;
requestAnimationFrame(tick); // 首次 tick 会 pair __ready + t=0
});
// ━━━━━━ seek 接口(供 render-video 防御性矫正用)━━━━━━
window.__seek = (t) => { fired.clear(); time = t; lastTick = null; render(t); };
```
**为什么这个模板对**
| 环节 | 为什么必须这样 |
|------|-------------|
| `lastTick = null` + 首帧 `return` | 避免「脚本加载到 tick 首次执行」的 L 秒被算进动画时间 |
| `playing = false` 默认 | 字体加载期间 `tick` 即使运行也不推进 time避免渲染错位 |
| `__ready` 在 tick 首帧设 | 录屏脚本此刻开始计时,对应的画面是动画真正的 t=0 |
| `document.fonts.ready.then(...)` 里才启动 tick | 规避字体 fallback 宽度测量、避免首帧字体跳变 |
| `window.__seek` 存在 | 让 `render-video.js` 可以主动矫正——第二道防线 |
**录屏脚本端的对应防御**
1. `addInitScript` 注入 `window.__recording = true`(先于 page goto
2. `waitForFunction(() => window.__ready === true)`,记录此刻偏移作为 ffmpeg trim
3. **额外**`__ready` 之后主动 `page.evaluate(() => window.__seek && window.__seek(0))`,把 HTML 可能的 time 偏差强制归零——这是第二道防线,对付不严格遵守 starter 模板的 HTML
**验证方法**:导出 MP4 后
```bash
ffmpeg -i video.mp4 -ss 0 -vframes 1 frame-0.png
ffmpeg -i video.mp4 -ss $DURATION-0.1 -vframes 1 frame-end.png
```
首帧必须是动画 t=0 的初始状态(不是中段,不是黑),末帧必须是动画终态(不是第二轮 loop 的某个时刻)。
**参考实现**`assets/animations.jsx` 的 Stage 组件、`scripts/render-video.js` 都已按此协议实现。手写 HTML 必须套 starter tick 模板——每一行都是防过具体 bug。
## 13. 录制时禁止 loop —— `window.__recording` 信号
**踩的坑**:动画 Stage 默认 `loop=true`(浏览器里方便看效果)。`render-video.js` 录完 duration 秒还多等 300ms 缓冲才停止,这 300ms 让 Stage 进入下一循环。ffmpeg `-t DURATION` 截取时,最后 0.5-1s 落入下一循环——视频结尾突然回到第一帧Scene 1观众以为视频出 bug。
**根因**:录制脚本和 HTML 之间没有"我在录制"的握手协议。HTML 不知道自己被录,依然按浏览器交互场景循环。
**规则**
1. **录制脚本**:在 `addInitScript` 里注入 `window.__recording = true`(先于 page goto
```js
await recordCtx.addInitScript(() => { window.__recording = true; });
```
2. **Stage 组件**:识别这个信号,强制 loop=false
```js
const effectiveLoop = (typeof window !== 'undefined' && window.__recording) ? false : loop;
// ...
if (next >= duration) return effectiveLoop ? 0 : duration - 0.001;
// ↑ 留 0.001 防止 Sprite end=duration 被关掉
```
3. **结尾 Sprite 的 fadeOut**:录制场景下应设 `fadeOut={0}`,否则视频末尾会渐变到透明/暗色——用户期望停在清晰的最后一帧,不是淡出。手写 HTML 时建议结尾 Sprite 都用 `fadeOut={0}`。
**参考实现**`assets/animations.jsx` 的 Stage / `scripts/render-video.js` 都已内置握手。手写 Stage 必须实现 `__recording` 检测——否则录制必踩这个坑。
**验证**:导出 MP4 后 `ffmpeg -ss 19.8 -i video.mp4 -frames:v 1 end.png`,检查倒数 0.2 秒是否还是预期最后一帧,没有突然切换到另一个 scene。
## 14. 60fps 视频默认用帧复制 —— minterpolate 兼容性差
**踩的坑**`convert-formats.sh` 用 `minterpolate=fps=60:mi_mode=mci...` 生成的 60fps MP4在 macOS QuickTime / Safari 部分版本下无法打开一片黑或直接拒打。VLC / Chrome 能打开。
**根因**minterpolate 输出的 H.264 elementary stream 包含某些播放器解析有问题的 SEI / SPS 字段。
**规则**
- 默认 60fps 用简单 `fps=60` filter帧复制兼容性广QuickTime/Safari/Chrome/VLC 都能开)
- 高质量插帧用 `--minterpolate` flag 显式启用——但**必须本地测过**目标播放器再交付
- 60fps 标签价值是**上传平台的算法识别**Bilibili / YouTube 上 60fps 标记会优先推流),实际感知流畅度对 CSS 动画来说提升微弱
- 加 `-profile:v high -level 4.0` 提升 H.264 通用兼容性
**`convert-formats.sh` 已默认改成兼容模式**。如果你需要插帧高质量,加 `--minterpolate` flag
```bash
bash convert-formats.sh input.mp4 --minterpolate
```
## 15. `file://` + 外部 `.jsx` 的 CORS 陷阱 —— 单文件交付必须内联引擎
**踩的坑**:动画 HTML 里用 `<script type="text/babel" src="animations.jsx"></script>` 外部加载引擎。本机双击打开(`file://` 协议)→ Babel Standalone 走 XHR 拉 `.jsx` → Chrome 报 `Cross origin requests are only supported for protocol schemes: http, https, chrome, chrome-extension...` → 整页黑屏,不报 `pageerror` 只报 console error很容易当"动画没触发"误诊。
启 HTTP server 也未必救得了——本机有全局代理时 `localhost` 也会走代理,返回 502 / 连接失败。
**规则**
- **单文件交付(双击打开即用的 HTML** → `animations.jsx` 必须**内联**到 `<script type="text/babel">...</script>` 标签内,不要用 `src="animations.jsx"`
- **多文件项目(起 HTTP server 演示)** → 可以外部加载,但交付时明确写清 `python3 -m http.server 8000` 命令
- 判断标准:交付给用户的是"HTML 文件"还是"带 server 的项目目录"?前者用内联
- Stage 组件 / animations.jsx 经常 200+ 行——贴进 HTML `<script>` 块完全可接受,别怕体积
**最小验证**:双击你生成的 HTML**不要**通过任何 server 打开。如果 Stage 正常显示动画首帧,才算通过。
## 16. 跨 scene 反色上下文 —— 画面内元素不要硬编码颜色
**踩的坑**:做多场景动画时,`ChapterLabel` / `SceneNumber` / `Watermark` 等**跨 scene 都出现**的元素,在组件里写死 `color: '#1A1A1A'`(深色文字)。前 4 个 scene 浅底 OK到第 5 个黑底 scene 时"05"和水印直接消失——不报错、不触发任何检查、关键信息隐形。
**规则**
- **跨多 scene 复用的画面内元素**chapter 标签 / scene 编号 / 时间码 / 水印 / 版权条)**禁止硬编码颜色值**
- 改用三种方式之一:
1. **`currentColor` 继承**:元素只写 `color: currentColor`,父 scene 容器设 `color: 计算值`
2. **invert prop**:组件接受 `<ChapterLabel invert />` 手动切换深浅
3. **基于底色自动计算**`color: contrast-color(var(--scene-bg))`CSS 4 新 API或 JS 判断)
- 交付前用 Playwright 抽**每个 scene 的代表帧**,人眼过一遍"跨 scene 元素"是否都可见
这条坑的隐蔽性在于——**没有 bug 报警**。只有人眼或 OCR 能发现。
## 快速自查清单(开工前 5 秒)
- [ ] 每个 `position: absolute` 的父元素都有 `position: relative`
- [ ] 动画里的特殊字符(`` `` `emoji`)都在字体里存在?
- [ ] Grid/Flex 模板的 count 和 JS 数据的 length 一致?
- [ ] 场景切换之间有 cross-fade没有 >0.3s 的纯空白?
- [ ] DOM 测量代码包在 `document.fonts.ready.then()` 里?
- [ ] `render(t)` 是 pure 的,或有明确的 reset 机制?
- [ ] 第 0 帧是完整初始状态,不是空白?
- [ ] 画面内没有「伪 chrome」装饰进度条/时间码/底部署名条与 Stage scrubber 撞车)?
- [ ] 动画 tick 第一帧同步设 `window.__ready = true`?(用 animations.jsx 自带;手写 HTML 自己加)
- [ ] Stage 检测 `window.__recording` 强制 loop=false手写 HTML 必加)
- [ ] 结尾 Sprite 的 `fadeOut` 设为 0视频末尾停清晰帧
- [ ] 60fps MP4 默认用帧复制模式(兼容性),高质量插帧才加 `--minterpolate`
- [ ] 导出后抽第 0 帧 + 末帧验证是动画初始/最终状态?
- [ ] 涉及具体品牌Stripe/Anthropic/Lovart/...走完了「品牌资产协议」SKILL.md §1.a 五步)?有没有写 `brand-spec.md`
- [ ] 单文件交付的 HTML`animations.jsx` 是内联的,不是 `src="..."`file:// 下 external .jsx 会 CORS 黑屏)
- [ ] 跨 scene 出现的元素chapter 标签/水印/scene 编号)没有硬编码颜色?在每个 scene 底色下都可见?

View File

@@ -0,0 +1,249 @@
# Animations时间轴动画引擎
做动画/motion design HTML时读这个。原理、用法、典型模式。
## 核心模式Stage + Sprite
我们的动画系统(`assets/animations.jsx`)提供一个时间轴驱动的引擎:
- **`<Stage>`**整个动画的容器自动提供auto-scalefit viewport+ scrubber + play/pause/loop控制
- **`<Sprite start end>`**时间片段。一个Sprite只在`start``end`这段时间内显示。内部可以通过`useSprite()` hook读取自己的本地进度`t` (0→1)
- **`useTime()`**:读当前全局时间(秒)
- **`Easing.easeInOut` / `Easing.easeOut` / ...**:缓动函数
- **`interpolate(t, from, to, easing?)`**根据t插值
这套模式借鉴Remotion/After Effects思路但轻量、零依赖。
## 起手
```html
<script type="text/babel" src="animations.jsx"></script>
<script type="text/babel">
const { Stage, Sprite, useTime, useSprite, Easing, interpolate } = window.Animations;
function Title() {
const { t } = useSprite(); // 本地进度 0→1
const opacity = interpolate(t, [0, 1], [0, 1], Easing.easeOut);
const y = interpolate(t, [0, 1], [40, 0], Easing.easeOut);
return (
<h1 style={{
opacity,
transform: `translateY(${y}px)`,
fontSize: 120,
fontWeight: 900,
}}>
Hello.
</h1>
);
}
function Scene() {
return (
<Stage duration={10}> {/* 10秒动画 */}
<Sprite start={0} end={3}>
<Title />
</Sprite>
<Sprite start={2} end={5}>
<SubTitle />
</Sprite>
{/* ... */}
</Stage>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Scene />);
</script>
```
## 常用动画模式
### 1. Fade In / Fade Out
```jsx
function FadeIn({ children }) {
const { t } = useSprite();
const opacity = interpolate(t, [0, 0.3], [0, 1], Easing.easeOut);
return <div style={{ opacity }}>{children}</div>;
}
```
**注意范围**`[0, 0.3]`意思是在sprite的前30%时间完成渐入后面保持opacity=1。
### 2. Slide In
```jsx
function SlideIn({ children, from = 'left' }) {
const { t } = useSprite();
const progress = interpolate(t, [0, 0.4], [0, 1], Easing.easeOut);
const offset = (1 - progress) * 100;
const directions = {
left: `translateX(-${offset}px)`,
right: `translateX(${offset}px)`,
top: `translateY(-${offset}px)`,
bottom: `translateY(${offset}px)`,
};
return (
<div style={{
transform: directions[from],
opacity: progress,
}}>
{children}
</div>
);
}
```
### 3. 逐字打字机
```jsx
function Typewriter({ text }) {
const { t } = useSprite();
const charCount = Math.floor(text.length * Math.min(t * 2, 1));
return <span>{text.slice(0, charCount)}</span>;
}
```
### 4. 数字计数
```jsx
function CountUp({ from = 0, to = 100, duration = 0.6 }) {
const { t } = useSprite();
const progress = interpolate(t, [0, duration], [0, 1], Easing.easeOut);
const value = Math.floor(from + (to - from) * progress);
return <span>{value.toLocaleString()}</span>;
}
```
### 5. 分段解释(典型教学动画)
```jsx
function Scene() {
return (
<Stage duration={20}>
{/* Phase 1: 展示问题 */}
<Sprite start={0} end={4}>
<Problem />
</Sprite>
{/* Phase 2: 展示思路 */}
<Sprite start={4} end={10}>
<Approach />
</Sprite>
{/* Phase 3: 展示结果 */}
<Sprite start={10} end={16}>
<Result />
</Sprite>
{/* 全程显示的字幕 */}
<Sprite start={0} end={20}>
<Caption />
</Sprite>
</Stage>
);
}
```
## Easing函数
预设的easing curves
| Easing | 特性 | 用在 |
|--------|------|------|
| `linear` | 匀速 | 滚动字幕、持续动画 |
| `easeIn` | 慢→快 | 退场消失 |
| `easeOut` | 快→慢 | 入场出现 |
| `easeInOut` | 慢→快→慢 | 位置变化 |
| **`expoOut`** ⭐ | **指数缓出** | **Anthropic 级主 easing**(物理重量感)|
| **`overshoot`** ⭐ | **弹性回弹** | **Toggle / 按钮弹出 / 强调交互** |
| `spring` | 弹簧 | 交互反馈、几何体归位 |
| `anticipation` | 先反向再正向 | 强调动作 |
**默认主 easing 用 `expoOut`**(不是 `easeOut`)—— 见 `animation-best-practices.md` §2。
入场用 `expoOut`、出场用 `easeIn`、toggle 用 `overshoot`——Anthropic 级动画的基础规律。
## 节奏和时长指南
### 微交互0.1-0.3秒)
- 按钮hover
- 卡片expand
- Tooltip出现
### UI过渡0.3-0.8秒)
- 页面切换
- 模态框出现
- 列表item加入
### 叙事动画2-10秒每段
- 概念解释的一个phase
- 数据图表的reveal
- 场景转换
### 单段叙事动画最长不超过10秒
人类注意力有限。10秒讲一件事讲完换下一件。
## 设计动画的思考顺序
### 1. 先有内容/故事,再有动画
**错误**先想要做fancy动画再塞内容进去
**正确**先想清楚要传达什么信息再用动画手段serve这个信息
动画是**signal**,不是**装饰**。一个fade-in强调的是"这里很重要,请看"——如果什么都fade-insignal就失效。
### 2. 分Scene写时间轴
```
0:00 - 0:03 问题出现fade in
0:03 - 0:06 问题放大/展开zoom+pan
0:06 - 0:09 解法出现slide in from right
0:09 - 0:12 解法展开说明typewriter
0:12 - 0:15 结果演示counter up + chart reveal
0:15 - 0:18 总结一句话static读3秒
0:18 - 0:20 CTA或fade out
```
写完时间轴再写组件。
### 3. 资源先行
动画要用的图片/图标/字体**先**准备好。不要画到一半去找素材——打断节奏。
## 常见问题
**动画卡顿**
→ 主要是layout thrashing。用`transform``opacity`,不要动`top`/`left`/`width`/`height`/`margin`。浏览器GPU加速`transform`
**动画太快,看不清楚**
→ 人读一个汉字需要100-150ms一个词300-500ms。如果你用文字讲故事单句至少留3秒。
**动画太慢,观众无聊**
→ 有趣的视觉变化要密集。静态画面超过5秒就会闷。
**多个动画互相影响**
→ 用CSS的`will-change: transform`提前告诉浏览器这个元素会动减少reflow。
**录制成视频**
→ 用 skill 自带工具链(一条命令出三种格式):见 `video-export.md`
- `scripts/render-video.js` — HTML → 25fps MP4Playwright + ffmpeg
- `scripts/convert-formats.sh` — 25fps MP4 → 60fps MP4 + 优化 GIF
- 想要更精确的帧渲染?让 render(t) 成为 pure function`animation-pitfalls.md` 第 5 条
## 和视频工具的配合
这个skill做的是**HTML动画**(在浏览器里跑的)。如果最终产出要作为视频素材:
- **短动画/concept demo**用这里的方法做HTML动画 → 屏幕录制
- **长视频/叙事**:本 skill 专注 HTML 动画,长视频用 AI 视频生成类 skill 或专业视频软件
- **motion graphics**专业的After Effects/Motion Canvas更合适
## 关于Popmotion等库
如果你真的需要物理动画spring、decay、keyframes with precise timing我们的engine搞不定可以fallback到Popmotion
```html
<script src="https://unpkg.com/popmotion@11.0.5/dist/popmotion.min.js"></script>
```
但**先试试我们的engine**。90%的情况够用。

View File

@@ -0,0 +1,338 @@
# Apple Gallery Showcase · 画廊展示墙动画风格
> 灵感来源Claude Design 官网 hero 视频 + 苹果产品页「作品墙」式陈列
> 实战出处huashu-design 发布 hero v5
> 适用场景:**产品发布 hero 动画、skill 能力演示、作品集展示**——任何需要把「多件高质量产出」同时展陈并引导观众注意力的场景
---
## 触发判断:什么时候用这个风格
**适合**
- 有10张以上真实产出要同屏展示PPT、App、网页、信息图
- 观众是专业受众(开发者、设计师、产品经理),对「质感」敏感
- 希望传递的气质是「克制、展览式、高级、有空间感」
- 需要焦点和全局同时存在(看细节但不失整体)
**不适合**
- 单产品聚焦(用 frontend-design 的产品 hero 模板)
- 情绪向/故事性强的动画(用时间轴叙事模板)
- 小屏幕 / 竖屏(倾斜视角在小画面上会糊)
---
## 核心视觉 Token
```css
:root {
/* 浅色画廊调板 */
--bg: #F5F5F7; /* 主画布底 — 苹果官网灰 */
--bg-warm: #FAF9F5; /* 温暖米白变体 */
--ink: #1D1D1F; /* 主字色 */
--ink-80: #3A3A3D;
--ink-60: #545458;
--muted: #86868B; /* 次级文字 */
--dim: #C7C7CC;
--hairline: #E5E5EA; /* 卡片1px边框 */
--accent: #D97757; /* 赤陶橙 — Claude brand */
--accent-deep:#B85D3D;
--serif-cn: "Noto Serif SC", "Songti SC", Georgia, serif;
--serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
--sans: "Inter", -apple-system, "PingFang SC", system-ui;
--mono: "JetBrains Mono", "SF Mono", ui-monospace;
}
```
**关键原则**
1. **绝不用纯黑底**。黑底会让作品看起来像电影、不像「可以被采用的工作成果」
2. **赤陶橙是唯一色相accent**,其他全部是灰阶 + 白
3. **三字体栈**serif英+serif中+sans+mono营造「出版物」而非「互联网产品」的气质
---
## 核心布局模式
### 1. 悬浮卡片(整个风格的基本单元)
```css
.gallery-card {
background: #FFFFFF;
border-radius: 14px;
padding: 6px; /* 内边距是「装裱纸」 */
border: 1px solid var(--hairline);
box-shadow:
0 20px 60px -20px rgba(29, 29, 31, 0.12), /* 主阴影,软且长 */
0 6px 18px -6px rgba(29, 29, 31, 0.06); /* 第二层近光,制造浮感 */
aspect-ratio: 16 / 9; /* 统一 slide 比例 */
overflow: hidden;
}
.gallery-card img {
width: 100%; height: 100%;
object-fit: cover;
border-radius: 9px; /* 比卡片圆角略小,视觉嵌套 */
}
```
**反面教材**不要贴边瓷砖无padding无border无shadow——那是信息图密度表达不是展览。
### 2. 3D倾斜作品墙
```css
.gallery-viewport {
position: absolute; inset: 0;
overflow: hidden;
perspective: 2400px; /* 深一些的透视,倾斜不夸张 */
perspective-origin: 50% 45%;
}
.gallery-canvas {
width: 4320px; /* 画布 = 2.25× viewport */
height: 2520px; /* 留出pan空间 */
transform-origin: center center;
transform: perspective(2400px)
rotateX(14deg) /* 向后倾 */
rotateY(-10deg) /* 向左转 */
rotateZ(-2deg); /* 轻微倾斜,去掉太规整 */
display: grid;
grid-template-columns: repeat(8, 1fr);
gap: 40px;
padding: 60px;
}
```
**参数 sweet spot**
- rotateX: 10-15deg再多就像开酒会 VIP 背景板)
- rotateY: ±8-12deg左右对称感
- rotateZ: ±2-3deg「这不是机器摆的」的人味
- perspective: 2000-2800px小于2000会鱼眼大于3000接近正投影
### 3. 2×2 四角汇聚(选择场景)
```css
.grid22 {
display: grid;
grid-template-columns: repeat(2, 800px);
gap: 56px 64px;
align-items: start;
}
```
每张卡片从对应角落tl/tr/bl/br向中心滑入 + fade in。对应的 `cornerEntry` 向量:
```js
const cornerEntry = {
tl: { dx: -700, dy: -500 },
tr: { dx: 700, dy: -500 },
bl: { dx: -700, dy: 500 },
br: { dx: 700, dy: 500 },
};
```
---
## 五种核心动画模式
### 模式 A · 四角汇聚0.8-1.2s
4 个元素从视口四角滑入,同时缩放 0.85→1.0,对应 ease-out。适合「展示多方向选择」的开场。
```js
const inP = easeOut(clampLerp(t, start, end));
card.style.transform = `translate3d(${(1-inP)*ce.dx}px, ${(1-inP)*ce.dy}px, 0) scale(${0.85 + 0.15*inP})`;
card.style.opacity = inP;
```
### 模式 B · 选中放大 + 其他滑出0.8s
被选中的卡片放大 1.0→1.28,其他卡片 fade out + blur + 向四角漂回:
```js
// 被选中
card.style.transform = `translate3d(${cellDx*outP}px, ${cellDy*outP}px, 0) scale(${1 + 0.28*easeOut(zoomP)})`;
// 未选中
card.style.opacity = 1 - outP;
card.style.filter = `blur(${outP * 1.5}px)`;
```
**关键**:未选中的要 blur不是纯 fade。blur 模拟景深,视觉上把被选中的「推出来」。
### 模式 C · Ripple 涟漪展开1.7s
从中心向外,按距离 delay每张卡片依次淡入 + 从 1.25x 缩到 0.94x(「镜头拉远」):
```js
const col = i % COLS, row = Math.floor(i / COLS);
const dc = col - (COLS-1)/2, dr = row - (ROWS-1)/2;
const dist = Math.sqrt(dc*dc + dr*dr);
const delay = (dist / maxDist) * 0.8;
const localT = Math.max(0, (t - rippleStart - delay) / 0.7);
card.style.opacity = easeOut(Math.min(1, localT));
// 同时整体 scale 1.25→0.94
const galleryScale = 1.25 - 0.31 * easeOut(rippleProgress);
```
### 模式 D · Sinusoidal Pan持续漂移
用正弦波 + 线性漂移组合,避免 marquee 那种「有起点有终点」的循环感:
```js
const panX = Math.sin(panT * 0.12) * 220 - panT * 8; // 横向左漂
const panY = Math.cos(panT * 0.09) * 120 - panT * 5; // 纵向上漂
const clampedX = Math.max(-900, Math.min(900, panX)); // 防止露边
```
**参数**
- 正弦周期 `0.09-0.15 rad/s`约30-50秒一个摆动
- 线性漂移 `5-8 px/s`(比观众眨眼慢)
- 振幅 `120-220 px`(大到能感觉,小到不会晕)
### 模式 E · Focus Overlay焦点切换
**关键设计**focus overlay 是一个**平面元素**(不倾斜),浮在倾斜画布之上。被选中的 slide 从瓦片位置约400×225缩放到屏幕中央960×540背景画布不倾斜变化但**变暗到 45%**
```js
// Focus overlay (flat, centered)
focusOverlay.style.width = (startW + (endW - startW) * focusIntensity) + 'px';
focusOverlay.style.height = (startH + (endH - startH) * focusIntensity) + 'px';
focusOverlay.style.opacity = focusIntensity;
// 背景卡片变暗但依然可见关键不要100%遮罩)
card.style.opacity = entryOp * (1 - 0.55 * focusIntensity); // 1 → 0.45
card.style.filter = `brightness(${1 - 0.3 * focusIntensity})`;
```
**清晰度铁律**
- Focus overlay 的 `<img>` 必须 `src` 直连原图,**不要复用 gallery 里的压缩缩略**
- 提前 preload 所有原图到 `new Image()[]` 数组
- overlay 自身 `width/height` 按帧计算,浏览器每帧 resample 原图
---
## 时间轴架构(可复用骨架)
```js
const T = {
DURATION: 25.0,
s1_in: [0.0, 0.8], s1_type: [1.0, 3.2], s1_out: [3.5, 4.0],
s2_in: [3.9, 5.1], s2_hold: [5.1, 7.0], s2_out: [7.0, 7.8],
s3_hold: [7.8, 8.3], s3_ripple: [8.3, 10.0],
panStart: 8.6,
focuses: [
{ start: 11.0, end: 12.7, idx: 2 },
{ start: 13.3, end: 15.0, idx: 3 },
{ start: 15.6, end: 17.3, idx: 10 },
{ start: 17.9, end: 19.6, idx: 16 },
],
s4_walloff: [21.1, 21.8], s4_in: [21.8, 22.7], s4_hold: [23.7, 25.0],
};
// 核心 easing
const easeOut = t => 1 - Math.pow(1 - t, 3);
const easeInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2;
function lerp(time, start, end, fromV, toV, easing) {
if (time <= start) return fromV;
if (time >= end) return toV;
let p = (time - start) / (end - start);
if (easing) p = easing(p);
return fromV + (toV - fromV) * p;
}
// 单一 render(t) 函数读时间戳、写所有元素
function render(t) { /* ... */ }
requestAnimationFrame(function tick(now) {
const t = ((now - startMs) / 1000) % T.DURATION;
render(t);
requestAnimationFrame(tick);
});
```
**架构精髓****所有状态由时间戳 t 推导**,没有状态机、没有 setTimeout。这样
- 播放到任意时刻 `window.__setTime(12.3)` 立刻跳转(方便 playwright 逐帧截)
- 循环天然无缝t mod DURATION
- Debug 时能冻结任意一帧
---
## 质感细节(容易被忽略但致命)
### 1. SVG noise texture
浅色底最怕「太平」。叠加一层极弱的 fractalNoise
```html
<style>
.stage::before {
content: '';
position: absolute; inset: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.078 0 0 0 0 0.078 0 0 0 0 0.074 0 0 0 0.035 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
opacity: 0.5;
pointer-events: none;
z-index: 30;
}
</style>
```
看上去没区别,去掉就知道有了。
### 2. 角落品牌标识
```html
<div class="corner-brand">
<div class="mark"></div>
<div>HUASHU · DESIGN</div>
</div>
```
```css
.corner-brand {
position: absolute; top: 48px; left: 72px;
font-family: var(--mono);
font-size: 12px;
letter-spacing: 0.22em;
text-transform: uppercase;
color: var(--muted);
}
```
只在作品墙 scene 显示,淡入淡出。像美术馆展签。
### 3. 品牌收束 wordmark
```css
.brand-wordmark {
font-family: var(--sans);
font-size: 148px;
font-weight: 700;
letter-spacing: -0.045em; /* 负字距是关键,让字紧凑成标志 */
}
.brand-wordmark .accent {
color: var(--accent);
font-weight: 500; /* accent字符反而细一点视觉差 */
}
```
`letter-spacing: -0.045em` 是苹果产品页大字的标准做法。
---
## 常见失败模式
| 症状 | 原因 | 解法 |
|---|---|---|
| 看起来像 PPT 模板 | 卡片没有 shadow / hairline | 加上两层 box-shadow + 1px border |
| 倾斜感廉价 | 只用了 rotateY 没加 rotateZ | 加 ±2-3deg rotateZ 打破工整 |
| Pan 感觉「卡顿」 | 用了 setTimeout 或 CSS keyframes 循环 | 用 rAF + sin/cos 连续函数 |
| Focus 时字看不清 | 复用了 gallery 瓦片的低分图 | 独立 overlay + 原图 src 直连 |
| 背景太空 | 纯色 `#F5F5F7` | 叠加 SVG fractalNoise 0.5 opacity |
| 字体太"互联网" | 只有 Inter | 加 Serif中英各一+ mono 三栈 |
---
## 引用
- 完整实现样本:`/Users/alchain/Documents/写作/01-公众号写作/项目/2026.04-huashu-design发布/配图/hero-animation-v5.html`
- 原始灵感claude.ai/design hero 视频
- 参考审美Apple 产品页、Dribbble shot 集合页
遇到「多件高质量产出要陈列」的动画需求,直接从此文件 copy 骨架,换内容 + 调 timing 即可。

View File

@@ -0,0 +1,260 @@
# 音频设计规则 · huashu-design
> 所有动画 demo 的音频应用配方。和 `sfx-library.md`(资产清单)配套使用。
> 实战锤炼huashu-design 发布 hero v1-v9 迭代 · Anthropic 三支官方片子的 Gemini 深度拆解 · 8000+ 次 A/B 对比
---
## 核心原则 · 音频双轨制(铁律)
动画音频**必须分两层独立设计**,不能只做一层:
| 层 | 作用 | 时间尺度 | 和视觉的关系 | 占据频段 |
|---|---|---|---|---|
| **SFX节拍层** | 标记每个视觉 beat | 0.2-2 秒短促 | **强同步**(帧级对齐) | **高频 800Hz+** |
| **BGM氛围底** | 情绪铺底、声场 | 连续 20-60 秒 | 弱同步(段落级) | **中低频 <4kHz** |
**只做BGM的动画是残废的**——观众潜意识感知到「画在动但没声音响应」,廉价感的根源就在这里。
---
## 金标准 · 黄金配比
这几组数值是实测 Anthropic 三支官方片子 + 我们自己 v9 定版对比得出的**工程硬参数**,直接套用即可:
### 音量
- **BGM 音量**`0.40-0.50`(相对满刻度 1.0
- **SFX 音量**`1.00`
- **响度差**BGM 比 SFX peak **低 -6 到 -8 dB**不是靠SFX绝对响度突出靠响度差
- **amix 参数**`normalize=0`(绝不用 normalize=1会把动态范围压平
### 频段隔离P1 硬优化)
Anthropic 的秘诀不是「SFX 音量大」,是**频段分层**
```bash
[bgm_raw]lowpass=f=4000[bgm] # BGM 限制在 <4kHz 的中低频
[sfx_raw]highpass=f=800[sfx] # SFX 推到 800Hz+ 的中高频
[bgm][sfx]amix=inputs=2:duration=first:normalize=0[a]
```
为什么:人耳对 2-5kHz 区间最敏感即「presence 频段」SFX 如果都在这个区间BGM 又全频段覆盖,**SFX 会被BGM的高频部分遮盖**。用 highpass 把 SFX 推高 + lowpass 把 BGM 压下两者在频谱上各占一方SFX 清晰度直接上一档。
### Fade
- BGM 入:`afade=in:st=0:d=0.3`0.3s,避免硬切)
- BGM 出:`afade=out:st=N-1.5:d=1.5`1.5s 长尾,收束感)
- SFX 自带 envelope不需要额外 fade
---
## SFX cue 设计规则
### 密度每10秒多少个SFX
实测 Anthropic 三支片子的 SFX 密度有三档:
| 片子 | 每10s SFX 数 | 产品性格 | 场景 |
|---|---|---|---|
| Artifactsref-1 | **~9个/10s** | 功能密集、信息多 | 复杂工具演示 |
| Code Desktopref-2 | **0个** | 纯氛围、冥想感 | 开发工具专注状态 |
| Wordref-3 | **~4个/10s** | 平衡、办公节奏 | 生产力工具 |
**启发式**
- 产品性格冷静/专注 → SFX 密度低0-3个/10sBGM 为主
- 产品性格活泼/信息多 → SFX 密度高6-9个/10sSFX 驱动节奏
- **不要填满每个视觉 beat**——留白比密集更高级。**删掉 30-50% 的 cue 会让剩下的更有戏剧性**。
### Cue 选择优先级
每个视觉 beat 不都要配 SFX。按这个优先级选
**P0 必配**(省略会有违和感):
- 打字(终端/输入)
- 点击/选择(用户决策时刻)
- 焦点切换(视觉主角转移)
- Logo reveal品牌收束
**P1 推荐配**
- 元素入场/离场modal / card
- 完成/成功反馈
- AI 生成开始/结束
- 重大过渡scene 切换)
**P2 选配**(多了会乱):
- hover / focus-in
- 进度 tick
- 装饰性 ambient
### 时间戳对齐精度
- **同帧对齐**0ms 误差):点击/焦点切换/Logo 落定
- **前置 1-2 帧**-33ms快速 whoosh给观众心理预期
- **后置 1-2 帧**+33ms物体落地/impact符合真实物理
---
## BGM 选择决策树
huashu-design skill 自带 6 首 BGM`assets/bgm-*.mp3`
```
动画性格是什么?
├─ 产品发布 / 技术演示 → bgm-tech.mp3minimal synth + piano
├─ 教程讲解 / 工具使用 → bgm-tutorial.mp3warm, instructional
├─ 教育学习 / 原理解释 → bgm-educational.mp3curious, thoughtful
├─ 营销广告 / 品牌宣传 → bgm-ad.mp3upbeat, promotional
└─ 同类风格需要变体 → bgm-*-alt.mp3各自替代版
```
### 无 BGM 的场景(值得考虑)
参考 Anthropic Code Desktopref-2**0 SFX + 纯 Lo-fi BGM** 也能很高级。
**何时选无BGM**
- 动画时长 <10sBGM 建立不起来
- 产品性格是专注/冥想
- 场景本身有环境音/讲解声
- SFX 密度很高时避免听觉过载
---
## 场景配方(开箱即用)
### 配方 A · 产品发布 herohuashu-design v9 同款)
```
时长25 秒
BGMbgm-tech.mp3 · 45% · 频段 <4kHz
SFX 密度:~6个/10s
cue
终端打字 → type × 4间隔0.6s
回车 → enter
卡片汇聚 → card × 4错峰 0.2s
选中 → click
Ripple → whoosh
4次焦点 → focus × 4
Logo → thud1.5s
音量BGM 0.45 / SFX 1.0 · amix normalize=0
```
### 配方 B · 工具功能演示(参考 Anthropic Code Desktop
```
时长30-45 秒
BGMbgm-tutorial.mp3 · 50%
SFX 密度0-2个/10s极少
策略:让 BGM + 讲解 voiceover 驱动SFX 只在**决定性时刻**(文件保存/命令执行完成)
```
### 配方 C · AI 生成演示
```
时长15-20 秒
BGMbgm-tech.mp3 或无 BGM
SFX 密度:~8个/10s高密度
cue
用户输入 → type + enter
AI 开始处理 → magic/ai-process1.2s 循环)
生成完成 → feedback/complete-done
结果呈现 → magic/sparkle
亮点ai-process 可以循环 2-3 次贯穿整个生成过程
```
### 配方 D · 纯氛围长镜头(参考 Artifacts
```
时长10-15 秒
BGM
SFX单独使用 3-5 个精心设计的 cue
策略:每个 SFX 都是主角没有BGM「糊在一起」的问题。
适合:单产品慢镜头、特写展示
```
---
## ffmpeg 合成模板
### 模板 1 · 单 SFX 叠加到视频
```bash
ffmpeg -y -i video.mp4 -itsoffset 2.5 -i sfx.mp3 \
-filter_complex "[0:a][1:a]amix=inputs=2:normalize=0[a]" \
-map 0:v -map "[a]" output.mp4
```
### 模板 2 · 多 SFX 时间轴合成按cue时间对齐
```bash
ffmpeg -y \
-i sfx-type.mp3 -i sfx-enter.mp3 -i sfx-click.mp3 -i sfx-thud.mp3 \
-filter_complex "\
[0:a]adelay=1100|1100[a0];\
[1:a]adelay=3200|3200[a1];\
[2:a]adelay=7000|7000[a2];\
[3:a]adelay=21800|21800[a3];\
[a0][a1][a2][a3]amix=inputs=4:duration=longest:normalize=0[mixed]" \
-map "[mixed]" -t 25 sfx-track.mp3
```
**关键参数**
- `adelay=N|N`前面是左声道延迟(ms)后面是右声道写两遍保证立体声对齐
- `normalize=0`保留动态范围关键
- `-t 25`截断到指定时长
### 模板 3 · 视频 + SFX track + BGM带频段隔离
```bash
ffmpeg -y -i video.mp4 -i sfx-track.mp3 -i bgm.mp3 \
-filter_complex "\
[2:a]atrim=0:25,afade=in:st=0:d=0.3,afade=out:st=23.5:d=1.5,\
lowpass=f=4000,volume=0.45[bgm];\
[1:a]highpass=f=800,volume=1.0[sfx];\
[bgm][sfx]amix=inputs=2:duration=first:normalize=0[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k final.mp4
```
---
## 失败模式速查
| 症状 | 根因 | 修复 |
|---|---|---|
| SFX 听不见 | BGM 高频部分遮盖 | `lowpass=f=4000` 给BGM + `highpass=f=800` 给SFX |
| 音效过响刺耳 | SFX 绝对音量太大 | SFX 音量降到 0.7同时降低 BGM 0.3保持差值 |
| BGM SFX 节奏冲突 | BGM 选错了用了有强beat的music | 换成 ambient / minimal synth BGM |
| 动画结束 BGM 突然断 | 没做 fade out | `afade=out:st=N-1.5:d=1.5` |
| SFX 重叠成糊 | cue 太密 + 每个 SFX 时长太长 | SFX 时长控到 0.5s 以内cue 间隔 0.2s |
| 公众号 mp4 没声音 | 公众号有时会 mute auto-play | 不用担心用户点开会有声音gif 本来就没声音 |
---
## 和视觉的联动(高级)
### SFX 音色要和视觉风格匹配
- 暖米/纸张感视觉 SFX **木质/柔和**音色Morse, paper snap, soft click
- 冷黑科技视觉 SFX **金属/数字**音色beep, pulse, glitch
- 手绘/童趣视觉 SFX **卡通/夸张**音色boing, pop, zap
我们当前 `apple-gallery-showcase.md` 的暖米底色 搭配 `keyboard/type.mp3`mechanical+ `container/card-snap.mp3`soft+ `impact/logo-reveal-v2.mp3`cinematic bass
### SFX 可以引导视觉节奏
高级技巧**先设计 SFX 时间轴然后调整视觉动画去对齐 SFX**不是反过来)。
因为 SFX 每个 cue 都是一个钟表 tick」,视觉动画适配 SFX 节奏会非常稳——反之 SFX 去追视觉常常 ±1 帧对不上就有违和感
---
## 质量检查清单(发布前自检)
- [ ] 响度差SFX peak - BGM peak = -6 -8 dB
- [ ] 频段BGM lowpass 4kHz + SFX highpass 800Hz
- [ ] amix normalize=0保留动态范围
- [ ] BGM fade-in 0.3s + fade-out 1.5s
- [ ] SFX 数量是否合适按场景性格选密度
- [ ] 每个 SFX 和视觉 beat 同帧对齐(±1 帧内
- [ ] Logo reveal 音效时长够建议 1.5s
- [ ] 关闭 BGM 听一遍SFX 单独是否足够有节奏感
- [ ] 关闭 SFX 听一遍BGM 单独是否有情绪起伏
两层任何一层单独听都应该自洽如果只有两层叠加才好听说明没做好
---
## 参考
- SFX 资产清单`sfx-library.md`
- 视觉风格参考`apple-gallery-showcase.md`
- Anthropic 三支片子深度音频分析`/Users/alchain/Documents/写作/01-公众号写作/项目/2026.04-huashu-design发布/参考动画/AUDIO-BEST-PRACTICES.md`
- huashu-design v9 实战案例`/Users/alchain/Documents/写作/01-公众号写作/项目/2026.04-huashu-design发布/配图/hero-animation-v9-final.mp4`

View File

@@ -0,0 +1,264 @@
# Cinematic Patterns · Workflow Demo 的 Best Practice
> 从「PPT 动画」升级到「发布会级 cinematic」的 5 个关键 pattern。
> 蒸馏自 2026-04 「聊聊 skill」 deck 的两个 cinematic demoNuwa workflow + Darwin workflow实测可复现。
---
## 0 · 这份文档解决什么问题
当你需要做「演示一个工作流的 demo 动画」时典型场景skill 工作流、产品 onboarding、API 调用流程、agent 任务执行),有两种常见做法:
| 范式 | 长什么样 | 后果 |
|---|---|---|
| **PPT 动画**(差) | step 1 fade in → step 2 fade in → step 3 fade in4 个 box 同屏排列 | 观众感觉「就是一个 PPT 加了 fade 效果」,没有 wow moment |
| **Cinematic**(好) | scene-based一次只 focus 一件事scene 之间是 dissolve / focus pull / morph | 观众感觉「这是一个产品发布会片段」,会想截图分享 |
差异的根源**不是动画技术**,是**叙事范式**。本文档讲怎么从前者升级到后者。
---
## 1 · 五个核心 pattern
### Pattern A · Dashboard + Cinematic Overlay 双层结构
**问题**:单纯的 cinematic 默认是黑屏 + 一个 ▶ 按钮,用户翻到这页如果没点,什么都看不到。
**解决**
```
DEFAULT 状态 (永远显示):完整静态 workflow dashboard
└── 观众一眼看清这个 skill / 工作流怎么跑
POINT ▶ 触发 (overlay 浮上来)22 秒 cinematic
└── 跑完自动 fade 回 DEFAULT
```
**实现要点**
- `.dash` 默认 visible`.cinema` 默认 `opacity: 0; pointer-events: none`
- `.play-cta` 是右下角金色小按钮(不是中央大覆盖)
- 点击 → `cinema.classList.add('show')` + `dash.classList.add('hide')`
-`requestAnimationFrame` 跑一次(不是循环),结束后 `endCinematic()` reverse 状态
**反 pattern**:默认 = 中央大 ▶ overlay 覆盖一切,没点之前页面是空白的。
---
### Pattern B · Scene-based, NOT Step-based
**问题**把动画拆成「step 1 显示 → step 2 显示 → ...」就是 PPT 思维。
**解决**:拆成 5 个 scene每个 scene 是**独立的镜头**,全屏只 focus 一件事:
| Scene 类型 | 职责 | 时长 |
|---|---|---|
| 1 · Invoke | 用户输入触发(终端 typewriter| 3-4s |
| 2 · Process | 核心工作流的可视化(独特视觉语言)| 5-6s |
| 3 · Result/Insight | 提炼出的关键产物(可视化)| 4-5s |
| 4 · Output | 实际产物展示(文件 / diff / 数字)| 3-4s |
| 5 · Hero Reveal | 收尾 hero moment大字 + 价值主张)| 4-5s |
**总时长 ≈ 22 秒**——这是经过测试的黄金长度:
- 短于 18 秒PM 还没进入状态就结束了
- 长于 25 秒:失去耐心
- 22 秒刚好够「钩住 → 展开 → 收束 → 留下印象」
**实现要点**
- `T = { DURATION: 22.0, s1_in: [0, 0.7], s2_in: [3.8, 4.6], ... }` 全局时间轴
- 单个 `requestAnimationFrame(render)` 跑所有 scene 的 opacity / transform 计算
- 不要用 setTimeout 链(容易断掉、难调试)
- Easing 必用 `expoOut` / `easeOut` / cubic-bezier**禁止 linear**
---
### Pattern C · 每个 demo 的视觉语言必须独立
**问题**:做完第一个 cinematic 后,做第二个时偷懒复用同一个模板(同样的 orbit + pentagon + typewriter + hero 大字),只换了文案。
**后果**:观众发现两个 skill「长得一模一样」等于在说「这两个 skill 没区别」。
**解决**:每个工作流的核心隐喻不同,视觉语言就必须不同。
**对照案例**
| 维度 | Nuwa蒸馏人| Darwin优化 skill|
|---|---|---|
| 核心隐喻 | 收集 → 提炼 → 写 | 循环 → 评估 → 棘轮 |
| 视觉运动 | 漂浮 / 辐射 / pentagon | 循环 / 上升 / 对比 |
| Scene 2 | 3D Orbit · 8 张档案在透视椭圆漂浮 | Spin Loop · token 沿 6 节点圆环跑 5 圈 |
| Scene 3 | Pentagon · 5 token 从中央辐射 | v1 vs v5 · 并列 diff红版 vs 金版) |
| Scene 4 | SKILL.md typewriter | Hill-Climb · 全屏曲线绘制 |
| Scene 5 hero | 「21 分钟」serif italic 大字 | 旋转齿轮 ⚙ + 「KEPT +1.1」金色 tag |
**判断标准**:盖住文案,只看视觉,能不能区分这是哪个 demo区分不了就是偷懒。
---
### Pattern D · 用 AI 生成的真实素材,不要 emoji 或 SVG 手画
**问题**3D orbit / gallery 里需要素材碎片漂浮emoji📚🎤丑且无品牌、SVG 手画书脊永远不像真书。
**解决**:用 `huashu-gpt-image` 跑一张 4×2 grid 大图8 件主题相关物品 · 白底 · 60px breathing space · unified style`extract_grid.py --mode bbox` 抠成 8 张独立透明 PNG。
**Prompt 要点**(详细 prompt patterns 见 `huashu-gpt-image` skill
- IP 锚定("1960s Caltech archive aesthetic" / "Hearthstone-style consistent treatment"
- 白底(便于抠图,灰底氛围好但抠透明背景困难)
- 4×2 不要 5×5避免末行压缩 bug
- Persona finishing"You are a Wired magazine curator preparing an exhibition photo"
**反 pattern**:用 emoji 当 icon、用 CSS 剪影代替产品图。
---
### Pattern E · BGM + SFX 双轨制
**问题**:只有动画没有声音,观众潜意识感觉「这玩意像个穷酸 demo」。
**解决**BGM 长音 + 11 个 SFX cues。
**通用 SFX cue 配方**(适用于工作流 demo
| 时点 | SFX | 触发场景 |
|---|---|---|
| 0.10s | whoosh | 终端从下方升起 |
| 3.0s | enter | typewriter 完成、按 enter |
| 4.0s | slide-in | scene 2 元素入场 |
| 5-9s × 5 次 | sparkle | 关键过程节点(每代 / 每个 token / 每个数据点)|
| 14s | click | 切换到 output scene |
| 17.8s | logo-reveal | hero reveal 时刻 |
| typewriter | type | 每 2 字符触发一次(密度别太高)|
**频段隔离**BGM volume 0.32低频底噪SFX volume 0.55(中高频 punchsparkle 0.7要醒目logo-reveal 0.85(最强 hero moment
**用户控制**
- 必须有 ▶ 启动覆盖(浏览器 autoplay 限制)
- 右上角小 mute 按钮(用户随时切静音)
- 不要做成「翻到这页就强制响」
---
## 2 · 静态 Dashboard 设计要点
Dashboard 是双层结构的 Layer 1PM 不点 ▶ 也能看懂这个 skill。
**布局**3 列 grid或 1 大 + 2 小),每个 panel 解决一个问题:
| Panel 类型 | 解决什么问题 | 案例 |
|---|---|---|
| **Pipeline / Flow Diagram** | 「这个 skill 的工作流程是什么?」| Nuwa 4 阶段 pipeline · Darwin autoresearch loop |
| **Snapshot / State** | 「跑出来的真实数据长什么样?」| Darwin 8 维 rubric snapshot |
| **Trajectory / Evolution** | 「多次运行后怎么变化?」| Darwin 5 代 hill-climb 曲线 |
| **Examples / Gallery** | 「已经产出过哪些东西?」| Nuwa 21 personas gallery |
| **Strip · Example I/O** | 「输入什么 → 输出什么」| Nuwa example strip` nuwa 蒸馏 费曼 → feynman.skill (21 min)` |
**关键约束**
- 信息密度要够(每个 panel 都要承载差异化信息)
- 但不能塞数据 slop每个数字都要有意义
- 配色与 cinematic 一致(同色系,方便切换不突兀)
---
## 3 · 调试与开发工具
任何长动画必须配三个 dev 工具,否则调试会爆炸。
### 工具 1 · `?seek=N` 冻结到第 N 秒
```js
const seek = parseFloat(params.get('seek'));
if (!isNaN(seek)) {
started = true; muted = true;
frozenT = seek; // render() 用这个 t 而不是 elapsed
cinema.classList.add('show'); dash.classList.add('hide');
}
// render() 里:
let t = frozenT !== null ? frozenT : (elapsed % T.DURATION);
```
用法:`http://.../slide.html?seek=12` 直接看第 12 秒画面,不用等播放。
### 工具 2 · `?autoplay=1` 跳过 ▶ overlay
方便 playwright 自动截图测试,也方便嵌入 iframe 时 force 启动。
### 工具 3 · 手动 REPLAY 按钮
右上角小按钮,用户/调试时可以重播任意次。CSS
```css
.replay{position:absolute;top:18px;right:18px;background:rgba(212,165,116,0.1);
border:1px solid rgba(212,165,116,0.3);color:#D4A574;
font-family:monospace;font-size:10px;letter-spacing:.28em;text-transform:uppercase;
padding:6px 12px;border-radius:1px;cursor:pointer;backdrop-filter:blur(6px);z-index:6}
```
---
## 4 · iframe 嵌入坑(如果 cinematic 嵌在 deck 里)
### 坑 1 · 父窗口的 click zone 拦截 iframe 内按钮
如果 deck index.html 加了「左右 22vw 透明 click zone 翻页」,会**覆盖到 iframe 内的 ▶ play 按钮**——用户点按钮被吞成「下一页」。
**修复**click zone 加 `top: 12vh; bottom: 25vh`,给顶部和底部 25% 不拦截,让 iframe 内的中央 ▶ 和右下角 ▶ 都能点。
### 坑 2 · iframe 抢焦点后键盘事件丢失
用户点过 iframe 后,焦点在 iframe 里,父窗口的 ←/→ 键盘事件收不到。
**修复**
```js
iframe.addEventListener('load', () => {
// 注入键盘转发器
const doc = iframe.contentDocument;
doc.addEventListener('keydown', (e) => {
window.dispatchEvent(new KeyboardEvent('keydown', { key: e.key, ... }));
});
// 点击后焦点拽回父窗口
doc.addEventListener('click', () => setTimeout(() => window.focus(), 0));
});
```
### 坑 3 · file:// vs https:// 行为差异
本地 file:// 测好的 cinematic 部署后可能崩,因为:
- file:// 下 iframe contentDocument 同源
- https:// 下也同源(如果同 host但 audio autoplay 限制更严格
**修复**
- 部署前用 `python3 -m http.server` 起本地 HTTP 测试一遍
- BGM 必须等用户点击 ▶ 后再 `bgm.play()`,不要 page-load 立刻播
---
## 5 · 反 pattern 速查表
| ❌ 反 pattern | ✅ 正 pattern |
|---|---|
| 默认 = 黑屏 ▶ overlay | 默认 = 静态 dashboard▶ 是辅助 |
| 4 个 step 横排同屏 fade in | 5 个 scene 全屏切换,每场只 focus 一件事 |
| 复用模板换文案做不同 demo | 每个 demo 独立视觉语言(盖文案能区分) |
| emoji / SVG 手画当素材 | gpt-image-2 大图 + extract_grid 抠图 |
| 无 BGM 无 SFX | BGM + 11 SFX cues 双轨制 |
| 用 setTimeout 链 schedule | requestAnimationFrame + 全局时间轴 T 对象 |
| linear 动画 | Expo / cubic-bezier easing |
| 没有 dev 工具 | `?seek=N` + `?autoplay=1` + REPLAY 按钮 |
| iframe 内的按钮被父 click zone 吞 | click zone 加 top/bottom margin 给按钮让位 |
---
## 6 · 时间预算
按这套 pattern一个完整 cinematic demo含 dashboard
| 任务 | 时间 |
|---|---|
| 设计 5-scene narrative + 视觉语言 | 30 分钟(要慎重,决定独立性)|
| Dashboard 静态布局 + 内容 | 1 小时 |
| Cinematic 5 scenes 实现 | 1.5 小时 |
| Audio cues 调时序 + replay 按钮 | 30 分钟 |
| Playwright 截图验证 5 个关键时刻 | 15 分钟 |
| **单个 demo 总计** | **3-4 小时** |
第二个 demo 复用框架但**视觉语言必须独立**,时间约 2-3 小时。

View File

@@ -0,0 +1,260 @@
# Content Guidelines反AI slop、内容准则、Scale规范
AI设计里最容易掉进去的陷阱。这是一份「不做什么」的清单比「做什么」更重要——因为AI slop是默认值你不主动避免就会发生。
## AI Slop 完整黑名单
### 视觉陷阱
**❌ 激进渐变背景**
- 紫色 → 粉色 → 蓝色 全屏渐变AI生成网页的典型味道
- 任何方向的rainbow gradient
- Mesh gradient铺满背景
- ✅ 如果要用渐变subtle、单色系、有意图地点缀比如button hover
**❌ 圆角卡片 + 左border accent色**
```css
/* 这是AI味卡片的典型签名 */
.card {
border-radius: 12px;
border-left: 4px solid #3b82f6;
padding: 16px;
}
```
这种卡片在AI生成的Dashboard里泛滥。想做强调用更有设计感的方式背景色对比、字重/字号对比、plain分隔线、或者干脆不分卡片。
**❌ Emoji 装饰**
除非品牌本身使用emoji比如Notion、Slack否则不要在UI上放emoji。**尤其不要**
- 标题前的 🚀 ⚡️ ✨ 🎯 💡
- Feature列表的 ✅
- CTA按钮里的 →箭头单独出现OKemoji箭头不行
没图标用真icon库Lucide/Heroicons/Phosphor或者用placeholder。
**❌ SVG 画 imagery**
不要试图用SVG画人物、场景、设备、物品、抽象艺术。AI画的SVG imagery一眼就是AI味幼稚且廉价。**一个灰色矩形+"插画位 1200×800"的文字标签比一个拙劣的SVG hero illustration强100倍**。
唯一可以用SVG的场景
- 真正的icon16×16到32×32级别
- 几何图形做装饰元素
- Data viz的chart
**❌ 过多iconography**
不是每个标题/feature/section都需要icon。滥用icon会让界面像toy。Less is more。
**❌ "Data slop"**
编造的stats装饰
- "10,000+ happy customers" (你都不知道有没有)
- "99.9% uptime" (没有真数据就别写)
- 用图标+数字+词组成的装饰"metric cards"
- Mock table里的假数据装点得花里胡哨
如果没真数据留placeholder或问用户要。
**❌ "Quote slop"**
编造的用户评价、名人名言装饰页面。留placeholder问用户要真quote。
### 字体陷阱
**❌ 避免这些烂大街字体**
- InterAI生成的网页默认
- Roboto
- Arial / Helvetica
- 纯system font stack
- FrauncesAI发现了这个就用滥了
- Space Grotesk最近AI的最爱
**✅ 用有特点的display+body配对**。灵感方向:
- 衬线display + 无衬线bodyeditorial feel
- Mono display + sans bodytechnical feel
- Heavy display + light bodycontrast
- Variable font做hero的粗细动画
字体资源:
- Google Fonts的冷门好选项Instrument Serif、Cormorant、Bricolage Grotesque、JetBrains Mono
- 开源字体站Fraunces的兄弟字体、Adobe Fonts
- 不要凭空发明字体名
### 色彩陷阱
**❌ 凭空发明颜色**
不要从头设计一整套不熟悉的色彩。这通常不和谐。
**✅ 策略**
1. 有品牌色 → 用品牌色缺的color token用oklch插值
2. 没有品牌色但有参考 → 从参考产品截图吸色
3. 完全从零 → 选一个known的配色系统Radix Colors / Tailwind默认palette / Anthropic brand不要自己调
**oklch定义色彩**是最现代的做法:
```css
:root {
--primary: oklch(0.65 0.18 25); /* 温暖的terracotta */
--primary-light: oklch(0.85 0.08 25); /* 同色系浅色 */
--primary-dark: oklch(0.45 0.20 25); /* 同色系深色 */
}
```
oklch能保证调整亮度时色相不漂移比hsl好用。
**❌ 夜间模式随手加反色**
不是简单invert颜色。好的dark mode需要重新调整饱和度、对比度、accent色。不想做dark mode就别做。
### Layout陷阱
**❌ Bento grid 过度泛滥**
每个AI生成的landing page都想搞bento。除非你的信息structure确实适合bento否则用其他layout。
**❌ 大hero + 3-column features + testimonials + CTA**
这个landing page模板被用烂了。想创新就真创新。
**❌ Card grid里每个card长一样**
Asymmetric、不同大小的cards、有的带image有的只有文字、有的跨列——这才像真设计师做的。
## 内容准则
### 1. Don't add filler content
每个元素都必须earn its place。空白是设计问题用**构图**解决(对比、节奏、留白),**不是**靠内容填满。
**判断filler的问题**
- 如果去掉这段内容,设计会变差吗?答案若是"不会",就去掉。
- 这个元素解决了什么真问题?如果是"让页面不那么空",删掉。
- 这个stats/quote/feature有真数据支持吗没有就不要凭空写。
「One thousand no's for every yes」。
### 2. Ask before adding material
你觉得多加一段/一页/一个section会更好先问用户不要单方面加。
原因:
- 用户知道他的受众比你清楚
- 加内容有成本,用户可能不想要
- 单方面加内容违反了"junior designer汇报工作"的关系
### 3. Create a system up front
探索完design context后**先口头说出你要用的系统**,让用户确认:
```markdown
我的设计系统:
- 色彩:#1A1A1A主体 + #F0EEE6背景 + #D97757 accent来自你的品牌
- 字型Instrument Serif做display + Geist Sans做body
- 节奏section title用full-bleed彩色背景 + 白字普通section用白背景
- 图像hero用full-bleed照片feature section用placeholder等你提供
- 最多用2种背景色避免杂乱
确认这个方向我就开始做。
```
用户确认后再动手。这个check-in能避免"做完一半发现方向错"。
## Scale 规范
### 幻灯片1920×1080
- 正文最小 **24px**,理想 28-36px
- 标题 60-120px
- Section title 80-160px
- Hero headline 可以用 180-240px 的大字
- 永远不要用 <24px 的字放幻灯片
### 印刷文档
- 正文最小 **10pt**(≈13.3px理想 11-12pt
- 标题 18-36pt
- Caption 8-9pt
### Web和移动端
- 正文最小 **14px**老年人友好用16px
- 移动端正文 **16px**避免iOS自动缩放
- Hit target可点击元素最小 **44×44px**
- 行高 1.5-1.7中文1.7-1.8
### 对比度
- 正文 vs 背景 **至少 4.5:1**WCAG AA
- 大字 vs 背景 **至少 3:1**
- 用Chrome DevTools的accessibility工具检查
## CSS 神器
**高级CSS特性**是设计师的好朋友大胆用
### 排版
```css
/* 让标题换行更自然,不会最后一行孤单单一个词 */
h1, h2, h3 { text-wrap: balance; }
/* 正文换行,避免寡孀和孤儿 */
p { text-wrap: pretty; }
/* 中文排版神器:标点挤压、行首行尾控制 */
p {
text-spacing-trim: space-all;
hanging-punctuation: first;
}
```
### Layout
```css
/* CSS Grid + named areas = 可读性爆表 */
.layout {
display: grid;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
}
/* Subgrid对齐卡片内容 */
.card { display: grid; grid-template-rows: subgrid; }
```
### 视觉效果
```css
/* 有设计感的滚动条 */
* { scrollbar-width: thin; scrollbar-color: #666 transparent; }
/* 玻璃拟态(克制使用) */
.glass {
backdrop-filter: blur(20px) saturate(150%);
background: color-mix(in oklch, white 70%, transparent);
}
/* View transitions API让页面切换丝滑 */
@view-transition { navigation: auto; }
```
### 交互
```css
/* :has()选择器让条件样式变容易 */
.card:has(img) { padding-top: 0; } /* 有图片的卡片无顶padding */
/* container queries让组件真的响应式 */
@container (min-width: 500px) { ... }
/* 新的color-mix函数 */
.button:hover {
background: color-mix(in oklch, var(--primary) 85%, black);
}
```
## 决策速查:当你犹豫时
- 想加个渐变?→ 大概率不加
- 想加个emoji?→ 不加
- 想给卡片加圆角+border-left accent?→ 不加换其他方式
- 想用SVG画个hero插画?→ 不画用placeholder
- 想加一段quote装饰?→ 先问用户有没有真quote
- 想加一排icon features?→ 先问要不要icon可能不需要
- 用Inter?→ 换一个更有特点的
- 用紫色渐变?→ 换一个有根据的配色
**当你觉得"加一下会更好看"的时候——那通常是AI slop的征兆**先做最简的版本只在用户要求时加

View File

@@ -0,0 +1,199 @@
# 设计评审深度指南
> Phase 7 的详细参考。提供评分标准、场景侧重点、常见问题清单。
---
## 评分标准详解
### 1. 哲学一致性Philosophy Alignment
| 分数 | 标准 |
|------|------|
| 9-10 | 设计完美体现了选定哲学的核心精神,每个细节都有哲学依据 |
| 7-8 | 整体方向正确,核心特征到位,个别细节偏离 |
| 5-6 | 能看出意图,但执行时混入了其他风格元素,不够纯粹 |
| 3-4 | 仅在表面模仿,未理解哲学内核 |
| 1-2 | 与选定哲学基本无关 |
**评审要点**
- 是否使用了该设计师/机构的标志性手法?
- 色彩、字体、布局是否符合该哲学体系?
- 有没有「自相矛盾」的元素如选了Kenya Hara却塞满内容
### 2. 视觉层级Visual Hierarchy
| 分数 | 标准 |
|------|------|
| 9-10 | 用户视线自然沿设计者意图流动,信息获取零摩擦 |
| 7-8 | 主次关系清晰偶有1-2处层级模糊 |
| 5-6 | 能分出标题和正文,但中间层级混乱 |
| 3-4 | 信息平铺,没有明确的视觉入口 |
| 1-2 | 混乱,用户不知道先看哪里 |
**评审要点**
- 标题与正文的字号对比是否足够至少2.5倍)
- 颜色/粗细/大小是否建立了3-4个清晰层级
- 留白是否在引导视线?
- 「眯眼测试」:眯起眼看,层级是否仍然清晰?
### 3. 细节执行Craft Quality
| 分数 | 标准 |
|------|------|
| 9-10 | 像素级精确,对齐、间距、颜色无任何瑕疵 |
| 7-8 | 整体精致有1-2处微小对齐/间距问题 |
| 5-6 | 基本对齐,但间距不统一,颜色使用不够系统 |
| 3-4 | 明显的对齐错误、间距混乱、颜色过多 |
| 1-2 | 粗糙,看起来像草稿 |
**评审要点**
- 是否使用了统一的间距系统如8pt网格
- 同类元素的间距是否一致?
- 颜色数量是否受控通常不超过3-4种
- 字体家族是否统一通常不超过2种
- 边缘对齐是否精确?
### 4. 功能性Functionality
| 分数 | 标准 |
|------|------|
| 9-10 | 每个设计元素都服务于目标,零冗余 |
| 7-8 | 功能导向明确,有少量可删减的装饰 |
| 5-6 | 基本可用,但有明显的装饰性元素分散注意力 |
| 3-4 | 形式大于功能,用户需要努力寻找信息 |
| 1-2 | 完全被装饰淹没,失去了传达信息的能力 |
**评审要点**
- 删掉任何一个元素,设计会变差吗?(如果不会,就应该删)
- CTA/关键信息是否在最显眼的位置?
- 是否有「因为好看所以加上去」的元素?
- 信息密度与载体是否匹配PPT不宜太密PDF可以更密
### 5. 创新性Originality
| 分数 | 标准 |
|------|------|
| 9-10 | 令人耳目一新,在该哲学框架内找到了独特表达 |
| 7-8 | 有自己的想法,不是简单的模板套用 |
| 5-6 | 中规中矩,看起来像模板 |
| 3-4 | 大量使用了cliché如渐变圆球代表AI |
| 1-2 | 完全是模板或素材拼凑 |
**评审要点**
- 是否避免了常见cliché见下方「常见问题清单」
- 在遵循设计哲学的同时是否有个人表达?
- 是否有「意想不到但很合理」的设计决策?
---
## 场景评审侧重
不同输出类型的评审重点不同:
| 场景 | 最重要维度 | 次重要 | 可放宽 |
|------|-----------|--------|--------|
| 公众号封面/配图 | 创新性、视觉层级 | 哲学一致性 | 功能性(单图不涉及交互) |
| 信息图 | 功能性、视觉层级 | 细节执行 | 创新性(准确优先) |
| PPT/Keynote | 视觉层级、功能性 | 细节执行 | 创新性(清晰优先) |
| PDF/白皮书 | 细节执行、功能性 | 视觉层级 | 创新性(专业优先) |
| 落地页/官网 | 功能性、视觉层级 | 创新性 | —(全面要求) |
| App UI | 功能性、细节执行 | 视觉层级 | 哲学一致性(可用性优先) |
| 小红书配图 | 创新性、视觉层级 | 哲学一致性 | 细节执行(氛围优先) |
---
## 常见设计问题 Top 10
### 1. AI科技cliché
**问题**:渐变圆球、数字雨、蓝色电路板、机器人脸
**为什么是问题**:用户已经对这些视觉疲劳,无法区分你和其他人
**修复**:用抽象隐喻替代直白符号(如用「对话」的隐喻而非聊天气泡图标)
### 2. 字号层级不足
**问题**:标题和正文差距太小(<2.5倍
**为什么是问题**用户无法快速定位关键信息
**修复**标题至少为正文的3倍如正文16px 标题48-64px
### 3. 颜色过多
**问题**使用5种以上颜色没有主次
**为什么是问题**视觉混乱品牌感弱
**修复**限制为1个主色+1个辅色+1个强调色+灰阶
### 4. 间距不统一
**问题**元素间距随意没有系统
**为什么是问题**看起来不专业视觉节奏混乱
**修复**建立8pt网格系统间距只用8/16/24/32/48/64px
### 5. 留白不足
**问题**所有空间都被内容填满
**为什么是问题**信息拥挤导致阅读疲劳反而降低信息传达效率
**修复**留白至少占总面积40%极简风格60%+
### 6. 字体过多
**问题**使用3种以上字体
**为什么是问题**视觉噪音削弱统一感
**修复**最多2种字体1种标题+1种正文用字重和大小创造变化
### 7. 对齐不一致
**问题**有的左对齐有的居中有的右对齐
**为什么是问题**破坏视觉秩序感
**修复**选定一种对齐方式推荐左对齐全局统一
### 8. 装饰大于内容
**问题**背景图案/渐变/阴影抢了主要内容的风头
**为什么是问题**本末倒置用户来看信息不是看装饰
**修复**:「如果删掉这个装饰设计会变差吗?」如果不会就删
### 9. 赛博霓虹滥用
**问题**深蓝底(#0D1117) + 霓虹色发光效果
**为什么是问题**默认审美禁区 skill 的品位基线且已成为最大 cliché 之一——用户可按自己品牌 override
**修复**选择更有辨识度的配色方案参考20种风格的色彩系统
### 10. 信息密度与载体不匹配
**问题**PPT里放了一整页文字 / 封面图里塞了10个元素
**为什么是问题**不同载体的最佳信息密度不同
**修复**
- PPT每页1个核心观点
- 封面图1个视觉焦点
- 信息图分层展示
- PDF可以更密但需要清晰的导航
---
## 评审输出模板
```
## 设计评审报告
**总体评分**X.X/10 [优秀(8+)/良好(6-7.9)/需改进(4-5.9)/不合格(<4)]
**分项评分**
- 哲学一致性X/10 [一句话说明]
- 视觉层级X/10 [一句话说明]
- 细节执行X/10 [一句话说明]
- 功能性X/10 [一句话说明]
- 创新性X/10 [一句话说明]
### 优点Keep
- [具体指出做得好的地方,用设计语言描述]
### 问题Fix
[按严重程度排序]
**1. [问题名称]** — ⚠️致命 / ⚡重要 / 💡优化
- 当前:[描述现状]
- 问题:[为什么这是问题]
- 修复:[具体操作,含数值]
### 快速修复清单Quick Wins
如果只有5分钟优先做这3件事
- [ ] [最有影响力的修复]
- [ ] [第二重要的修复]
- [ ] [第三重要的修复]
```
---
**版本**v1.0
**更新日期**2026-02-13

View File

@@ -0,0 +1,213 @@
# Design Context从已有上下文出发
**这是这个skill最重要的one thing。**
好的hi-fi设计一定是从已有design context长出来的。**凭空做hi-fi是last resort一定会产出generic的作品**。所以每次设计任务开始,先问:有没有可以参考的东西?
## 什么是Design Context
按优先级从高到低:
### 1. 用户的Design System/UI Kit
用户自己产品已有的组件库、色彩token、字型规范、icon系统。**最完美的情况**。
### 2. 用户的Codebase
如果用户给了代码库里面就有活生生的组件实现。Read那些组件文件
- `theme.ts` / `colors.ts` / `tokens.css` / `_variables.scss`
- 具体的组件Button.tsx、Card.tsx
- Layout scaffoldApp.tsx、MainLayout.tsx
- Global stylesheets
**读代码抄exact values**hex codes、spacing scale、font stack、border radius。不要凭记忆重画。
### 3. 用户已发布的产品
如果用户有上线的产品但没给代码用Playwright或让用户提供截图。
```bash
# 用Playwright截图一个公开URL
npx playwright screenshot https://example.com screenshot.png --viewport-size=1920,1080
```
让你看到真实的视觉vocabulary。
### 4. 品牌指南/Logo/已有素材
用户可能有Logo文件、品牌色规范、营销物料、slide模板。这些都是context。
### 5. 竞品参考
用户说"像XX网站那样"——让他提供URL或截图。**不要**凭你训练数据里的模糊印象做。
### 6. 已知的design systemfallback
如果以上都没有用公认的设计系统作为base
- Apple HIG
- Material Design 3
- Radix Colors配色
- shadcn/ui组件
- Tailwind默认palette
明确告诉用户你用的什么,让他知道这是起点不是定稿。
## 获取Context的流程
### Step 1问用户
任务开始时的必问清单(来自`workflow.md`
```markdown
1. 你有现成的design system/UI kit/组件库吗?在哪?
2. 有品牌指南、色彩/字体规范吗?
3. 可以给我现有产品的截图或URL吗
4. 有codebase我可以读吗
```
### Step 2用户说"没有"时,帮他找
别直接放弃。尝试:
```markdown
让我看看有没有线索:
- 你之前的项目有相关设计吗?
- 公司的marketing网站用什么色彩/字型?
- 你产品的Logo什么风格能给我一张吗
- 有什么你欣赏的产品作为参考?
```
### Step 3Read所有能找到的context
如果用户给了codebase路径你读
1. **先list文件结构**找style/theme/component相关的文件
2. **读theme/token文件**lift具体的hex/px values
3. **读2-3个代表性组件**看视觉vocabularyhover state、shadow、border、padding node pattern
4. **读global stylesheet**基础重置、font loading
5. **如果有Figma链接/截图**:看图,但**更相信代码**
**重要****不要**看了一眼就凭印象做。读下来有30+个具体values才真的lift到了。
### Step 4Vocalize你要用的系统
看完context后告诉用户你要用的系统
```markdown
根据你的codebase和产品截图我提炼的设计系统
**色彩**
- Primary: #C27558从tokens.css
- Background: #FDF9F0
- Text: #1A1A1A
- Muted: #6B6B6B
**字型**
- Display: Instrument Serif从global.css的@font-face
- Body: Geist Sans
- Mono: JetBrains Mono
**Spacing**来自你的scale系统
- 4, 8, 12, 16, 24, 32, 48, 64
**Shadow pattern**
- `0 1px 2px rgba(0,0,0,0.04)`subtle card
- `0 10px 40px rgba(0,0,0,0.1)`elevated modal
**Border-radius**
- 小组件 4px卡片 12px按钮 8px
**component vocabulary**
- Buttonfilled primaryoutlined secondaryghost tertiary全部圆角8px
- Card白色背景subtle shadow无border
我按这套系统开始做。确认没问题?
```
用户确认后再动手。
## 凭空做设计没Context时的 fallback
**强烈警告**:这种情况下的产出质量会显著下降。明确告诉用户。
```markdown
你没有design context我就只能基于通用直觉做。
产出会是"看起来OK但缺乏独特性"的东西。
你愿意继续,还是先补一些参考材料?
```
用户执意要你做,按这个顺序做决策:
### 1. 选一个aesthetic direction
不要给generic结果。挑一个明确方向
- brutally minimal
- editorial/magazine
- brutalist/raw
- organic/natural
- luxury/refined
- playful/toy
- retro-futuristic
- soft/pastel
告诉用户你选了哪个。
### 2. 选一个known design system作为骨架
- 用Radix Colors做配色https://www.radix-ui.com/colors
- 用shadcn/ui做组件vocabularyhttps://ui.shadcn.com
- 用Tailwind spacing scale4的倍数
### 3. 选有特点的字体配对
不要用Inter/Roboto。建议组合从Google Fonts白嫖
- Instrument Serif + Geist Sans
- Cormorant Garamond + Inter Tight
- Bricolage Grotesque + Söhne付费
- Fraunces + Work Sans注意Fraunces已经被AI用烂
- JetBrains Mono + Geist Sanstechnical feel
### 4. 每个关键决策都有reasoning
不要默默选。在HTML的comment里写
```html
<!--
Design decisions:
- Primary color: warm terracotta (oklch 0.65 0.18 25) — fits the "editorial" direction
- Display: Instrument Serif for humanist, literary feel
- Body: Geist Sans for cleanness contrast
- No gradients — committed to minimal, no AI slop
- Spacing: 8px base, golden ratio friendly (8/13/21/34)
-->
```
## Import策略用户给了codebase
如果用户说"import这个codebase做参考"
### 小型(<50文件
全部Read把context内化。
### 中型50-500文件
Focus在
- `src/components/``components/`
- 所有styles/tokens/theme相关的文件
- 2-3个代表性的整页组件Home.tsx、Dashboard.tsx
### 大型(>500文件
让用户指明focus
- "我要做settings页面" → 读现有的settings相关
- "我要做一个新的feature" → 读整体shell + 最接近的参考
- 不求全,求准
## 和Figma/设计稿的配合
如果用户给了Figma链接
- **不要**期望你能直接"转Figma为HTML"——那需要额外工具
- Figma链接通常不公开可访问
- 让用户:导出为**截图**发给你 + 告诉你具体的color/spacing values
如果只给了Figma截图告诉用户
- 我能看到视觉但取不到精确values
- 关键数字hex、px请告诉我或者export as codeFigma支持
## 最后的提醒
**一个项目的设计质量上限由你拿到的context质量决定**
花10分钟收集context比花1小时凭空画hi-fi更有价值。
**遇到没context的情况优先问用户要而不是硬上**

View File

@@ -0,0 +1,591 @@
# 设计哲学风格库20种体系
> 用于视觉设计(网页/PPT/PDF/信息图/配图/App等的设计风格库。
> 每种风格提供:哲学内核 + 核心特征 + 提示词DNA与场景模板组合使用
## 风格×场景×执行路径 速查表
| 风格 | 网页 | PPT | PDF | 信息图 | 封面 | AI生成 | 最佳路径 |
|------|:---:|:---:|:---:|:-----:|:---:|:-----:|---------|
| 01 Pentagram | ★★★ | ★★★ | ★★☆ | ★★☆ | ★★★ | ★☆☆ | HTML |
| 02 Stamen Design | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★☆ | ★★☆ | 混合 |
| 03 Information Architects | ★★★ | ★☆☆ | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | HTML |
| 04 Fathom | ★★☆ | ★★★ | ★★★ | ★★★ | ★★☆ | ★☆☆ | HTML |
| 05 Locomotive | ★★★ | ★★☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★☆ | 混合 |
| 06 Active Theory | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★★ | AI生成 |
| 07 Field.io | ★★☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
| 08 Resn | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★☆ | AI生成 |
| 09 Experimental Jetset | ★★☆ | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★☆ | 混合 |
| 10 Müller-Brockmann | ★★☆ | ★★★ | ★★★ | ★★★ | ★★☆ | ★☆☆ | HTML |
| 11 Build | ★★★ | ★★★ | ★★☆ | ★☆☆ | ★★★ | ★☆☆ | HTML |
| 12 Sagmeister & Walsh | ★★☆ | ★★★ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
| 13 Zach Lieberman | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
| 14 Raven Kwok | ★☆☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
| 15 Ash Thorp | ★★☆ | ★★☆ | ★☆☆ | ★☆☆ | ★★★ | ★★★ | AI生成 |
| 16 Territory Studio | ★★☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
| 17 Takram | ★★★ | ★★★ | ★★★ | ★★☆ | ★★☆ | ★☆☆ | HTML |
| 18 Kenya Hara | ★★☆ | ★★★ | ★★★ | ★☆☆ | ★★★ | ★☆☆ | HTML |
| 19 Irma Boom | ★☆☆ | ★★☆ | ★★★ | ★★☆ | ★★★ | ★★☆ | 混合 |
| 20 Neo Shen | ★★☆ | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
> 场景适配:★★★ = 强烈推荐 / ★★☆ = 适合 / ★☆☆ = 需改造
> AI生成★★★ = 直出效果好 / ★★☆ = 需调整 / ★☆☆ = 建议HTML执行
> 最佳路径AI生成图片直出/ HTML代码渲染数据精确/ 混合HTML布局+AI配图
**核心规律**:有明确视觉元素的风格(插画/粒子/生成艺术AI直出效果好依赖精确排版和数据的风格网格/信息架构/留白HTML渲染更可控。
---
## 一、信息建筑派01-04
> 哲学:「数据不是装饰,是建筑材料」
### 01. Pentagram - Michael Bierut风格
**哲学**:字体即语言,网格即思想
**核心特征**
- 极度克制的颜色(黑白+1个品牌色
- 瑞士网格系统的现代演绎
- 字体排印作为主要视觉语言
- 负空间的战略性使用60%+留白)
**提示词DNA**
```
Pentagram/Michael Bierut style:
- Extreme typographic hierarchy, Helvetica/Univers family
- Swiss grid with precise mathematical spacing
- Black/white + one accent color (#HEX)
- Information architecture as visual structure
- 60%+ whitespace ratio
- Data visualization as primary decoration
```
**代表作**Hillary Clinton 2016 campaign identity
**搜索关键词**pentagram hillary logo system
---
### 02. Stamen Design - 数据诗学
**哲学**:让数据成为可触摸的风景
**核心特征**
- 地图学思维应用于信息设计
- 算法生成的有机图形
- 温暖的数据可视化色调(赭石、鼠尾草绿、深蓝)
- 可交互的层级系统
**提示词DNA**
```
Stamen Design aesthetic:
- Cartographic approach to data visualization
- Organic, algorithm-generated patterns
- Warm palette (terracotta, sage green, deep blues)
- Layered information like topographic maps
- Hand-crafted feel despite digital precision
- Soft shadows and depth
```
**代表作**COVID-19 surge map
**搜索关键词**stamen covid map visualization
---
### 03. Information Architects - 内容优先原则
**哲学**:设计不是装饰,是内容的建筑
**核心特征**
- 极端的内容层级清晰度
- 只使用系统字体(优化阅读)
- 蓝色超链接传统的坚守
- 性能即美学
**提示词DNA**
```
Information Architects philosophy:
- Content-first hierarchy, zero decorative elements
- System fonts only (SF Pro/Roboto/Inter)
- Classic blue hyperlinks (#0000EE)
- Reading-optimized line length (66 characters)
- Progressive disclosure of depth
- Text-heavy, fast-loading design
```
**代表作**iA Writer app
**搜索关键词**information architects ia writer
---
### 04. Fathom Information Design - 科学叙事
**哲学**:每一个像素都必须承载信息
**核心特征**
- 科学期刊的严谨+设计的优雅
- 定量数据的精确可视化
- 冷静的专业色调(灰、海军蓝)
- 注释与引用系统的设计化
**提示词DNA**
```
Fathom Information Design style:
- Scientific journal aesthetic meets modern design
- Precise data visualization (charts, timelines, scatter plots)
- Neutral scheme (grays, navy, one highlight color)
- Footnote/citation design integrated into layout
- Clean sans-serif (GT America/Graphik)
- Information density without clutter
```
**代表作**Bill & Melinda Gates Foundation年度报告
**搜索关键词**fathom information design gates foundation
---
## 二、运动诗学派05-08
> 哲学:「技术本身就是一种流动的诗」
### 05. Locomotive - 滚动叙事大师
**哲学**:滚动不是浏览,是旅程
**核心特征**
- 丝滑的视差滚动
- 电影化的分镜叙事
- 大胆的空间留白
- 动态元素的精确编排
**提示词DNA**
```
Locomotive scroll narrative style:
- Film-like scene composition with parallax depth
- Generous vertical spacing between sections
- Bold typography emerging from darkness
- Smooth motion blur effects
- Dark mode (near-black backgrounds)
- Strategic glowing accents
- Hero sections 100vh tall
```
**代表作**Lusion.co website
**搜索关键词**locomotive scroll lusion
---
### 06. Active Theory - WebGL诗人
**哲学**:让技术可见化即让技术可理解
**核心特征**
- 3D粒子系统作为核心元素
- 实时渲染的数据可视化
- 鼠标交互驱动的世界构建
- 霓虹与深空的配色
**提示词DNA**
```
Active Theory WebGL aesthetic:
- Particle systems representing data flow
- 3D visualization in depth space
- Neon gradients (cyan/magenta/electric blue) on dark
- Mouse-reactive environment
- Depth of field and bokeh effects
- Floating UI with glassmorphism
```
**代表作**NASA Prospect
**搜索关键词**active theory nasa webgl
---
### 07. Field.io - 算法美学
**哲学**:代码即设计师
**核心特征**
- 生成艺术系统
- 每次访问都不同的动态图形
- 抽象几何的智能编排
- 技术感与艺术性的平衡
**提示词DNA**
```
Field.io generative design style:
- Abstract geometric patterns, algorithmically generated
- Dynamic composition that feels computational
- Monochromatic base with vibrant accent
- Mathematical precision in spacing
- Voronoi diagrams or Delaunay triangulation
- Clean code aesthetic
```
**代表作**British Council digital installations
**搜索关键词**field.io generative design
---
### 08. Resn - 叙事驱动的交互
**哲学**:每个点击都推进故事
**核心特征**
- 游戏化的用户旅程
- 强烈的情感化设计
- 插画与代码的深度结合
- 非线性的探索体验
**提示词DNA**
```
Resn interactive storytelling approach:
- Illustrative style mixed with UI elements
- Gamified exploration (progress indicators)
- Warm color palette despite tech subject
- Character-driven design
- Scroll-triggered animations
- Editorial illustration meets product design
```
**代表作**Resn.co.nz portfolio
**搜索关键词**resn interactive storytelling
---
## 三、极简主义派09-12
> 哲学:「删减到无法再删」
### 09. Experimental Jetset - 概念极简
**哲学**:一个想法=一个形式
**核心特征**
- 单一视觉隐喻贯穿整个设计
- 蓝/红/黄+黑白的蒙德里安色系
- 字体即图形
- 反商业的诚实设计
**提示词DNA**
```
Experimental Jetset conceptual minimalism:
- Single visual metaphor for entire design
- Primary colors only (red/blue/yellow) + black/white
- Typography as main graphic element
- Grid-based with deliberate rule-breaking
- No photography, only type and geometry
- Anti-commercial, honest aesthetic
```
**代表作**Whitney Museum identity
**搜索关键词**experimental jetset whitney responsive w
---
### 10. Müller-Brockmann传承 - 瑞士网格纯粹主义
**哲学**:客观性即美
**核心特征**
- 数学精确的网格系统8pt基线
- 绝对的左对齐或居中
- 单色或双色方案
- 功能主义至上
**提示词DNA**
```
Josef Müller-Brockmann Swiss modernism:
- Mathematical grid system (8pt baseline)
- Strict alignment (flush left or centered)
- Two-color maximum (black + one accent)
- Akzidenz-Grotesk or similar rationalist typeface
- No decorative elements
- Timeless, objective aesthetic
```
**代表作**《Grid Systems in Graphic Design》
**搜索关键词**muller brockmann grid systems poster
---
### 11. Build - 当代极简品牌
**哲学**:精致的简单比复杂更难
**核心特征**
- 奢侈品级的留白70%+
- 微妙的字重对比200-600
- 单一强调色的战略使用
- 呼吸感的节奏
**提示词DNA**
```
Build studio luxury minimalism:
- Generous whitespace (70%+ of area)
- Subtle typography weight shifts (200 to 600)
- Single accent color used sparingly
- High-end product photography aesthetic
- Soft shadows and subtle gradients
- Golden ratio proportions
```
**代表作**Build studio portfolio
**搜索关键词**build studio london branding
---
### 12. Sagmeister & Walsh - 快乐极简
**哲学**:美即功能的情感维度
**核心特征**
- 意外的色彩爆发
- 手工感与数字的融合
- 正能量的视觉语言
- 实验性但可读
**提示词DNA**
```
Sagmeister & Walsh joyful philosophy:
- Unexpected color bursts on minimal base
- Handmade elements (physical objects in digital)
- Optimistic visual language
- Experimental typography that remains legible
- Human warmth through imperfection
- Mix of analog and digital aesthetics
```
**代表作**The Happy Show
**搜索关键词**sagmeister walsh happy show
---
## 四、实验先锋派13-16
> 哲学:「打破规则即创造规则」
### 13. Zach Lieberman - 代码诗学
**哲学**:编程即绘画
**核心特征**
- 手绘感的算法图形
- 实时生成艺术
- 黑白的纯粹表达
- 工具本身的可见性
**提示词DNA**
```
Zach Lieberman code-as-art style:
- Hand-drawn aesthetic generated by code
- Black and white only, no color
- Real-time generative patterns
- Sketch-like line quality
- Visible process/grid/construction lines
- Poetic interpretation of algorithms
```
**代表作**openFrameworks creative coding
**搜索关键词**zach lieberman openframeworks generative
---
### 14. Raven Kwok - 参数化美学
**哲学**:系统的美胜过个体的美
**核心特征**
- 分形与递归图形
- 黑白高对比
- 建筑化的信息结构
- 东方园林的算法演绎
**提示词DNA**
```
Raven Kwok parametric aesthetic:
- Fractal patterns and recursive structures
- High-contrast black and white
- Architectural visualization of data
- Chinese garden principles in algorithm form
- Intricate detail that rewards zooming
- Processing/Creative coding aesthetic
```
**代表作**Raven Kwok generative art exhibitions
**搜索关键词**raven kwok processing generative art
---
### 15. Ash Thorp - 赛博诗意
**哲学**:未来不是冰冷的,是孤独的诗
**核心特征**
- 电影级的光影
- 赛博朋克的温暖版本(橙/青,非冷蓝)
- 故事性的概念设计
- 工业美学的精致化
**提示词DNA**
```
Ash Thorp cinematic concept art:
- Film-grade lighting and atmospheric effects
- Warm cyberpunk (orange/teal, NOT cold blue)
- Industrial design meets luxury
- Narrative concept art feel
- Volumetric lighting and god rays
- Blade Runner warmth over Tron coldness
```
**代表作**Ghost in the Shell concept art
**搜索关键词**ash thorp ghost shell concept art
---
### 16. Territory Studio - 屏幕界面虚构
**哲学**未来UI的今日想象
**核心特征**
- 科幻电影中的屏幕设计FUI
- 全息投影感
- 多层叠加的数据可视化
- 可信的未来感
**提示词DNA**
```
Territory Studio FUI (Fantasy User Interface):
- Fantasy User Interface design
- Holographic projection aesthetics
- Orange/amber monochrome or cyan accents
- Multiple overlapping data layers
- Believable future technology
- Technical readouts and data streams
```
**代表作**Blade Runner 2049 screen graphics
**搜索关键词**territory studio blade runner interface
---
## 五、东方哲学派17-20
> 哲学:「留白即内容」
### 17. Takram - 日式思辨设计
**哲学**:技术是思考的媒介
**核心特征**
- 概念原型的优雅
- 柔和的科技感(圆角、柔和阴影)
- 图表即艺术
- 谦逊的精致
**提示词DNA**
```
Takram Japanese speculative design:
- Elegant concept prototypes and diagrams
- Soft tech aesthetic (rounded corners, gentle shadows)
- Charts and diagrams as art pieces
- Modest sophistication
- Neutral natural colors (beige, soft gray, muted green)
- Design as philosophical inquiry
```
**代表作**NHK Fabricated City
**搜索关键词**takram nhk data visualization
---
### 18. Kenya Hara - 空的设计
**哲学**:设计不是填充,是清空
**核心特征**
- 极致的留白80%+
- 纸张质感的数字化
- 白色的层次(暖白、冷白、米白)
- 触觉的视觉化
**提示词DNA**
```
Kenya Hara "emptiness" design:
- Extreme whitespace (80%+)
- Paper texture and tactility in digital form
- Layers of white (warm white, cool white, off-white)
- Minimal color (if any, very desaturated)
- Design by subtraction not addition
- Zen simplicity
```
**代表作**Muji art direction, 《Designing Design》
**搜索关键词**kenya hara designing design muji
---
### 19. Irma Boom - 书籍建筑师
**哲学**:信息的物理诗学
**核心特征**
- 非线性的信息架构
- 边缘与边界的游戏
- 意外的颜色组合(粉+红、橙+棕)
- 手工艺的数字转译
**提示词DNA**
```
Irma Boom book architecture style:
- Non-linear information structure
- Play with edges, margins, boundaries
- Unexpected color combos (pink+red, orange+brown)
- Handcraft translated to digital
- Dense information inviting exploration
- Editorial design, unconventional grid
```
**代表作**SHV Think Book (2136 pages)
**搜索关键词**irma boom shv think book
---
### 20. Neo Shen - 东方光影诗
**哲学**:技术需要人的温度
**核心特征**
- 水墨晕染的数字化
- 柔和的光晕效果
- 诗意的留白
- 情感化的色彩(深蓝、暖灰、柔金)
**提示词DNA**
```
Neo Shen poetic Chinese aesthetic:
- Digital interpretation of ink wash painting
- Soft glow and light diffusion effects
- Poetic negative space
- Emotional palette (deep blues, warm grays, soft gold)
- Calligraphic influences in typography
- Atmospheric depth
```
**代表作**Neo Shen digital art series
**搜索关键词**neo shen digital ink wash art
---
## 提示词使用说明
**组合公式**`[风格提示词DNA] + [场景模板见scene-templates.md] + [具体内容]`
### 核心原则描述情绪而非布局Mood, Not Layout
AI图像生成的关键短提示词 > 长提示词。描述3句情绪和内容比30行布局细节效果更好。
| 杀死多样性的写法 | 激发创造力的写法 |
|----------------|----------------|
| 指定颜色比例60%/25%/15% | 描述情绪("warm like Sunday morning" |
| 规定布局位置("标题居中,图片右侧" | 引用具体美学("Pentagram editorial feel" |
| 限制角色姿势和表情 | 让AI自然诠释风格 |
| 列出所有要包含的视觉元素 | 描述观众应该感受到什么 |
### Good / Bad 示例
**Bad — 过度约束AI生成出来空且平**
```
Professional presentation slide. Dark background, light text.
Title centered at top. Two columns below. Left column: bullet points.
Right column: bar chart. Colors: navy 60%, white 30%, gold 10%.
Font size: title 36pt, body 18pt. Margins: 40px all sides.
```
**Good — 情绪驱动(生成多样且有质感):**
```
A data visualization that feels like a Bloomberg Businessweek
editorial spread. The key number "28.5%" should dominate the
composition like a headline. Warm cream tones with sharp black
typography. The data tells a story of dramatic channel shift.
```
### 执行路径选择
根据速查表的「最佳路径」列选择:
- **AI生成**有明确视觉元素的风格06/07/12/13/14/15/16/20用 Gemini/Midjourney 直出
- **HTML渲染**依赖精确排版的风格01/03/04/10/11/17/18代码控制数据和布局
- **混合**HTML做骨架布局 + AI生成配图/背景02/05/08/09/19
### 质量控制
1. ❌ 不要直接写 "in the style of Pentagram" → ✅ 用具体设计特征描述
2. 文字在AI生成中常出错 → 生成后替换文字
3. 比例易失真 → 明确指定 aspect ratio
4. 先生成3-5个变体选择最佳后细化
**默认审美禁区**(用户可按自己品牌 override
- ❌ 赛博霓虹/深蓝色底(#0D1117
- ❌ 封面图加个人署名/水印
---
**版本**v2.1
**更新日期**2026-02-13
**适用场景**:网页/PPT/PDF/信息图/封面/配图/App等所有视觉设计
**与 image-to-slides 联动**PPT场景可直接引用本文件风格通过 image-to-slides skill 执行生成

View File

@@ -0,0 +1,334 @@
# 可编辑 PPTX 导出HTML 硬约束 + 尺寸决策 + 常见错误
本文档讲的是**用 `scripts/html2pptx.js` + `pptxgenjs` 把 HTML 逐元素翻译成真·可编辑 PowerPoint 文本框**的路径,也是 `export_deck_pptx.mjs` 唯一支持的路径。
> **核心前提**要走这条路HTML 必须从第一行就按下面 4 条约束写。**不是写完再转**——事后补救会触发 2-3 小时返工2026-04-20 期权私董会项目实测踩坑)。
>
> 视觉自由度优先的场景(动画 / web component / CSS 渐变 / 复杂 SVG请改走 PDF 路径(`export_deck_pdf.mjs` / `export_deck_stage_pdf.mjs`**不要**指望 pptx 导出能兼得视觉保真和可编辑——这是 PPTX 文件格式本身的物理约束(见文末「为什么 4 条约束不是 Bug 而是物理约束」)。
---
## 画布尺寸:用 960×540ptLAYOUT_WIDE
PPTX 单位是 **inch**(物理尺寸),不是 px。决策原则body 的 computedStyle 尺寸要**匹配 presentation layout 的 inch 尺寸**±0.1",由 `html2pptx.js``validateDimensions` 强制检查)。
### 3 个候选尺寸对比
| HTML body | 物理尺寸 | 对应 PPT layout | 何时选 |
|---|---|---|---|
| **`960pt × 540pt`** | **13.333″ × 7.5″** | **pptxgenjs `LAYOUT_WIDE`** | ✅ **默认推荐**(现代 PowerPoint 16:9 标配) |
| `720pt × 405pt` | 10″ × 5.625″ | 自定义 | 仅当用户指定「老版 PowerPoint Widescreen」模板时 |
| `1920px × 1080px` | 20″ × 11.25″ | 自定义 | ❌ 非标尺寸,投影后字体显得异常小 |
**别把 HTML 尺寸当分辨率想。** PPTX 是矢量文档body 尺寸决定的是**物理尺寸**不是清晰度。超大 body20″×11.25″)不会让文字更清晰——只会让字号 pt 相对画布变小,投影/打印时反而更难看。
### body 写法三选一(等价)
```css
body { width: 960pt; height: 540pt; } /* 最清晰,推荐 */
body { width: 1280px; height: 720px; } /* 等价px 习惯 */
body { width: 13.333in; height: 7.5in; } /* 等价,英寸直觉 */
```
配套的 pptxgenjs 代码:
```js
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_WIDE'; // 13.333 × 7.5 inch, 无需自定义
```
---
## 4 条硬约束(违反会直接报错)
`html2pptx.js` 把 HTML 的 DOM 逐元素翻译成 PowerPoint 对象。PowerPoint 的格式约束投射到 HTML 上 = 下面 4 条规则。
### 规则 1DIV 里不能直接写文字 — 必须用 `<p>` 或 `<h1>`-`<h6>` 包裹
```html
<!-- ❌ 错误:文字直接在 div 里 -->
<div class="title">Q3营收增长23%</div>
<!-- ✅ 正确:文字在 <p> 或 <h1>-<h6> 里 -->
<div class="title"><h1>Q3营收增长23%</h1></div>
<div class="body"><p>新用户是主要驱动力</p></div>
```
**为什么**PowerPoint 文本必须存在 text frame 里text frame 对应 HTML 的段落级元素p/h*/li。裸 `<div>` 在 PPTX 里没有对应的文本容器。
**也不能用 `<span>` 承载主文字**——span 是行内元素没法独立对齐成文本框。span 只能**夹在 p/h\* 里**做局部样式(加粗、换色)。
### 规则 2不支持 CSS 渐变 — 只能用纯色
```css
/* ❌ 错误 */
background: linear-gradient(to right, #FF6B6B, #4ECDC4);
/* ✅ 正确:纯色 */
background: #FF6B6B;
/* ✅ 如果必须多色条纹,用 flex 子元素各自纯色 */
.stripe-bar { display: flex; }
.stripe-bar div { flex: 1; }
.red { background: #FF6B6B; }
.teal { background: #4ECDC4; }
```
**为什么**PowerPoint 的 shape fill 只支持 solid/gradient-fill 两种,但 pptxgenjs 的 `fill: { color: ... }` 只映射 solid。渐变走 PowerPoint 原生 gradient 需要另写结构,目前工具链不支持。
### 规则 3背景/边框/阴影只能在 DIV 上,不能在文字标签上
```html
<!-- ❌ 错误:<p> 有背景色 -->
<p style="background: #FFD700; border-radius: 4px;">重点内容</p>
<!-- ✅ 正确:外层 div 承载背景/边框,<p> 只负责文字 -->
<div style="background: #FFD700; border-radius: 4px; padding: 8pt 12pt;">
<p>重点内容</p>
</div>
```
**为什么**PowerPoint 里 shape方块/圆角矩形)和 text frame 是两个对象。HTML 的 `<p>` 只翻译成 text frame背景/边框/阴影属于 shape——必须在**包裹 text 的 div** 上写。
### 规则 4DIV 不能用 `background-image` — 用 `<img>` 标签
```html
<!-- ❌ 错误 -->
<div style="background-image: url('chart.png')"></div>
<!-- ✅ 正确 -->
<img src="chart.png" style="position: absolute; left: 50%; top: 20%; width: 300pt; height: 200pt;" />
```
**为什么**`html2pptx.js` 只从 `<img>` 元素提取图片路径,不解析 CSS 的 `background-image` URL。
---
## 合并文本框(`data-pptx-merge`
**默认行为**HTML 里每个 `<p>`/`<h1>`-`<h6>` 在 PPTX 里都是**独立文本框**。卡片里写 3 个 `<p>` → PPT 里 3 个文本框摞着,编辑时不能整段回车换行加段,得逐个改字号/对齐。
**解决方法**:给外层 div 加 `data-pptx-merge="true"`,容器内的所有 `<p>/<h*>` 会合并为**一个可编辑文本框**每段之间用段落分隔符隔开PPT 里就是一段一段连续编辑。
```html
<!-- ✅ 合并写法4 段全部在一个文本框里 -->
<div class="card" data-pptx-merge="true"
style="position: absolute; top: 60pt; left: 60pt; width: 420pt;
background: #1A4A8A; border-radius: 8pt; padding: 20pt 24pt;">
<h2 style="font-size: 24pt; color: #FFFFFF;">标题</h2>
<p style="font-size: 14pt; color: #DDEEFF;">第一段正文。</p>
<p style="font-size: 14pt; color: #FFD166;">第二段:换颜色作为强调。</p>
<p style="font-size: 14pt; color: #DDEEFF;">第三段:同一个文本框里继续写。</p>
</div>
```
**保留的样式**per-paragraph 作为 run options 写入):`font-size``color``font-family``font-weight`bold`font-style`italic`text-decoration: underline``<b>/<i>/<u>/<strong>/<em>/<span>` 内联样式。
**取自第一段、整框统一**`text-align``line-height`。因为 PowerPoint 的对齐和行距是 paragraph/textbox 级别——一框里只能有一种对齐。如果几段对齐不同,请别用 merge让它们各自独立。
**容器自身的 `background`/`border`/`box-shadow`/`border-radius`** 照常作为 shape 渲染,行为和普通 div 完全一样——也就是说蓝色卡片底 + 文本仍然是「shape + text frame」两层只是文本层从 3-4 个文本框塌缩成 1 个。
**限制**
- 不能嵌套 `data-pptx-merge`(会报错)。
- 容器不能用 `background-image`(同 4 条硬约束规则 4
- 容器内不要再放有 `background`/`border` 的子 div——它们仍会被当作独立 shape 渲染,但里面的文字已被合并走了,可能产生视觉错位。
**什么时候用**:内容会反复改、要在 PPT 里继续编辑的场景。一次性导出归档的不用加,行为一致。
---
## Path A HTML 模板骨架
每张 slide 一个独立 HTML 文件,彼此作用域隔离(避开单文件 deck 的 CSS 污染)。
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
width: 960pt; height: 540pt; /* ⚠️ 匹配 LAYOUT_WIDE */
font-family: system-ui, -apple-system, "PingFang SC", sans-serif;
background: #FEFEF9; /* 纯色,不能渐变 */
overflow: hidden;
}
/* DIV 负责布局/背景/边框 */
.card {
position: absolute;
background: #1A4A8A; /* 背景在 DIV 上 */
border-radius: 4pt;
padding: 12pt 16pt;
}
/* 文字标签只负责字体样式,不加背景/边框 */
.card h2 { font-size: 24pt; color: #FFFFFF; font-weight: 700; }
.card p { font-size: 14pt; color: rgba(255,255,255,0.85); }
</style>
</head>
<body>
<!-- 标题区:外层 div 定位,内层文字标签 -->
<div style="position: absolute; top: 40pt; left: 60pt; right: 60pt;">
<h1 style="font-size: 36pt; color: #1A1A1A; font-weight: 700;">标题用断言句,不是主题词</h1>
<p style="font-size: 16pt; color: #555555; margin-top: 10pt;">副标题补充说明</p>
</div>
<!-- 内容卡片div 负责背景h2/p 负责文字 -->
<div class="card" style="top: 130pt; left: 60pt; width: 240pt; height: 160pt;">
<h2>要点一</h2>
<p>简短说明文字</p>
</div>
<!-- 列表:使用 ul/li不用手动 • 符号 -->
<div style="position: absolute; top: 320pt; left: 60pt; width: 540pt;">
<ul style="font-size: 16pt; color: #1A1A1A; padding-left: 24pt; list-style: disc;">
<li>第一条要点</li>
<li>第二条要点</li>
<li>第三条要点</li>
</ul>
</div>
<!-- 插图:用 <img> 标签,不用 background-image -->
<img src="illustration.png" style="position: absolute; right: 60pt; top: 110pt; width: 320pt; height: 240pt;" />
</body>
</html>
```
---
## 常见错误速查
| 错误信息 | 原因 | 修复方法 |
|---------|------|---------|
| `DIV element contains unwrapped text "XXX"` | div 里有裸文字 | 把文字包进 `<p>``<h1>`-`<h6>` |
| `CSS gradients are not supported` | 用了 linear/radial-gradient | 改为纯色,或用 flex 子元素分段 |
| `Text element <p> has background` | `<p>` 标签加了背景色 | 外套 `<div>` 承载背景,`<p>` 只写文字 |
| `Background images on DIV elements are not supported` | div 用了 background-image | 改为 `<img>` 标签 |
| `HTML content overflows body by Xpt vertically` | 内容超出 540pt | 减少内容或缩小字号,或 `overflow: hidden` 截断 |
| `HTML dimensions don't match presentation layout` | body 尺寸和 pres layout 对不上 | body 用 `960pt × 540pt``LAYOUT_WIDE`;或 defineLayout 自定义尺寸 |
| `Text box "XXX" ends too close to bottom edge` | 大字号 `<p>` 距离 body 底边 < 0.5 inch | 往上挪留足下边距PPT 底部本身就会被遮住一部分 |
---
## 基本工作流3 步出 PPTX
### Step 1按约束写每页独立 HTML
```
我的Deck/
├── slides/
│ ├── 01-cover.html # 每个文件都是完整 960×540pt HTML
│ ├── 02-agenda.html
│ └── ...
└── illustration/ # 所有 <img> 引用的图片
├── chart1.png
└── ...
```
### Step 2写 build.js 调用 `html2pptx.js`
```js
const pptxgen = require('pptxgenjs');
const html2pptx = require('../scripts/html2pptx.js'); // 本 skill 脚本
(async () => {
const pres = new pptxgen();
pres.layout = 'LAYOUT_WIDE'; // 13.333 × 7.5 inch匹配 HTML 的 960×540pt
const slides = ['01-cover.html', '02-agenda.html', '03-content.html'];
for (const file of slides) {
await html2pptx(`./slides/${file}`, pres);
}
await pres.writeFile({ fileName: 'deck.pptx' });
})();
```
### Step 3打开检查
- PowerPoint/Keynote 打开导出 PPTX
- 双击任意文字应能直接编辑如果是图片说明第 1 条违反了
- 验证 overflow每页应该在 body 范围内没有被截
---
## 这条路径 vs 其他选项(什么时候选什么)
| 需求 | 选什么 |
|------|------|
| 同事会改 PPTX 里的文字 / 发给非技术人员继续编辑 | **本文路径**editable需从头按 4 条约束写 HTML |
| 只是演讲用 / 发存档不再改 | `export_deck_pdf.mjs`多文件 `export_deck_stage_pdf.mjs`单文件 deck-stage出矢量 PDF |
| 视觉自由度优先动画web componentCSS 渐变复杂 SVG接受不可编辑 | **PDF**同上)——PDF 既保真又跨平台图片 PPTX更合适 |
**绝不要在视觉自由写好的 HTML 上硬跑 html2pptx**——实测视觉驱动的 HTML pass < 30%剩下的逐页改造比重写还慢这种场景应该出 PDF不是硬挤 PPTX
---
## Fallback已有视觉稿但用户坚持要 editable PPTX
偶尔会遇到这个场景/用户已经写好一份视觉驱动的 HTML渐变web component复杂 SVG 都用上了本来出 PDF 最合适但用户明确说不行必须是可编辑的 PPTX」。
**不要硬跑 `html2pptx` 期待它 pass**——实测视觉驱动 HTML html2pptx pass <30%剩下 70% 会报错或走样正确的 fallback
### Step 1 · 先告知局限性(透明沟通)
一句话跟用户说清三件事
> 「你现在的 HTML 用了 [具体列出:渐变 / web component / 复杂 SVG / ...],直接转 editable PPTX 会 fail。我有两个方案
> - A. **出 PDF**(推荐)——视觉 100% 保留,接收方能看能印但不能改文字
> - B. **以视觉稿为蓝本,重写一版 editable HTML**(保留色彩/布局/文案的设计决策,但按 4 条硬约束重新组织 HTML 结构,**牺牲**渐变、web component、复杂 SVG 等视觉能力)→ 再导出 editable PPTX
>
> 你选哪个?」
不要把 B 方案说得云淡风轻——明确告知**会丢失什么**。让用户做取舍
### Step 2 · 如果用户选 BAI 主动改写,不要求用户自己写
这里的 doctrine **用户给的是设计意图你负责翻译成合规实现**。不是让用户去学 4 条硬约束然后自己重写
改写时的遵循原则
- **保留**色彩系统主色/辅色/中性色)、信息层级标题/副标题/正文/注解)、核心文案layout 骨架上中下 / 左右分栏 / 网格)、页面节奏
- **降级**CSS 渐变 纯色或 flex 分段web component 段落级 HTML复杂 SVG 简化的 `<img>` 或纯色几何阴影 删除或降为极弱自定义字体 向系统字体靠齐
- **重写**裸文字 包进 `<p>` / `<h*>``background-image` `<img>` 标签`<p>` 上的背景边框 外层 div 承载
### Step 3 · 产出对照清单(透明交付)
改写完成后给用户一份 before/after 对照让他知道哪些视觉细节被简化了
```
原设计 → editable 版调整
- 标题区紫色渐变 → 主色 #5B3DE8 纯色背景
- 数据卡片阴影 → 删除(改为 2pt 描边区分)
- 复杂 SVG 折线图 → 简化为 <img> PNG从 HTML 截图生成)
- Hero 区 web component 动效 → 静态首帧web component 无法翻译)
```
### Step 4 · 导出 & 双格式交付
- `editable` HTML `scripts/export_deck_pptx.mjs` 出可编辑 PPTX
- **建议同时保留**原视觉稿 `scripts/export_deck_pdf.mjs` 出高保真 PDF
- 双格式交付给用户视觉稿的 PDF + 可编辑的 PPTX各司其职
### 什么情况下直接拒绝 B 方案
个别场景下改写代价过高应该劝用户放弃 editable PPTX
- HTML 核心价值是动画或交互改写后只剩静态首帧信息量损失 50%+
- 页数 > 30改写成本超过 2 小时
- 视觉设计深度依赖精确 SVG / 自定义 filter改写后和原图几乎无关
此时告诉用户:「这个 deck 改写代价过高,建议出 PDF 而不是 PPTX。如果接收方确实要 pptx 格式,就接受视觉会大幅朴素化——要不要换成 PDF
---
## 为什么 4 条约束不是 Bug 而是物理约束
这 4 条不是 `html2pptx.js` 作者偷懒——它们是 **PowerPoint 文件格式OOXML本身的约束**投射到 HTML 上的结果:
- PPTX 里文字必须在 text frame`<a:txBody>`),对应段落级 HTML 元素
- PPTX 的 shape 和 text frame 是两个对象,无法在同一 element 上同时画背景和写文字
- PPTX 的 shape fill 对 gradient 支持有限(仅某些 preset gradients不支持 CSS 任意角度渐变)
- PPTX 的 picture 对象必须引用真实图片文件,不是 CSS 属性
理解这点后,**不要期待工具变聪明** —— 是 HTML 写法要适配 PPTX 格式,不是反过来。

View File

@@ -0,0 +1,250 @@
# Gallery Ripple + Multi-Focus · 场景编排哲学
> 从 huashu-design hero 动画 v925 秒8 场景)里提炼出的**一种可复用的视觉编排结构**。
> 不是动画制作流水线,是**什么场景下这种编排是"对的"**。
> 实战参考:[demos/hero-animation-v9.mp4](../demos/hero-animation-v9.mp4) · [https://www.huasheng.ai/huashu-design-hero/](https://www.huasheng.ai/huashu-design-hero/)
## 一句话先行
> **当你有 20+ 同质视觉素材、场景需要"表达规模感和深度"时,优先考虑 Gallery Ripple + Multi-Focus 这套编排,而不是堆砌排版。**
通用 SaaS feature 动画、产品发布会、skill 推广、系列作品集展示——只要素材数量够、风格一致,这套结构几乎都能出效果。
---
## 这个手法究竟在表达什么
不是"秀素材"——是通过**两个节奏变化**讲一个叙事:
**第一拍 · Ripple 展开(~1.5s**:从中心向四周扩散出 48 张卡片,观众被"量"震住——「哦,这东西有这么多产出」。
**第二拍 · Multi-Focus~8s4 次循环)**:镜头在慢速 pan 的同时4 次把背景 dim + desaturate把某一张卡单独放大到屏幕中央——观众从"量的冲击"切换到"质的凝视",每次 1.7s 节奏稳定。
**核心叙事结构****规模Ripple → 凝视Focus × 4 → 淡出Walloff**。这三拍组合起来表达的是「Breadth × Depth」——不只是能做很多每一个还都值得停下来看。
对比一下反例:
| 做法 | 观众感知 |
|------|---------|
| 48 张卡静态排列(没有 Ripple| 好看但无叙事,像一张 grid screenshot |
| 一张一张快切(没有 Gallery context| 像 slideshow失去"规模感" |
| 只有 Ripple 没有 Focus | 震住了但没让人记住任何具体一张 |
| **Ripple + Focus × 4本配方** | **先震撼于量,再凝视于质,最后平静淡出——完整情绪弧线** |
---
## 前置条件(必须全部满足)
这套编排**不是万能的**,下面 4 条缺一不可:
1. **素材规模 ≥ 20 张,最好 30+**
少于 20 张 Ripple 会显得"空"——48 格里每格都在动才有密度感。v9 用了 48 格 × 32 张图(循环填充)。
2. **素材视觉风格一致**
全是 16:9 slide 预览 / 全是 app 截图 / 全是封面设计——长宽比、色调、版式得像是"一套"。混搭会让 Gallery 看起来像剪贴板。
3. **素材单独放大后仍有可读信息**
Focus 是把某张卡放大到 960px 宽如果原图放大后糊了或信息稀薄Focus 这一拍就废了。反向验证:能不能从 48 张里挑出 4 张作为"最有代表性"的?挑不出来就说明素材质量不齐。
4. **场景本身是 landscape 或 square不是竖屏**
Gallery 的 3D 倾斜(`rotateX(14deg) rotateY(-10deg)`)需要横向延伸感,竖屏会让倾斜效果看起来窄且别扭。
**缺条件的后备路径**
| 缺什么 | 退化为什么 |
|-------|-----------|
| 素材 < 20 | 改用3-5 张并排静态展示 + 逐个 focus |
| 风格不一致 | 改用封面 + 3 章节大图 keynote-style |
| 信息稀薄 | 改用data-driven dashboard金句 + 大字 |
| 竖屏场景 | 改用vertical scroll + sticky cards |
---
## 技术配方v9 实战参数)
### 4-Layer 结构
```
viewport (1920×1080, perspective: 2400px)
└─ canvas (4320×2520, 超大 overflow) → 3D tilt + pan
└─ 8×6 grid = 48 cards (gap 40px, padding 60px)
└─ img (16:9, border-radius 9px)
└─ focus-overlay (absolute center, z-index 40)
└─ img (matches selected slide)
```
**关键**canvas viewport 2.25 这样 pan 才有"窥视更大世界"的感觉
### Ripple 展开(距离延迟算法)
```js
// 每张卡的入场时间 = 距中心的距离 × 0.8s 延迟
const col = i % 8, row = Math.floor(i / 8);
const dc = col - 3.5, dr = row - 2.5; // 到中心的 offset
const dist = Math.hypot(dc, dr);
const maxDist = Math.hypot(3.5, 2.5);
const delay = (dist / maxDist) * 0.8; // 0 → 0.8s
const localT = Math.max(0, (t - rippleStart - delay) / 0.7);
const opacity = expoOut(Math.min(1, localT));
```
**核心参数**
- 总时长 1.7s`T.s3_ripple: [8.3, 10.0]`
- 最大延迟 0.8s中心最早出角落最晚
- 每张卡入场时长 0.7s
- Easing: `expoOut`爆发感不是平滑
**同时做的事**canvas scale 1.25 0.94zoom out to reveal)—— 配合出现的同步推远感
### Multi-Focus4 次节奏)
```js
T.focuses = [
{ start: 11.0, end: 12.7, idx: 2 }, // 1.7s
{ start: 13.3, end: 15.0, idx: 3 }, // 1.7s
{ start: 15.6, end: 17.3, idx: 10 }, // 1.7s
{ start: 17.9, end: 19.6, idx: 16 }, // 1.7s
];
```
**节奏规律**每个 focus 1.7s间隔 0.6s 喘息总计 8s11.019.6s)。
**每次 focus 内部**
- In ramp: 0.4s`expoOut`
- Hold: 中间 0.9s`focusIntensity = 1`
- Out ramp: 0.4s`easeOut`
**背景变化(这是关键)**
```js
if (focusIntensity > 0) {
const dimOp = entryOp * (1 - 0.6 * focusIntensity); // dim to 40%
const brt = 1 - 0.32 * focusIntensity; // brightness 68%
const sat = 1 - 0.35 * focusIntensity; // saturate 65%
card.style.filter = `brightness(${brt}) saturate(${sat})`;
}
```
**不只是 opacity——同时 desaturate + darken**这让前景 overlay 的色彩"跳出来"而不是只是"变亮一点"。
**Focus overlay 尺寸动画**
- 400×225入场)→ 960×540hold
- 外围有 3 shadow + 3px accent outline ring呈现"被框住的感觉"
### Pan持续感让静止不无聊
```js
const panT = Math.max(0, t - 8.6);
const panX = Math.sin(panT * 0.12) * 220 - panT * 8;
const panY = Math.cos(panT * 0.09) * 120 - panT * 5;
```
- 正弦波 + 线性 drift 双层运动——不是纯循环每个时刻位置都不同
- X/Y 频率不同0.12 vs 0.09避免视觉上看出"规律循环"
- clamp ±900/500px 防止漂出
**为什么不用纯线性 pan**纯线性观众会"预测"下一秒在哪正弦+drift 让每一秒都是新的3D 倾斜下产生"微晕船感"好的那种注意力被拉住
---
## 5 个可复用模式(从 v6→v9 迭代中蒸馏)
### 1. **expoOut 作为主 easing不是 cubicOut**
`easeOut = 1 - (1-t)³`平滑vs `expoOut = 1 - 2^(-10t)`爆发后迅速收敛)。
**选择理由**expoOut 的前 30% 很快达到 90%更像物理阻尼符合"重的东西落地"的直觉特别适合
- 卡片入场重量感
- Ripple 扩散冲击波
- Brand 浮起落定感
**什么时候仍用 cubicOut**focus out ramp对称的微动效
### 2. **纸感底色 + 赤陶橙 accentAnthropic 血统)**
```css
--bg: #F7F4EE; /* 暖纸 */
--ink: #1D1D1F; /* 几乎黑 */
--accent: #D97757; /* 赤陶橙 */
--hairline: #E4DED2; /* 暖线条 */
```
**为什么**温暖底色在 GIF 压缩后依然有"呼吸感"不像纯白会显得"屏幕感"。赤陶橙作为唯一 accent 贯穿 terminal promptdir-card 选中cursorbrand hyphenfocus ring——所有视觉锚点都被这一个色串起来
**v5 教训**加了 noise overlay 以模拟"纸纹"结果 GIF 帧压缩全废每帧都不同)。v6 改为"只用底色 + shadow"纸感保留 90%GIF 体积缩小 60%。
### 3. **两档 Shadow 模拟深度,不用真 3D**
```css
.gallery-card.depth-near { box-shadow: 0 32px 80px -22px rgba(60,40,20,0.22), ... }
.gallery-card.depth-far { box-shadow: 0 14px 40px -16px rgba(60,40,20,0.10), ... }
```
`sin(i × 1.7) + cos(i × 0.73)` 确定性算法给每张卡分配 near/mid/far 三档 shadow——**视觉上有"三维堆叠"但每帧 transform 完全不变GPU 消耗 0**。
**真 3D 的代价**每个 card 单独 `translateZ`GPU 每帧都在算 48 transform + shadow blurv4 试过Playwright 录制 25fps 都吃力v6 的两档 shadow 肉眼效果差距 <5%但成本差 10
### 4. **字重变化font-variation-settings比字号变化更电影感**
```js
const wght = 100 + (700 - 100) * morphP; // 100 → 700 over 0.9s
wordmark.style.fontVariationSettings = `"wght" ${wght.toFixed(0)}`;
```
Brand wordmark Thin Bold 0.9s 渐变配合 letter-spacing 微调-0.045 -0.048em)。
**为什么比放大缩小好**
- 放大缩小观众看过太多预期固化
- 字重变化是"内在的充实感"像气球被吹满而不是"被推近"
- variable fonts 2020+ 才普及的特性观众下意识感觉"现代"
**限制**必须用支持 variable font 的字体Inter/Roboto Flex/Recursive )。普通静态字体只能拟态切换几个固定 weight 有跳变)。
### 5. **Corner Brand 低强度持续签名**
Gallery 阶段左上角有个 `HUASHU · DESIGN` 小标识16% opacity 色值12px 字号宽字距
**为什么加这个**
- Ripple 爆发后观众容易"失焦"不记得在看什么左上角轻标示帮助 anchor
- 比全屏大 logo 更高级——做品牌的人知道品牌签名不需要喊
- GIF 被截屏分享时仍留下归属信号
**规则**只在中段画面 busy出现开场关闭不遮 terminal结尾关闭brand reveal 是主角)。
---
## 反例:什么时候不要用这套编排
** 产品演示要展示功能的**Gallery 让每一张都一闪而过观众记不住任何一个功能改用单屏 focus + tooltip 标注」。
** 数据驱动内容**观众要读数字Gallery 的快速节奏不给时间读改用数据图表 + 逐项 reveal」。
** 故事叙事**Gallery "并列"结构故事需要"因果"。改用 keynote 章节切换
** 素材只有 3-5 **Ripple 密度不够看起来像"补丁"。改用静态排列 + 逐张高亮」。
** 竖屏9:16**3D tilt 需要横向延伸竖屏会让倾斜感觉""而不是"展开"。
---
## 如何判断自己的任务适用这套编排
三步快速检查
**Step 1 · 素材数量**数一下你有多少同类视觉素材。< 15 15-25 25+ 直接用
**Step 2 · 一致性测试** 4 张随机素材并排放是否像一套」?不像 先统一风格再做或改方案
**Step 3 · 叙事匹配**你要表达的是Breadth × Depth」( × 还是流程」「功能」「故事」?不是前者就别硬套
三步都 yes直接 fork v6 HTML `SLIDE_FILES` 数组和时间轴就能复用调色板改 `--bg / --accent / --ink`整体换皮不换骨
---
## 相关 Reference
- 完整技术流程[references/animations.md](animations.md) · [references/animation-best-practices.md](animation-best-practices.md)
- 动画导出流水线[references/video-export.md](video-export.md)
- 音频配置BGM + SFX 双轨[references/audio-design-rules.md](audio-design-rules.md)
- Apple 画廊风格的横向参考[references/apple-gallery-showcase.md](apple-gallery-showcase.md)
- HTMLv6 + 音频集成版`www.huasheng.ai/huashu-design-hero/index.html`

View File

@@ -0,0 +1,293 @@
# Launch Film 工作流:先写万字 director's notes再做动画
> 高规格视觉作品(≥ 20 秒、含品牌叙事、含 slogan reveal、可能上 X / 公众号 / B 站推广)的标准工作流。
>
> 触发条件:任务是「产品升级宣传片 / 品牌 launch film / launch trailer / superbowl-tier ad / brand campaign / hero animation video」且**用户对质量有明确预期**如「超级碗品质感」「10x 细节」「Apple 级别」)。
>
> 反触发:不要在「快速做个动画 demo」「简单 motion graphic」「单个图标动画」时用这条流程——会过度工程化。
---
## 1. 为什么先写 director's notes
实战教训2026-05-11 huashu-md-html v2.0 项目):
第一轮直接动手写 HTML产出的是「程序员视角的动画」——每个 capability 平均用力、节奏匀速、slogan 撞在一起、缺少叙事弧。
第二轮接到用户「停下,先按苹果导演视角写 1 万字分镜脚本」的指令,写了 v5-director-notes.md11500 字、13 镜 shot-by-shot spec然后按脚本实施——一次过、每帧 pause 都耐看、节奏起伏有 climax。
**核心差异**:写脚本是 think写 HTML 是 execute。先 think 透了execute 就是机械翻译。先 execute每个 shot 都是临场决策,必然乱。
写 director's notes 不是「装」,是把所有视觉决策**在动手之前**沉淀成文档——每一镜都已经在脑里 visualize 过、reasoning 过、和上下文 trace 过。HTML 实施时不需要再做创意决策,只需要忠实翻译。
---
## 2. 触发判断(先问自己 3 个问题)
启动 launch film 工作流前问:
1. **这支片承担品牌叙事吗?**(有 thesis / slogan reveal / 升级仪式感)—— 是 → 走 director's notes 流程
2. **观众会暂停看吗?**(可能截图、做 X 海报、做封面、慢速 review—— 是 → 每帧要耐看
3. **客户/用户有「我希望像 XXX 那样」的参照?**Apple / Anthropic / Nike / Penguin / 某导演)—— 是 → 必须明确视觉语境
任一为「是」就走流程。三个都「否」就跳过,直接用 [animations.md](animations.md) 的标准流程。
---
## 3. Director's Notes 的 5 大部分结构
万字10000-12000 字中文 / 等量英文director's notes 必须包含这 5 大部分。**任一部分缺失都属于不完整,质量会受影响**。
### Part I · Director's Statement创作论约 1500-2000 字)
回答 5 个问题:
1. **这部片不是什么?**(明确排除——如「这不是功能介绍片」「不是 demo」
2. **核心 thesis 一行**——观众看完只记一句话是哪句?
3. **跟谁的语境对话?**——列出 5-8 个视觉参照(导演 / 设计师 / 品牌 / 摄影师 / 作品名 + 年份),说明每个参照学了什么
4. **三类观众画像 + 对每类的承诺**:主受众 / 次受众 / 外受众,各对应一段
5. **节奏哲学**——慢拍 / 加速 / 顶峰 / 缓收的曲线说明 + emotional climax 在第几秒(**不一定是最后一秒**
最后加一段 anti-slop checklist**这部片不做的事**(具体列出,不模糊)。
### Part II · Visual System视觉系统全谱约 1500-2500 字)
这是工程化的视觉 spec。完整后任何执行者拿到都能产出一致的视觉。
必含子节:
- **完整色板**:至少 8-10 色,每色含 HEX + 功能定义 + 占画面比例上限
- **字体系统**:至少 6 个字号层级,每层级含字体名 + weight + size + letter-spacing + 用途
- **网格系统**:画布尺寸 + 外边距 + column grid + baseline grid + 关键安全区 + 黄金分割锚点
- **动画系统**easing 库4 条以内)+ duration 字典 + stagger 法则 + scene 过渡规则
- **Chrome 元素**贯穿全片的小细节counter / chip / ticker / watermark / texture每个含位置 + 入退场时机
- **音频系统**BGM 30 秒走向曲线(分层)+ SFX 字典10+ cues 含时间码 + 音量 + 频段隔离)
- **反 AI slop checklist**per-shot 自检表10-15 项)
铁律:**所有视觉决策都从 Visual System 推导,不要在 shot list 里临时发明新值**。
### Part III · Story Arc故事弧约 500-800 字)
三幕结构 + 情绪曲线:
- **Act I · SETUP**0 → 第 1/5 时长e.g. 0-6s for 30s观众进入问题被提出
- **Act II · ESCALATION**(中间 2/3答案展开主题铺陈
- **Act III · PAYOFF**(最后 1/4升华、slogan reveal、品牌印章
含 ASCII 情绪曲线图 + emotional climax 时刻标记。
**关键决策**climax 不一定在末尾。30s 片子 climax 通常在 22-25s不是 29s——最后几秒是 resolution / decay不是 peak。这条规则违反必然让作品「虎头蛇尾」。
### Part IV · Shot-by-Shot Storyboard分镜脚本约 5000-7000 字 · 占 60% 篇幅)
每镜含 10 个字段(缺一不可):
```
SHOT NN · NAME
[TIMECODE] 起止时间 + 时长
[FUNCTION] 这一镜在故事弧中的功能(一句话)
[VISUAL] 画面构图 + 元素位置 + 运动方向
[TYPE] 排版 spec字体 / 字号 / 字距 / 行高 / 颜色 / 对齐)
[ANIM] 每元素 in/out 时机 + easing + duration + stagger + delay
[AUDIO] music beat + SFX cue每镜对应 BGM 节奏 + 必含 SFX 时间表)
[CHROME] 四角元素状态(哪些 chrome 在 / 哪些 fade in/out / 哪个 pulse
[ANTI-SLOP] 这一镜通过了哪些自检项 + 有什么 120% 细节签名
[WHY] 承接上一镜的逻辑 + 推进下一镜的钩子
```
**字段平均 30-80 字 → 每镜 400-700 字 → 12-15 镜 → 5000-7000 字**
实战经验:写完 storyboard 后**自己读一遍**——任意一镜删掉,整支片是否还成立?如果可以删,那镜就是多余的,删掉。
### Part V · Production Manifest制作清单约 800-1200 字)
工程交付清单:
- 字体加载 URL含 preconnect
- CSS 变量(直接可粘贴)
- BGM 来源选择标准 + Suno/Udio prompt 关键词 + 备选库
- SFX 字典(按时间码逐 cue 列出文件路径 + 音量)
- **关键帧验证计划**12-15 张 pause-and-check 关键帧时间码每帧验证项列出fonts / positions / chrome state
- 录制参数fps / codec / bitrate / preset
- ffmpeg 音频混合命令(含 audio stream 验证)
- 交付物清单mp4 / mp4-60fps / gif / poster.png / silent.mp4 / shot-list.csv
- 全链路时间估算(小时级精度)
---
## 4. 写 director's notes 的 5 条建议
**4.1 用导演的口吻,不用 PM 的口吻**
❌「This shot displays the product features.」
✅「This is the hero shot — if the audience pauses anywhere, I want it to be here.」
导演笔记是给执行者读的,但也是给未来的自己读的。第一人称 + judgment 表达比 description 表达留更多决策线索。
**4.2 引用具体作品(含年份),不只是流派名**
❌「Apple-inspired」
✅「Apple 'Designed by Apple in California' (2013, dir. Mark Romanek) — 学的是慢拍 + 衬线 + 大白底」
引用具体作品的好处:(a) 任何观众都能上网搜到对照 (b) 你逼自己想清楚学的是什么具体技术 (c) 防止「灵感模糊」。
**4.3 每个决策都 trace 回 first principle**
整支片有一句 first principle如 "Markdown is the new typewriter.")。每个具体决策——配色 / 字体 / 节奏 / chrome——都要能 trace 回这句话。
trace 不上的决策就是装饰,删掉。
**4.4 写 anti-slop 比写 do-this 更重要**
「这部片不做的事」清单(紫渐变 / emoji / Lorem ipsum / Inter display / SVG 画人物 / 圆角卡 + 左 border accent比「这部片做的事」清单更能保护质量。
正向决策无穷多,负向 checklist 是有限的——但负向 checklist 一旦违反就是 slop。
**4.5 写完不要立即实施——隔 30 分钟再读一遍**
写作时大脑在「生产模式」,看不见 inconsistency。隔 30 分钟读自己写的 storyboard会发现
- 某两镜功能重复(删一个)
- 某镜叙事跳跃太大(加过渡)
- emotional climax 位置错(移动)
- chrome 元素和 shot 数量不匹配(重新对齐)
这 30 分钟省下的是后期 2 小时的返工。
---
## 5. Director's Notes → HTML 实施流程
写完 director's notes 后HTML 实施步骤:
1. **复用 starter components**`assets/animations.jsx` 的 Stage/Sprite/Easing/interpolate— 不重新发明
2. **CSS 变量直接从 Visual System Part II 粘贴** — 不在 HTML 里临时改色
3. **按 Sprite start/end 时间轴对照 Part IV 时间码** — 不擅自加镜
4. **chrome 元素抽成独立组件**ChromeA/B/C/D用 useTime() 驱动状态切换
5. **destination cards 内容必须真实可读**(不是 fake bar lines—— 这是 v5 项目里最被反复提及的 120% 细节签名
6. **每写完一镜就立即截关键帧验证**(用 `?t=NN` URL 参数 + Playwright不要写完全片再统一验证
---
## 6. 关键帧验证流程
URL 参数实现(必须在 Stage 组件加):
```js
const urlMatch = window.location.search.match(/[?&]t=([\d.]+)/);
const frozenTime = urlMatch ? parseFloat(urlMatch[1]) : null;
const [time, setTime] = useState(frozenTime != null ? frozenTime : 0);
const [playing, setPlaying] = useState(frozenTime == null);
```
→ 这样 `file:///path/animation.html?t=14.5` 直接 freeze 在 14.5 秒。
批量截图:
```bash
for t in 0.5 2.5 4.9 7.0 10.5 13.5 16.5 19.0 21.5 23.4 25.5 28.0 29.9; do
npx -y playwright screenshot \
"file://$PWD/animation.html?t=$t" \
"keyframes/t-$t.png" \
--viewport-size=1920,1136 \
--wait-for-timeout=2500
done
```
每张截图必须验证:
- [ ] 元素无溢出 1920×1080 canvas
- [ ] 字距、行高 visually correct不挤、不散
- [ ] 关键 typography 细节(句点颜色 / em-dash / italic / small caps可识别
- [ ] chrome 元素位置 + 状态正确
- [ ] 反 AI slop checklist 通过
- [ ] 「pause 时值得看」的 120% 细节存在
---
## 7. 多视角并行策略advanced
复杂项目(如 launch film 选不出方向 / 想看多个美学差异 / 客户没拍板风格)可以**启动多个 subagent 并行做不同导演视角的版本**。
实战配置2026-05-11 huashu-md-html 项目,并行 6 个版本):
```
v5 · 基线Anthropic / Penguin Classics 出版社品位)
v5a · Wes Anderson对称 + 复古 + 章节卡片)
v5b · Saul Bass剪纸 + 60s 大字 + 几何切割)
v5c · 王家卫(中文衬线 + 慢动作 + 怀旧)
v5d · Massimo Vignelli现代主义 grid + 红黑)
v5e · 原研哉 Kenya Hara极简日式 + 留白)
v5f · 草间彌生 Yayoi Kusama圆点 + 重复 + 单一强色)
```
每个 subagent 接到独立 brief
- 项目背景(同一份)
- 必读参考(同一份 v5-director-notes.md 作为方法论模板)
- **指定的艺术家 DNA**(色板 / 字体 / 视觉语言 / 节奏 / 招牌元素 / 反 slop 强化版本,每条 30-50 字)
- 统一任务清单director-notes.md + animation.html + keyframes/ + README.md
- 统一约束30s / 1920×1080 / file:// / Google Fonts
并行启动 + 后台运行,约 30-60 分钟出 6 套完整版本。
完成后审校对比:
1. 各版本核心美学决策表
2. 关键帧并排对比(每版同时刻一帧)
3. 投票:哪个最贴合用户的真实需求
**关键**:不要让 subagent 之间相互参考——它们必须独立产出,否则就会撞到「平均值」。每个 subagent 的指令里要明说「不要重复 v5 的美学」。
---
## 8. 触发的几种典型场景
| 用户场景 | 是否触发 | 备注 |
|---------|---------|------|
| 「做个 SaaS 升级宣传片」 | ✅ 触发 | 默认走完整流程 |
| 「Apple 级别 / 超级碗品质感的视频」 | ✅ 触发 + 升级 | 强力推荐多视角并行 |
| 「30 秒品牌 launch film」 | ✅ 触发 | |
| 「这个项目 1 万字脚本再做动画」 | ✅ 触发 | 用户明确指明 |
| 「简单 motion graphiclogo 转一下」 | ❌ 不触发 | 用 animations.md 标准流程 |
| 「做个 onboarding 动画 demo」 | ❌ 不触发 | 用 animations.md |
| 「教程视频带配音」 | ❌ 不触发 | 走 voiceover-pipeline.md |
| 「单个 hero animation」 | ⚠️ 看复杂度 | 如果是高规格 hero触发普通 hero 用 hero-animation-case-study.md |
---
## 9. 参考样本
完整 director's notes 参考样本self-contained本 skill 内):
`assets/director-notes-samples/launch-film-30s-sample.md`(约 78KB · 11500 字 · 13 镜 · 5 大部分齐全)
原始项目位置(含对应实施 HTML + 关键帧):
- `~/.claude/skills/huashu-md-html/demos/v5-director-notes.md`director's notes
- `~/.claude/skills/huashu-md-html/demos/v5-six-forms.html`HTML 实施)
- `~/.claude/skills/huashu-md-html/demos/v5-keyframes/`(关键帧验证截图)
写新项目时强烈建议**先 Read 这份样本**,理解工作量和细节密度,再决定要不要全套走流程。
---
## 10. 反模式(不要这样做)
**写 1000 字的精简版 director's notes 就动手**
→ 精简版必然漏 Visual System 的某个子项,导致 HTML 实施时不停回头补 spec。要做就做万字级要省就直接跳过。
**storyboard 只写 5-8 镜**
→ 30 秒片至少 12-15 镜(每镜 2-3 秒)。镜少 = 节奏匀速 = 没 climax。
**director's notes 写完就交付,不做实施**
→ 文档不是交付物,动画才是。文档 + 动画一起交付,文档作为「设计依据」附录。
**多视角并行时让 subagent 看其他版本**
→ 各 subagent 必须独立,否则趋同。审校阶段才对比。
**跳过关键帧验证直接录 MP4**
→ 必然返工。关键帧验证是最便宜的 quality gate。
**把动画细节决策推迟到「等我录的时候再想」**
→ 录制阶段是机械执行,不能做创意决策。所有决策必须在 director's notes 写死。
---
*最后修订2026-05-11*
*真实案例huashu-md-html v2.0 launch filmv5-director-notes.md*

View File

@@ -0,0 +1,267 @@
# 多视角并行实验 · Case Study
> huashu-md-html v2.0 launch film 项目 · 2026-05-11
> 6 位艺术家视角的并行 director's notes + HTML + 关键帧实验
---
## 背景
用户要求「为 huashu-md-html v2.0 制作 30 秒升级宣传片」时,主线程先产出了 v5 基线Anthropic / Penguin Classics 出版社品位)。但用户认为可以做得更好,给了 critical instruction
> 「调用不同的 subagent 分别再去生成 6 个全然不同的表达方式和视觉设计的版本。你可以试试启用不同的导演和艺术家。然后全都完成后,再评判审校。」
这是首次系统化的「多视角并行 director's notes」实验验证了一套可复用的工作流。
---
## 6 个视角的选择逻辑
不要随便选 6 个 designer——他们必须**视觉差异度极高**,避免趋同。
最终选择的 6 个视角(含选择理由):
| 视角 | 流派 | 美学锚点 | 跟其他视角的差异 |
|------|------|---------|----------------|
| **v5 基线** | 现代出版社 | Anthropic 赤陶橙 + Penguin Classics 衬线 + Vignelli grid | 安全的「品位」选择 |
| **v5a Wes Anderson** | 电影章节美学 | The French Dispatch 杂志感 + 1960 Olivetti 工业目录 | 对称构图 + 章节卡片 + 装饰边框 |
| **v5b Saul Bass** | 60s 影片标题艺术 | cut-paper + Trajan caps + 流动几何 | 剪纸 silhouette + 大字 + 强对角线 |
| **v5c 王家卫** | 港式新浪潮 | 《花样年华》《2046》 letterboxing + 中文衬线 | 慢拍 + 雾化光晕 + 中文为主 |
| **v5d Massimo Vignelli** | 1970 现代主义 | Knoll identity manual + NYC Subway map | 严格 grid + 3 色铁律 + 拒绝装饰 |
| **v5e Kenya Hara** | 极简日式 | MUJI 海报 + 《白》 | 留白哲学 + 无 chrome + ma 间 |
| **v5f Yayoi Kusama** | 装置艺术 | Infinity Mirror Rooms + Polka Dot Obsession | obsessive 重复 + 单一强色 + 圆点 |
**选择原则**
1. **3 个不同地理文化**(西方电影 / 日本设计 / 港式中文)
2. **3 个不同年代**1960s / 1970s / 2010s+
3. **3 个不同载体**(电影 / 平面设计 / 装置艺术)
4. **每个都有「跟训练语料里通用 SaaS 美学完全相反」的视觉签名**
---
## 实施流程
### Step 1 · 为每个视角写独立 brief约 15 分钟)
每个 brief 包含 8 个固定字段:
```
1. 项目背景(同一份)
2. 必读参考(同一份 v5-director-notes.md 作方法论模板)
3. 你要做的事4 项交付清单)
4. 该艺术家 DNA核心字段 6 项):
- 色板(具体 HEX
- 字体(具体名字 + 替代方案)
- 视觉语言(核心几条)
- 招牌元素identifiable signatures
- 节奏(区别其他视角)
- 反 AI slop 强化版(在该风格语境下的禁区)
5. 30 秒结构参考4-6 个 shot 草拟)
6. destination cards 设计要求(保持真实可读)
7. 关键约束30s / 1920×1080 / file:// / Google Fonts CDN
8. 输出验证清单 + 完成报告格式
```
**关键**:每个 brief 必须强调「**不要重复 v5 的美学**」——否则 subagent 会被 v5 director-notes 影响而趋同。
### Step 2 · 并行启动 6 个 subagent同一 message 中 6 个 Agent tool calls
```js
Agent({ subagent_type: "general-purpose", run_in_background: true, name: "v5a-anderson", ... })
Agent({ subagent_type: "general-purpose", run_in_background: true, name: "v5b-bass", ... })
// ... 6 个
```
后台运行,预期 30-60 分钟。
### Step 3 · 等待期间的 idle work
不要 polling agent 状态。subagent 完成会自动 task-notification。等待期间做
- 修主线程的 v5 基线 bug
- 写 review framework每个版本要打的分维度 / Q&A
- 沉淀方法论到 skill这正是这份 case study 的来源)
- 准备 final summary 文档骨架
### Step 4 · 失败处理(约 16% 失败率,可接受)
实战观测6 个 subagent 中约 1 个会因网络或 token 超限失败Bass 首轮 socket error。处理
1. 收到 completion notification 时**立即检查**该 agent 的输出文件夹
2. 缺少关键交付物 → 重启该 agent同样 brief可标注「上次失败请重新执行」
3. 部分完成(如有 html 没截图)→ 主线程补 Playwright 截图,不重启 agent
### Step 5 · 6 版本完成后系统审校
审校 framework5 维度 + 3 顶层问 + use case 分配):
```
5 维度评分(每维 1-10
- Distinctiveness 视觉差异化
- Coherence 美学一致性
- Anti-slop 反 AI slop 执行
- Story arc 节奏与故事弧
- Pause-and-look 细节密度
3 顶层问:
- Q1 截图分享?(能在社交平台触发暂停)
- Q2 记一句话?(能留下命题级记忆)
- Q3 跨时代5 年后回看不显廉价)
use case 分配(按平台和受众):
- 公众号 / X / B 站 / 朋友圈 / Dribbble / 客户演示 / 私域 / ...
```
详见 `assets/director-notes-samples/launch-film-30s-sample.md` 的同目录 REVIEW.md。
---
## 实验产出(事实)
### 文档量
- v5 基线 director-notes11500 字
- 6 视角 director-notes 各 4000-12000 字
- 总文档量:约 55000-70000 字
- 5 大部分结构齐全6/6 版本
### HTML 实施
- 每版独立 animation.html30 秒1920×1080
- 文件大小 28-74KB
- 全部 file:// 可打开(不依赖 server
### 关键帧
- 每版 10-18 张 PNG覆盖完整 30 秒故事弧
- 总截图量80+ 张
- 平均每张 PNG 大小100-200KB
### 时长
- 6 个 subagent 并行运行:约 12-15 分钟duration_ms 显示)
- 主线程并行 idle work修 v5 + 写方法论):同期完成
- 整体「从启动 6 视角到所有 deliverable 到位」:约 60 分钟
---
## 关键洞察(写给 huashu-design 的未来用户)
### 洞察 1 · 「先写万字 director's notes」方法论**完全 reproducible**
6 个 subagent 都按 5 大部分结构产出了 4000-12000 字的完整 spec且实施 HTML 时都达到了 marketing-ready 质量。这证明方法论本身不依赖单一执行者的天赋——**只要 brief 给得清楚,多个独立执行者能产出一致的高质量结果**。
### 洞察 2 · 「视角」必须具体到「作品 + 年份」
每个 brief 里都列出具体作品对话:
- Anderson → *The French Dispatch* (2021) + *Moonrise Kingdom* (2012) + Penguin Classics dust jackets + 1960s Olivetti catalogues
- WKW → *In the Mood for Love* (2000) + *2046* (2004)
- Vignelli → 1972 NYC Subway map + Knoll identity manual + *The Vignelli Canon*
- Hara → MUJI brand 1995-2023 + 《白》 + Junya Ishigami transparency
- Kusama → Infinity Mirrored Rooms (2013-2023) + Polka Dot Obsession 装置
**实战结果**:所有 subagent 都准确捕捉到了该作品的核心 visual DNA而不是流派的「平均值」。
### 洞察 3 · 反 AI slop 的「风格强化版本」是关键
通用 anti-slop紫渐变 / emoji / SVG 人物)适用所有版本。但**每个风格还要写「专属 anti-slop」**
- Bass: 不用 Helvetica太干净Bass 是粗犷)
- Vignelli: 不用圆角(所有 corner 90°
- Hara: 不用任何渐变 + 不用 sans display
- Kusama: 不用现代 SaaS look
- Anderson: 不用 cyber 配色
- WKW: 不用 InterWKW 用衬线)
加了这些后6 个版本风格纯度极高,无一相互趋同。
### 洞察 4 · 多视角的真正价值不是「选 winner」
最初设想是 A/B test 选最好的版本。实际审校时发现:**6 个版本各自有清晰 use case**
- v5 基线 → 产品页 / 微信读书(信息密度高)
- Anderson → 公众号长文头图(翻杂志感强)
- WKW → B 站 / 中文文化向(怀旧温度)
- Vignelli → 设计圈 / Dribbble每帧都是印刷海报
- Hara → 客户演示 / 静态截图(极简哲学)
- Kusama → X 短视频 / 病毒传播(视觉冲击)
**结论**marketing 不是 single-shot是 platform-specific multiplex。6 视角并行的真正价值是**让一个项目有 6 个差异化武器**,不是让 5 个版本上不了台面。
### 洞察 5 · subagent 的失败率 ~16% 是可接受的
6 个里 1 个失败Bass 首轮 socket error。处理代价重启 + 5 分钟简化版 brief再等 12-15 分钟。**对比 vs. 等 1 个 agent 顺序跑 6 个版本90+ 分钟)**——并行 + 重试明显更经济。
### 洞察 6 · 主线程在等待期间必须做 substantive idle work
subagent 完成需要 12-15 分钟。这段时间主线程绝不该空闲:
- **修主版本 bug**(用户已经反馈的)
- **写 review framework**(等审校时填)
- **沉淀方法论到 skill**(如这份 case study
- **准备 final summary**(用户回来一目了然)
这是 parallel multi-agent workflow 的「主线程职责」——不是 PM 等结果,是 orchestrator 同步推进。
---
## 何时启用「多视角并行」
| 场景 | 是否启用 | 原因 |
|------|---------|------|
| 用户明确说「想看不同方向」「再多做几个版本」 | ✅ 立刻启用 | 直接需求 |
| 第一版做出来用户不满意但说不清要啥 | ✅ 启用 | A/B 选优于「我猜你要啥」 |
| 项目准备多平台分发X / 公众号 / B 站 / 朋友圈) | ✅ 启用 | 每平台一个版本 |
| 客户没拍板风格但有预算time + token | ✅ 启用 | 反复改 = 5 倍代价 |
| 用户已经给了明确风格参考且只要 1 个版本 | ❌ 不启用 | 浪费 |
| 任务是简单 motion graphic / icon 动画 | ❌ 不启用 | 过度工程化 |
| 时间紧 < 30 分钟 | 不启用 | subagent 跑不完 |
---
## 完整方法论流程图
```
用户 brief含质量预期
[主线程] 写 v5 基线 director's notes万字级 5 大部分)
[主线程] 实施 v5 HTML + 截关键帧marketing baseline
[决策点] 是否启用多视角?
↓ YES
[主线程] 选 6 个差异化视角 + 写 6 份独立 brief每份 8 字段)
[6 subagents 并行]
├── v5a brief → director-notes + html + keyframes + README
├── v5b brief → ...
├── v5c brief → ...
├── v5d brief → ...
├── v5e brief → ...
└── v5f brief → ...
[主线程同步做] 修 v5 bug · 写 review framework · 沉淀方法论
[全 6 通知到达]
[主线程] 失败检测 + 重试 / 补截图
[主线程] 5 维度评分 + 3 顶层问 + use case 分配
[主线程] 写 final REVIEW.md
[交付] 6 完整版本 + review + 平台分发推荐
```
---
## 相关文档
- 完整方法论`references/launch-film-director-notes.md`
- 单视角样本`assets/director-notes-samples/launch-film-30s-sample.md`v5 基线
- 实战项目位置`~/.claude/skills/huashu-md-html/demos/` 6 + 1 视角全套文件
- 审校 review`~/.claude/skills/huashu-md-html/demos/REVIEW.md`
---
*Last updated: 2026-05-11*
*Real case study: huashu-md-html v2.0 launch film 6-perspective parallel experiment*

View File

@@ -0,0 +1,276 @@
# React + Babel 项目规范
用HTML+React+Babel做原型时必须遵守的技术规范。不遵守会炸。
## Pinned Script Tags必须用这些版本
在HTML的`<head>`里放这三个script tag用**固定版本+integrity hash**
```html
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
```
**不要**用`react@18``react@latest`这种unpinned版本——会出现版本漂移/缓存问题。
**不要**省略`integrity`——CDN一旦被劫持或篡改这是防线。
## 文件结构
```
项目名/
├── index.html # 主HTML
├── components.jsx # 组件文件type="text/babel"加载)
├── data.js # 数据文件
└── styles.css # 额外CSS可选
```
HTML里加载方式
```html
<!-- 先React+Babel -->
<script src="https://unpkg.com/react@18.3.1/..."></script>
<script src="https://unpkg.com/react-dom@18.3.1/..."></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/..."></script>
<!-- 然后你的组件文件 -->
<script type="text/babel" src="components.jsx"></script>
<script type="text/babel" src="pages.jsx"></script>
<!-- 最后主入口 -->
<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
```
**不要**用`type="module"`——会和Babel冲突。
## 三条不可违反的规矩
### 规矩1styles 对象必须用唯一命名
**错误**(多组件时必炸):
```jsx
// components.jsx
const styles = { button: {...}, card: {...} };
// pages.jsx ← 同名覆盖!
const styles = { container: {...}, header: {...} };
```
**正确**每个组件文件的styles用唯一前缀。
```jsx
// terminal.jsx
const terminalStyles = {
screen: {...},
line: {...}
};
// sidebar.jsx
const sidebarStyles = {
container: {...},
item: {...}
};
```
**或者用inline styles**(小组件推荐):
```jsx
<div style={{ padding: 16, background: '#111' }}>...</div>
```
这条是**非协商**的。每次写`const styles = {...}`都必须replace成specific命名否则多组件加载时全栈报错。
### 规矩2Scope 不共享需手动export
**关键认知**:每个`<script type="text/babel">`被Babel独立编译它们之间**scope不通**。`components.jsx`里定义的`Terminal`组件,在`pages.jsx`里**默认是undefined**。
**解决方式**:在每个组件文件末尾,把要共享的组件/工具export到`window`
```jsx
// components.jsx 末尾
function Terminal(props) { ... }
function Line(props) { ... }
const colors = { green: '#...', red: '#...' };
Object.assign(window, {
Terminal, Line, colors,
// 所有你要在别处用的都列在这里
});
```
然后`pages.jsx`就能直接用`<Terminal />`因为JSX会去`window.Terminal`找。
### 规矩3不要用 scrollIntoView
`scrollIntoView`会把整个HTML容器往上推搞坏web harness的布局。**永远不要用**。
替代方案:
```js
// 滚到容器内某个位置
container.scrollTop = targetElement.offsetTop;
// 或者用element.scrollTo
container.scrollTo({
top: targetElement.offsetTop - 100,
behavior: 'smooth'
});
```
## 调 Claude APIHTML内
部分原生 design-agent 环境(如 Claude.ai Artifacts有免配置的 `window.claude.complete`,但大部分 agent 环境Claude Code / Codex / Cursor / Trae / etc.)本地里**没有**。
如果你的 HTML 原型需要调用 LLM 做 demo比如做个聊天 interface两个选项
### 选项A不真调用mock
Demo场景推荐。写一个假helper返回预设的response
```jsx
window.claude = {
async complete(prompt) {
await new Promise(r => setTimeout(r, 800)); // 模拟延迟
return "这是一个mock响应。真部署时请替换为真API。";
}
};
```
### 选项B真调Anthropic API
需要API key用户必须在HTML里填入自己的key才能跑。**永远不要把key硬编码在HTML里**。
```html
<input id="api-key" placeholder="粘贴你的Anthropic API key" />
<script>
window.claude = {
async complete(prompt) {
const key = document.getElementById('api-key').value;
const res = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': key,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-haiku-4-5',
max_tokens: 1024,
messages: [{ role: 'user', content: prompt }]
})
});
const data = await res.json();
return data.content[0].text;
}
};
</script>
```
**注意**浏览器直接调Anthropic API会遇到CORS问题。如果用户给你的预览环境不支持CORS bypass这条路不通。这时候用选项A mock或者告诉用户需要一个proxy后端。
### 选项 C用 agent 侧的 LLM 能力生成 mock 数据
如果只是本地演示用,可以在当前 agent 会话里临时调用该 agent 的 LLM 能力(或用户装的 multi-model 类 skill先生成 mock 响应数据,再硬编码写进 HTML。这样 HTML 运行时完全不依赖任何 API。
## 典型 HTML 起手模板
拷贝这个模板作为React原型的骨架
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Prototype Name</title>
<!-- React + Babel pinned -->
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; width: 100%; }
body {
font-family: -apple-system, 'SF Pro Text', sans-serif;
background: #FAFAFA;
color: #1A1A1A;
}
#root { min-height: 100vh; }
</style>
</head>
<body>
<div id="root"></div>
<!-- 你的组件文件 -->
<script type="text/babel" src="components.jsx"></script>
<!-- 主入口 -->
<script type="text/babel">
const { useState, useEffect } = React;
function App() {
return (
<div style={{padding: 40}}>
<h1>Hello</h1>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>
```
## 常见报错及解决
**`styles is not defined``Cannot read property 'button' of undefined`**
→ 你在一个文件里定义了`const styles`另一个文件覆盖了。给每个改成specific命名。
**`Terminal is not defined`**
→ 跨文件引用时scope不通。在定义Terminal的文件末尾加`Object.assign(window, {Terminal})`
**整个页面白屏,控制台没错误**
→ 多半是JSX语法错误但Babel没报在控制台。把`babel.min.js`临时换成`babel.js`非压缩版,错误信息更清晰。
**ReactDOM.createRoot is not a function**
→ 版本不对。确认用了react-dom@18.3.1而不是17或其他
**`Objects are not valid as a React child`**
→ 你渲染了一个对象而不是JSX/字符串。通常是`{someObj}`写成了`{someObj.name}`
## 大项目怎么拆文件
**>1000行的单文件**难维护。分拆思路:
```
项目/
├── index.html
├── src/
│ ├── primitives.jsx # 基础元素Button、Card、Badge...
│ ├── components.jsx # 业务组件UserCard、PostList...
│ ├── pages/
│ │ ├── home.jsx # 首页
│ │ ├── detail.jsx # 详情页
│ │ └── settings.jsx # 设置页
│ ├── router.jsx # 简单路由React state切换
│ └── app.jsx # 入口组件
└── data.js # mock data
```
HTML里按顺序加载
```html
<script type="text/babel" src="src/primitives.jsx"></script>
<script type="text/babel" src="src/components.jsx"></script>
<script type="text/babel" src="src/pages/home.jsx"></script>
<script type="text/babel" src="src/pages/detail.jsx"></script>
<script type="text/babel" src="src/pages/settings.jsx"></script>
<script type="text/babel" src="src/router.jsx"></script>
<script type="text/babel" src="src/app.jsx"></script>
```
**每个文件末尾**都要`Object.assign(window, {...})`导出要共享的东西。

View File

@@ -0,0 +1,262 @@
# 场景模板库:按输出类型组织
> 与 design-styles.md 的「提示词DNA」组合使用。
> 公式:`[风格提示词DNA] + [场景模板] + [具体内容描述]`
---
## 1. 公众号封面 / 文章题图
**规格**
- 封面图2.35:1900×383px 或 1200×510px
- 正文插图16:91200×675px或 4:31200×900px
**关键设计要素**
- 视觉冲击力优先(用户在信息流中快速扫过)
- 文字极少或无文字(公众号标题会覆盖在上面)
- 色彩饱和度适中(微信阅读环境偏白)
- 避免过度细节(缩略图也要可辨识)
**推荐风格**01 Pentagram / 11 Build / 12 Sagmeister / 18 Kenya Hara / 07 Field.io
**场景提示词模板**
```
[风格DNA插入此处]
- Article cover image for WeChat subscription
- Landscape format, 2.35:1 aspect ratio
- Bold visual impact, minimal or no text
- Moderate color saturation for white reading environment
- Must remain recognizable as thumbnail
- Clean composition with clear focal point
```
---
## 2. 正文配图 / 概念插画
**规格**
- 16:91200×675px最通用
- 1:1800×800px适合强调
- 4:31200×900px适合信息密集
**关键设计要素**
- 服务于文章论点,不是装饰
- 与上下文形成视觉节奏
- 简洁表达一个核心概念
- AI生成优先HTML截图仅在精确数据表格时用
**推荐风格**:根据文章调性选择,常用 01/04/10/17/18
**场景提示词模板**
```
[风格DNA插入此处]
- Article illustration, concept visualization
- [16:9 / 1:1 / 4:3] aspect ratio
- Single clear concept: [描述核心概念]
- Serve the argument, not decoration
- [Light/Dark] background to match article tone
```
---
## 3. 信息图 / 数据可视化
**规格**
- 竖版长图1080×1920px手机阅读
- 横版1920×1080px文章内嵌
- 方形1080×1080px社交媒体
**关键设计要素**
- 信息层级清晰(标题 → 核心数据 → 细节)
- 数据准确,不编造
- 视觉引导线(用户阅读路径)
- 适当使用图标/图表辅助理解
**推荐风格**04 Fathom / 10 Müller-Brockmann / 02 Stamen / 17 Takram
**场景提示词模板**
```
[风格DNA插入此处]
- Infographic / data visualization
- [Vertical 1080x1920 / Horizontal 1920x1080 / Square 1080x1080]
- Clear information hierarchy: title → key data → details
- Visual flow guiding reader's eye path
- Icons and charts for comprehension
- Data-accurate, no decorative distortion
```
---
## 4. PPT / Keynote 演示
**规格**
- 标准16:91920×1080px
- 宽屏16:101920×1200px
**关键设计要素**
- 每页一个核心信息(不堆砌)
- 字号层级明确标题40pt+ / 正文24pt+ / 注释16pt+
- 大量留白,投影时更清晰
- 图文比例至少 60:40
- 一致的视觉系统(颜色、字体、间距)
**推荐风格**01 Pentagram / 10 Müller-Brockmann / 11 Build / 18 Kenya Hara / 04 Fathom
**场景提示词模板**
```
[风格DNA插入此处]
- Presentation slide design, 16:9
- One core message per slide
- Clear type hierarchy (title 40pt+, body 24pt+)
- Generous whitespace for projection clarity
- Consistent visual system throughout
- [Light/Dark] theme
```
---
## 5. PDF 白皮书 / 技术报告
**规格**
- A4 纵向210×297mm / 595×842pt
- Letter 纵向216×279mm / 612×792pt
**关键设计要素**
- 长文阅读优化行宽66字符、行高1.5-1.8
- 清晰的章节导航系统
- 页眉/页脚/页码的统一设计
- 图表与正文的优雅共存
- 引用/注释系统
- 封面页设计精致
**推荐风格**10 Müller-Brockmann / 04 Fathom / 03 Information Architects / 17 Takram / 19 Irma Boom
**场景提示词模板**
```
[风格DNA插入此处]
- PDF document / white paper design
- A4 portrait format (210×297mm)
- Long-form reading optimized (66 char line width, 1.5 line height)
- Clear chapter navigation system
- Elegant header/footer/page number design
- Charts integrated with body text
- Professional cover page
```
---
## 6. 落地页 / 产品官网
**规格**
- Desktop: 1440px 宽度设计响应至320px
- 首屏高度100vh
**关键设计要素**
- 首屏5秒内传达核心价值
- 清晰的CTA行动按钮
- 滚动叙事结构(问题→方案→证明→行动)
- 移动端适配
- 加载速度
**推荐风格**05 Locomotive / 01 Pentagram / 11 Build / 08 Resn / 06 Active Theory
**场景提示词模板**
```
[风格DNA插入此处]
- Landing page / product website
- Desktop 1440px width, responsive
- Hero section 100vh, core value in 5 seconds
- Clear CTA button design
- Scroll narrative: problem → solution → proof → action
- Modern web aesthetic
```
---
## 7. App UI / 原型界面
**规格**
- iOS: 390×844ptiPhone 15
- Android: 360×800dp
- 平板: 1024×1366ptiPad Pro
**关键设计要素**
- 触摸友好最小点击区44×44pt
- 系统设计语言一致性
- 状态栏/导航栏/Tab栏的标准处理
- 信息密度适中(移动端不宜过密)
**推荐风格**17 Takram / 11 Build / 03 Information Architects / 01 Pentagram
**场景提示词模板**
```
[风格DNA插入此处]
- Mobile app UI design
- iOS [390×844pt] / Android [360×800dp]
- Touch-friendly (44pt minimum tap targets)
- Consistent design system
- Standard status bar / navigation / tab bar
- Moderate information density
```
---
## 8. 小红书配图
**规格**
- 竖版3:41080×1440px最佳
- 方形1:11080×1080px
- 首图决定点击率
**关键设计要素**
- 视觉吸引力第一(在瀑布流中竞争)
- 可以有少量文字但不超过画面20%
- 色彩鲜明但不俗
- 生活感/质感/氛围感
**推荐风格**12 Sagmeister / 11 Build / 20 Neo Shen / 09 Experimental Jetset
**场景提示词模板**
```
[风格DNA插入此处]
- Social media image for Xiaohongshu (RED)
- Vertical 3:4 (1080×1440px)
- Eye-catching in waterfall feed
- Minimal text overlay (under 20% of area)
- Vivid but tasteful colors
- Lifestyle/texture/atmosphere feel
```
---
## 组合示例
**场景**公众号封面介绍一款AI编程工具想要专业但有温度
**Step 1**:选风格 → 17 Takram专业+温度)
**Step 2**取Takram提示词DNA + 公众号封面模板
```
Takram Japanese speculative design:
- Elegant concept prototypes and diagrams
- Soft tech aesthetic (rounded corners, gentle shadows)
- Charts and diagrams as art pieces
- Modest sophistication
- Neutral natural colors (beige, soft gray, muted green)
- Design as philosophical inquiry
Article cover image for WeChat subscription
- Landscape format, 2.35:1 aspect ratio (1200×510px)
- Bold visual impact, minimal text
- Moderate color saturation for white reading environment
- Must remain recognizable as thumbnail
- Clean composition with clear focal point
Content: An AI coding assistant tool, showing the concept of human-AI collaboration
in software development, warm and professional atmosphere
```
---
**版本**v1.0
**更新日期**2026-02-13

View File

@@ -0,0 +1,226 @@
# SFX Library · huashu-design
> 全部由 ElevenLabs Sound Generation API 生成,苹果发布会级音质。
> 产品级 SFX 资产库,覆盖花叔动画/演示/产品 Demo 全场景。
**资产位置**`assets/sfx/<category>/<name>.mp3`
**总数**37 个 SFX30 批量生成 + 7 个 v7b 保留)
**生成模型**ElevenLabs Sound Generation APIprompt_influence 0.4
**音质**44.1kHz MP3苹果发布会级清晰度无额外混响
---
## 目录结构
```
assets/sfx/
├── keyboard/ type, type-fast, delete-key, space-tap, enter
├── ui/ click, click-soft, focus, hover-subtle, tap-finger, toggle-on
├── transition/ whoosh, whoosh-fast, swipe-horizontal, slide-in, dissolve
├── container/ card-snap, card-flip, stack-collapse, modal-open
├── feedback/ success-chime, error-tone, notification-pop, achievement
├── progress/ loading-tick, complete-done, generate-start
├── impact/ logo-reveal, logo-reveal-v2, brand-stamp, drop-thud
├── magic/ sparkle, ai-process, transform
└── terminal/ command-execute, output-appear, cursor-blink
```
---
## 快速索引
### ⌨️ Keyboard键盘输入
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/keyboard/type.mp3` | 0.5s | 单键敲击mechanical keyboard single key | mechanical keyboard single key press |
| `sfx/keyboard/type-fast.mp3` | 1.5s | 连续快速打字(演示输入提示词) | fast continuous typing rhythm, apple magic keyboard |
| `sfx/keyboard/delete-key.mp3` | 0.5s | backspace 回删 | single backspace key, low pitched thud |
| `sfx/keyboard/space-tap.mp3` | 0.5s | 空格键轻击 | soft spacebar tap, wide flat |
| `sfx/keyboard/enter.mp3` | 0.5s | 回车确认v7b 保留) | enter key press, crisp tactile |
### 🎯 UI界面交互
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/ui/click.mp3` | 0.5s | 标准 UI 点击v7b 保留) | crisp modern interface click |
| `sfx/ui/click-soft.mp3` | 0.5s | 柔和 UI click次要按钮/链接) | soft gentle button click, mid pitched |
| `sfx/ui/focus.mp3` | 0.5s | 元素聚焦/选中v7b 保留) | subtle focus tone, element highlight |
| `sfx/ui/hover-subtle.mp3` | 0.5s | 悬停提示(微秒级反馈) | barely audible tick, air whisper |
| `sfx/ui/tap-finger.mp3` | 0.5s | 移动端 tapiOS 界面) | finger tap on touchscreen, muted thud |
| `sfx/ui/toggle-on.mp3` | 0.5s | 开关打开 | ios toggle switch flip, satisfying click |
### 🌊 Transition过渡
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/transition/whoosh.mp3` | 0.5s | 标准 whooshv7b 保留) | air whoosh transition |
| `sfx/transition/whoosh-fast.mp3` | 0.6s | 快速 whoosh标题闪入、标签切换 | quick fast air whoosh, cinematic |
| `sfx/transition/swipe-horizontal.mp3` | 0.7s | 横向滑动轮播、tab 切换) | smooth left-to-right air movement |
| `sfx/transition/slide-in.mp3` | 0.6s | 元素滑入side panel、抽屉 | smooth soft whoosh with arrival |
| `sfx/transition/dissolve.mp3` | 0.8s | 柔化融化(图片淡出淡入) | soft dissolve, airy shimmer |
### 🃏 Container卡片/容器)
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/container/card-snap.mp3` | 0.5s | 卡片吸附/定位v7b 保留) | card snap into place |
| `sfx/container/card-flip.mp3` | 0.7s | 卡片翻转(前后面切换) | playing card flip, crisp snap |
| `sfx/container/stack-collapse.mp3` | 0.8s | 堆叠合拢(列表聚合) | cards stacking, paper taps collapsing |
| `sfx/container/modal-open.mp3` | 0.6s | 模态框打开 | modal popping open, whoosh + thud |
### 🔔 Feedback通知/反馈)
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/feedback/success-chime.mp3` | 1.0s | 成功提示(支付成功、任务完成) | two ascending bell tones, ios-style |
| `sfx/feedback/error-tone.mp3` | 0.7s | 错误提示(警告、失败) | descending two-note warning, soft |
| `sfx/feedback/notification-pop.mp3` | 0.6s | 消息弹出toast、通知 | notification bloop, ios message alert |
| `sfx/feedback/achievement.mp3` | 1.5s | 成就达成(里程碑、徽章) | triumphant rising arpeggio, game-style |
### ⏳ Progress进度/状态)
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/progress/loading-tick.mp3` | 0.5s | 加载计时(进度条节拍) | soft short pulse, minimal ambient |
| `sfx/progress/complete-done.mp3` | 0.8s | 完成确认step 完成) | two ascending satisfying tones |
| `sfx/progress/generate-start.mp3` | 0.8s | AI 开始生成 | soft rising shimmer, magical whoosh |
### 💥 Impact品牌/冲击)
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/impact/logo-reveal.mp3` | 0.7s | Logo impactv7b 保留) | logo reveal thud |
| `sfx/impact/logo-reveal-v2.mp3` | 1.5s | 更长的 Logo impact电影感 | cinematic bass hit with shimmer tail |
| `sfx/impact/brand-stamp.mp3` | 1.0s | 印章重击(认证、盖章) | rubber stamp thud, paper contact |
| `sfx/impact/drop-thud.mp3` | 0.7s | 物件落地(插入、放置) | heavy thud, wood surface contact |
### ✨ MagicAI 变换)
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/magic/sparkle.mp3` | 0.8s | 魔法闪光AI 高亮、惊喜) | bright twinkling stars, fairy dust |
| `sfx/magic/ai-process.mp3` | 1.2s | AI 处理音thinking 状态) | modulating digital hum with shimmer |
| `sfx/magic/transform.mp3` | 1.0s | 变换过渡morph 效果) | rising shimmer whoosh with sparkle tail |
### 💻 Terminal命令行
| 文件 | 时长 | 用途 | Prompt 要点 |
|---|---|---|---|
| `sfx/terminal/command-execute.mp3` | 0.5s | 命令执行 | crisp digital beep with tick, hacker ui |
| `sfx/terminal/output-appear.mp3` | 0.6s | 输出出现 | rapid digital ticks, retro printout |
| `sfx/terminal/cursor-blink.mp3` | 0.5s | 光标闪烁 | subtle soft digital pulse, rhythmic |
---
## 按场景推荐搭配
### 💻 Terminal 交互演示
```
type (0.5s) → enter (0.5s) → command-execute (0.5s) → output-appear (0.6s)
```
循环元素:`cursor-blink` 作为 idle 时的环境音。
### 🃏 卡片选择流程
```
hover-subtle (0.5s, UI悬停) → click-soft (0.5s, 点击) → card-snap (0.5s, 定位)
```
或进阶版:`card-flip` 做前后面切换。
### 🤖 AI 生成全流程
```
generate-start (0.8s, 启动) → ai-process (1.2s, 处理) → sparkle (0.8s, 闪现) → complete-done (0.8s, 完成)
```
错误时用 `error-tone` 替代 `complete-done`
### 🎬 Logo Reveal品牌时刻
```
whoosh-fast (0.6s, 铺垫) → logo-reveal-v2 (1.5s, 落点) → sparkle (0.8s, 尾韵)
```
简版:`whoosh → logo-reveal`(直接 v7b 两件套)。
### 📱 UI 交互演示(移动端)
```
tap-finger (0.5s, 点击) → slide-in (0.6s, 面板滑入) → toggle-on (0.5s, 开关)
```
完成后:`success-chime``notification-pop`
### 📊 数据可视化/仪表盘
```
loading-tick (0.5s, 节拍) × N → complete-done (0.8s, 数据到位) → achievement (1.5s, 惊艳落点)
```
### 🎯 表单提交流程
```
click-soft (0.5s) → loading-tick ×2 (1.0s) → success-chime (1.0s)
```
失败分支:`error-tone (0.7s)`
### 🪄 Magic Transform 场景
```
whoosh-fast (0.6s) → transform (1.0s) → sparkle (0.8s)
```
适合:元素变形、效果前后对比、"AI 重写"等演示。
---
## 使用规范
### 音量建议(来自 apple-gallery-showcase.md 音频双轨制)
- **SFX 主轨**`1.0`(不做衰减)
- **BGM 背景轨**`0.4 ~ 0.5`SFX 明显穿透)
- **多 SFX 叠加**:用 `amix=inputs=N:duration=longest:normalize=0` 保留动态范围
### ffmpeg 拼接模板
```bash
# 单 SFX 对齐时间点:
ffmpeg -i video.mp4 -itsoffset 2.5 -i sfx/ui/click.mp3 \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=longest:normalize=0[a]" \
-map 0:v -map "[a]" output.mp4
# 多 SFX + BGM
ffmpeg -i video.mp4 \
-itsoffset 1.0 -i sfx/transition/whoosh-fast.mp3 \
-itsoffset 1.6 -i sfx/impact/logo-reveal-v2.mp3 \
-i bgm.mp3 \
-filter_complex "[3:a]volume=0.4[bgm];[0:a][1:a][2:a][bgm]amix=inputs=4:normalize=0[a]" \
-map 0:v -map "[a]" output.mp4
```
### 选型决策树
1. **有 tactile 动作**(打字/点击/滑动)→ `keyboard/` or `ui/`
2. **元素进场/出场**`transition/`
3. **容器层操作**(卡片/模态) → `container/`
4. **状态反馈**(成功/失败/通知) → `feedback/`
5. **进度/时间流逝**`progress/`
6. **品牌落点/重要时刻**`impact/`
7. **AI 魔法/变换**`magic/`
8. **命令行/代码演示**`terminal/`
### 避免叠音堆积
- 同一个时间点 `max 2 个 SFX` 并发
- BGM 降到 0.3 以下时可以放 3 个
- 品牌 impact 时清空其他 SFX留空 0.2s 再落点)
---
## Prompt 撰写原则(供复用)
参考风格:`apple keynote, tight, minimal, no reverb unless ambient, crisp, elegant`
**好 prompt 的三要素**
1. **声音物理描述**:什么物体、什么动作("mechanical keyboard single key press"
2. **质感/风格限定**apple-style / ios-style / cinematic / retro
3. **反例排除**no reverb / clean studio / minimal
❌ "click sound"
✅ "crisp ui button click, clean modern interface sound, apple-style, high pitched"
❌ "magic"
✅ "bright twinkling stars sound, high pitched glittery chime, fairy dust"
---
## 详见
- 音频双轨制与 ffmpeg 拼接:`apple-gallery-showcase.md`
- 原始生成脚本:`/tmp/gen_sfx_batch.sh`(一次性批量生成器)

View File

@@ -0,0 +1,726 @@
# Slide DecksHTML幻灯片制作规范
做幻灯片是设计工作的高频场景。这份文档说明怎么做好HTML幻灯片——从架构选型、单页设计到 PDF/PPTX 导出的完整路径。
**本 skill 的能力覆盖**
- **HTML 演示版(基础产物,永远默认必做)** → 每页独立 HTML + `assets/deck_index.html` 聚合,浏览器里键盘翻页、全屏演讲
- HTML → PDF 导出 → `scripts/export_deck_pdf.mjs` / `scripts/export_deck_stage_pdf.mjs`
- HTML → 可编辑 PPTX 导出 → `references/editable-pptx.md` + `scripts/html2pptx.js` + `scripts/export_deck_pptx.mjs`(要求 HTML 按 4 条硬约束写)
> **⚠️ HTML 是基础PDF/PPTX 是衍生物。** 不管最终交付什么格式,都**必须**先做 HTML 聚合演示版(`index.html` + `slides/*.html`它是幻灯片作品的「源」。PDF/PPTX 是从 HTML 一行命令导出的快照。
>
> **为什么 HTML 优先**
> - 演讲/演示现场最好用(投影仪 / 共享屏幕直接全屏,键盘翻页,不依赖 Keynote/PPT 软件)
> - 开发过程中每页可单独双击打开验证,不用每次重新跑导出
> - 是 PDF/PPTX 导出的唯一上游(避免「导出后才发现要改 HTML 又要重出」的死循环)
> - 交付物可以是「HTML + PDF」或「HTML + PPTX」双份接收方爱用哪个用哪个
>
> 2026-04-22 moxt brochure 实测:做完 13 页 HTML + index.html 聚合后,`export_deck_pdf.mjs` 一行导出 PDF零改动。HTML 版本身就是可直接浏览器演讲的交付物。
---
## 🛑 开工前先确认交付格式(最硬的 checkpoint
**这个决策比「单文件还是多文件」更先。** 2026-04-20 期权私董会项目实测:**不在动手前确认交付格式 = 2-3 小时返工。**
### 决策树HTML-first 架构)
所有交付都从同一套 HTML 聚合页(`index.html` + `slides/*.html`)开始。交付格式只决定 **HTML 的写法约束****导出命令**
```
【永远默认 · 必做】 HTML 聚合演示版index.html + slides/*.html
├── 只要浏览器演讲 / 本地 HTML 存档 → 到这里已经完成HTML 视觉自由度最大
├── 还要 PDF打印 / 发群 / 存档) → 跑 export_deck_pdf.mjs 一键出
│ HTML 写法自由,视觉无约束
└── 还要可编辑 PPTX同事要改文字 → 从第一行 HTML 就按 4 条硬约束写
跑 export_deck_pptx.mjs 一键出
牺牲渐变 / web component / 复杂 SVG
```
### 开工话术(抄走即用)
> 不管最后交付是 HTML、PDF 还是 PPTX我都会先做一个可在浏览器里切换和演讲的 HTML 聚合版(`index.html` 加键盘翻页)——这是永远的默认基础产物。在此之上再问你要不要额外出 PDF / PPTX 的快照。
>
> 你需要哪个导出格式?
> - **只要 HTML**(演讲/存档)→ 视觉完全自由
> - **还要 PDF** → 同上,加一条导出命令
> - **还要可编辑 PPTX**(同事会在 PPT 里改文字)→ 我必须从第一行 HTML 就按 4 条硬约束写,会牺牲一些视觉能力(无渐变、无 web component、无复杂 SVG
### 为什么「要 PPTX 就得从头走 4 条硬约束」
PPTX 可编辑的前提是 `html2pptx.js` 能把 DOM 逐元素翻译为 PowerPoint 对象。它需要 **4 条硬约束**
1. body 固定 960pt × 540pt匹配 `LAYOUT_WIDE`13.333″ × 7.5″,不是 1920×1080px
2. 所有文字包在 `<p>`/`<h1>`-`<h6>` 里(禁止 div 直接放文字,禁止用 `<span>` 承载主文字)
3. `<p>`/`<h*>` 自身不能有 background/border/shadow放外层 div
4. `<div>` 不能用 `background-image`(用 `<img>` 标签)
5. 不用 CSS gradient、不用 web component、不用复杂 SVG 装饰
**本 skill 默认的 HTML 视觉自由度高**——大量 span、嵌套 flex、复杂 SVG、web component`<deck-stage>`、CSS 渐变——**几乎没有一条能天然过 html2pptx 的约束**(实测视觉驱动的 HTML 直接上 html2pptxpass 率 < 30%)。
### 两条真实路径的代价对比2026-04-20 真实踩坑)
| 路径 | 做法 | 结果 | 代价 |
|------|------|------|------|
| **先自由写 HTML事后补救 PPTX** | 单文件 deck-stage + 大量 SVG/span 装饰 | 要可编辑 PPTX 只剩两条路<br>A. 手写 pptxgenjs 几百行 hardcode 坐标<br>B. 重写 17 页 HTML 成 Path A 格式 | 2-3 小时返工,且手写版**维护成本永续**HTML 改一个字PPTX 要再人肉同步) |
| ✅ **从第一步按 Path A 约束写** | 每页独立 HTML + 4 条硬约束 + 960×540pt | 一条命令导出 100% 可编辑 PPTX同时也能浏览器全屏演讲Path A HTML 就是浏览器可播放的标准 HTML | 写 HTML 时多花 5 分钟想「文字怎么包进 `<p>`」,零返工 |
### 混合交付怎么办
用户说「我要 HTML 演讲 **和** 可编辑 PPTX」——**这不是混合**,是 PPTX 需求覆盖 HTML 需求。按 Path A 写出来的 HTML 本身就能浏览器全屏演讲(加个 `deck_index.html` 拼接器就行)。**没有额外代价。**
用户说「我要 PPTX **和** 动画 / web component」——**这是真矛盾**。告诉用户:要可编辑 PPTX 就得牺牲这些视觉能力。让他做取舍,不要偷偷做手写 pptxgenjs 方案(会变成永续维护债)。
### 事后才知道要 PPTX 怎么办(紧急补救)
极个别情况HTML 已经写好了才发现要 PPTX。推荐走 **fallback 流程**(完整说明见 `references/editable-pptx.md` 末尾「Fallback已有视觉稿但用户坚持要 editable PPTX」
1. **首选:改出 PDF**(视觉 100% 保留,跨平台,接收方能看能印)—— 如果接收方实际需求是「演讲/存档」PDF 就是最佳交付物
2. **次选AI 以视觉稿为蓝本,重写一版 editable HTML** → 导出 editable PPTX —— 保留色彩/布局/文案的设计决策牺牲渐变、web component、复杂 SVG 等视觉能力
3. **不推荐:手写 pptxgenjs 重建**——位置、字体、对齐都要手调,维护成本高,且后续 HTML 改一个字都得再人肉同步一次
永远把选择告诉用户,让他决定。**永远不要第一反应就开始手写 pptxgenjs**——那是最后的兜底手段。
---
## 🛑 批量制作前:先做 2 页 showcase 定 grammar
**只要 deck ≥ 5 页,绝对不能从第 1 页直接写到最后一页。** 2026-04-22 moxt brochure 实战验证的正确顺序:
1. 选 **2 个视觉差异最大的页面类型**先做 showcase如「封面」+「情绪/引用页」,或「封面」+「产品展示页」)
2. 截图让用户确认 grammarmasthead / 字体 / 色 / 间距 / 结构 / 中英双语比例)
3. 方向通过了再批量推剩下 N-2 页,每页复用已建立的 grammar
4. 全部完成后一起合成 HTML 聚合 + PDF / PPTX 衍生物
**为什么**:直接写 13 页到底 → 用户说「方向不对」= 返工 13 次。先做 2 页 showcase → 方向错 = 返工 2 次。视觉 grammar 一旦确立,后续 N 页的决策空间大幅收窄,只剩「内容怎么放进去」。
**showcase 页选择原则**:选视觉结构最不一样的两页。这两页过了 = 其他中间态都能过。
| Deck 类型 | 推荐 showcase 页组合 |
|-----------|---------------------|
| B2B brochure / 产品宣发 | 封面 + 内容页(理念/情感页) |
| 品牌发布 | 封面 + 产品特色页 |
| 数据报告 | 数据大图页 + 分析结论页 |
| 教程课件 | 章节封页 + 具体知识点页 |
---
## 📐 出版物 grammar 模板moxt 实测可复用)
适合 B2B brochure / 产品宣发 / 长报告类 deck。每页复用这套结构 = 13 页视觉完全一致、0 返工。
### 每页骨架
```
┌─ masthead顶部 strip + 横线)────────────┐
│ [logo 22-28px] · A Product Brochure Issue · Date · URL │
├──────────────────────────────────────────┤
│ │
│ ── kicker绿色短横 + uppercase 标签) │
│ CHAPTER XX · SECTION NAME │
│ │
│ H1中文 Noto Serif SC 900
│ 重点词单独上品牌主色 │
│ │
│ English subtitle (Lora italic副标题) │
│ ─────────── 分隔线 ────────── │
│ │
│ [具体内容:双栏 60/40 / 2x2 grid / 列表] │
│ │
├──────────────────────────────────────────┤
│ section name XX / total │
└──────────────────────────────────────────┘
```
### 样式约定(直接抄走)
- **H1**:中文 Noto Serif SC 900字号 80-140px 看信息量,重点词单独上品牌主色(不要全文堆色)
- **英文副**Lora italic 26-46px品牌签名词如 "AI team")粗体 + 主色斜体
- **正文**Noto Serif SC 17-21pxline-height 1.75-1.85
- **accent 高亮**:正文里用主色加粗标注关键词,每页不超过 3 处(过多就失去锚点作用)
- **背景**:暖米底 #FAFAFA + 极淡 radial-gradient noise`rgba(33,33,33,0.015)`)增加纸感
### 视觉主角必须差异化
13 页如果全是「文字 + 一张截图」就太单调。**每页的视觉主角类型轮换**
| 视觉类型 | 适合的 section |
|---------|---------------|
| 封面排版(大字 + masthead + pillar | 首页 / 篇章封 |
| 单角色 portrait超大单只 momo 等) | 介绍单个概念/角色 |
| 多角色合影 / 头像卡并排 | 团队 / 用户案例 |
| 时间轴卡片递进 | 展示「长期关系」「演进」 |
| 知识图谱 / 连接节点图 | 展示「协作」「流动」 |
| Before/After 对比卡 + 中间箭头 | 展示「改变」「差异」 |
| 产品 UI 截图 + 描边设备框 | 具体功能展示 |
| 大引号 big-quote半页大字 | 情绪页 / 问题页 / 引文页 |
| 真人头像 + 引言卡2×2 或 1×4 | 用户见证 / 使用场景 |
| 大字封底 + URL 椭圆按钮 | CTA / 结尾 |
---
## ⚠️ 常见踩坑moxt 实战总结)
### 1. Emoji 在 Chromium / Playwright 导出时不渲染
Chromium 默认不带彩色 emoji 字体,`page.pdf()``page.screenshot()` 时 emoji 显示为空方框。
**对策**:用 Unicode 文字符号(`✦` `✓` `✕` `→` `·` `—`替代或直接改纯文字「Email · 23」而不是「📧 23 emails」
### 2. `export_deck_pdf.mjs` 报错 `Cannot find package 'playwright'`
原因ESM 模块解析从脚本所在位置向上找 `node_modules`。脚本在 `~/.claude/skills/huashu-design/scripts/`,那里没依赖。
**对策**:把脚本复制到 deck 项目目录(例如 `brochure/build-pdf.mjs`),在项目根跑 `npm install playwright pdf-lib`,然后 `node build-pdf.mjs --slides slides --out output/deck.pdf`
### 3. Google Fonts 没加载完就截图 → 中文显示为系统默认黑体
Playwright 截图/PDF 前至少 `wait-for-timeout=3500` 让 webfont 下载并 paint。或者把字体 self-host 到 `shared/fonts/` 减少网络依赖。
### 4. 信息密度失衡:内容页塞太多
moxt philosophy 页第一版用 2×2 = 4 段 + 底部 3 信条 = 7 块内容,挤压且重复。改成 1×3 = 3 段后呼吸感立刻回来。
**对策**每页控制在「1 个核心信息 + 3-4 个辅助点 + 1 个视觉主角」,超过就拆到新页。**少即是多**——观众一页看 10 秒,给他 1 个记忆点比 4 个记忆点更容易记住。
---
## 🛑 先定架构:单文件 还是 多文件?
**这个选择是做幻灯片的第一步,错了会反复踩坑。先读完这一节再动手。**
### 两种架构对比
| 维度 | 单文件 + `deck_stage.js` | **多文件 + `deck_index.html` 拼接器** |
|------|--------------------------|--------------------------------------|
| 代码结构 | 一个 HTML所有 slide 是 `<section>` | 每页独立 HTML`index.html` 用 iframe 拼接 |
| CSS 作用域 | ❌ 全局,一页的样式可能影响所有页 | ✅ 天然隔离iframe 各自一片天 |
| 验证粒度 | ❌ 要 JS goTo 才能切到某页 | ✅ 单页文件双击就能在浏览器看 |
| 并行开发 | ❌ 一个文件,多 agent 改会冲突 | ✅ 多 agent 可并行做不同页,零冲突 merge |
| 调试难度 | ❌ 一处 CSS 出错,全 deck 翻车 | ✅ 一页出错只影响自己 |
| 内嵌交互 | ✅ 跨页共享状态很简单 | 🟡 iframe 间需 postMessage |
| 打印 PDF | ✅ 内置 | ✅ 拼接器 beforeprint 遍历 iframe |
| 键盘导航 | ✅ 内置 | ✅ 拼接器内置 |
### 选哪个?(决策树)
```
│ 问deck 预计有多少页?
├── ≤10 页、需要 in-deck 动画或跨页交互、pitch deck → 单文件
└── ≥10 页、学术讲座、课件、长 deck、多 agent 并行 → 多文件(推荐)
```
**默认走多文件路径**。它不是「备选」,是**长 deck 和团队协作的主路径**。原因单文件架构的每一个优势键盘导航、打印、scale多文件都有而多文件的作用域隔离和可验证性是单文件补不回来的。
### 为什么这条规则这么硬?(真实事故记录)
单文件架构曾经在 AI心理学讲座 deck 制作中连踩四坑:
1. **CSS 特异性覆盖**`.emotion-slide { display: grid }` (特异性 10) 干翻 `deck-stage > section { display: none }` (特异性 2),导致所有页同时渲染叠加。
2. **Shadow DOM slot 规则被外层 CSS 压制**`::slotted(section) { display: none }` 挡不住 outer rule 的覆盖sections 不肯隐藏。
3. **localStorage + hash 导航竞态**:刷新后不是跳到 hash 位置,而是停在 localStorage 记录的旧位置。
4. **验证成本高**:必须 `page.evaluate(d => d.goTo(n))` 才能截某页,比直接 `goto(file://.../slides/05-X.html)` 慢一倍,还常报错。
全部根因是**单一全局命名空间**——多文件架构从物理层面把这些问题消除了。
---
## 路径 A默认多文件架构
### 目录结构
```
我的Deck/
├── index.html # 从 assets/deck_index.html 复制来,改 MANIFEST
├── shared/
│ ├── tokens.css # 共享设计 token色板/字号/常用 chrome
│ └── fonts.html # <link> 引入 Google Fonts每页 include
└── slides/
├── 01-cover.html # 每个文件都是完整 1920×1080 HTML
├── 02-agenda.html
├── 03-problem.html
└── ...
```
### 每张 slide 的模板骨架
```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>P05 · Chapter Title</title>
<link href="https://fonts.googleapis.com/css2?family=..." rel="stylesheet">
<link rel="stylesheet" href="../shared/tokens.css">
<style>
/* 这一页独有的样式。用任何 class 名都不会污染别的页。*/
body { padding: 120px; }
.my-thing { ... }
</style>
</head>
<body>
<!-- 1920×1080 的内容(由 body 的 width/height 在 tokens.css 里锁定)-->
<div class="page-header">...</div>
<div>...</div>
<div class="page-footer">...</div>
</body>
</html>
```
**关键约束**
- `<body>` 就是画布,直接在上面布局。不要包 `<section>` 或其他 wrapper。
- `width: 1920px; height: 1080px``shared/tokens.css` 里的 `body` 规则锁定。
-`shared/tokens.css` 共享设计 token色板、字号、page-header/footer 等)。
- 字体 `<link>` 每页自己写fonts 单独 import 不贵,且保证每页独立可打开)。
### 拼接器:`deck_index.html`
**直接从 `assets/deck_index.html` 复制**。你只需要改一处——`window.DECK_MANIFEST` 数组,按顺序列出所有 slide 文件名和人类可读标签:
```js
window.DECK_MANIFEST = [
{ file: "slides/01-cover.html", label: "封面" },
{ file: "slides/02-agenda.html", label: "目录" },
{ file: "slides/03-problem.html", label: "问题陈述" },
// ...
];
```
拼接器已内置:键盘导航(←/→/Home/End/数字键/P 打印、scale + letterbox、右下计数器、localStorage 记忆、hash 跳页、打印模式(遍历 iframe 按页输出 PDF
### 单页验证(这是多文件架构的杀手级优势)
每张 slide 都是独立 HTML。**做完一张就在浏览器双击打开看**
```bash
open slides/05-personas.html
```
Playwright 截图也是直接 `goto(file://.../slides/05-personas.html)`,不需要 JS 跳页,也不会被别的页的 CSS 干扰。这让「改一点验一点」的工作流成本接近零。
### 并行开发
把每张 slide 的任务拆给不同 agent同时跑——HTML 文件彼此独立merge 时没有冲突。长 deck 用这种并行方式能把制作时间压到 1/N。
### `shared/tokens.css` 该放什么
只放**真正跨页共用**的东西:
- CSS 变量(色板、字号阶、间距阶)
- `body { width: 1920px; height: 1080px; }` 这样的 canvas 锁定
- `.page-header` / `.page-footer` 这种每页都用一模一样的 chrome
**不要**把单页的布局 class 塞进来——那会退化回单文件架构的全局污染问题。
---
## 路径 B小 deck单文件 + `deck_stage.js`
适用于 ≤10 页、需要跨页共享状态(比如一个 React tweaks 面板要操控所有页)、或者做 pitch deck demo 这种要求极度紧凑的场景。
### 基本用法
1.`assets/deck_stage.js` 读取内容,嵌入 HTML 的 `<script>`(或 `<script src="deck_stage.js">`
2. 在 body 里用 `<deck-stage>` 包 slide
3. 🛑 **script 标签必须放在 `</deck-stage>` 之后**(见下方硬约束)
```html
<body>
<deck-stage>
<section>
<h1>Slide 1</h1>
</section>
<section>
<h1>Slide 2</h1>
</section>
</deck-stage>
<!-- ✅ 正确script 在 deck-stage 之后 -->
<script src="deck_stage.js"></script>
</body>
```
### 🛑 Script 位置硬约束2026-04-20 真实踩坑)
**不能把 `<script src="deck_stage.js">` 放在 `<head>` 里。** 即使它在 `<head>` 里能定义 `customElements`parser 在解析到 `<deck-stage>` 开始标签时就会触发 `connectedCallback`——此时子 `<section>` 还没被 parse`_collectSlides()` 拿到空数组counter 显示 `1 / 0`,所有页同时叠加渲染。
**三条合规写法**(任选其一):
```html
<!-- ✅ 最推荐script 在 </deck-stage> 之后 -->
</deck-stage>
<script src="deck_stage.js"></script>
<!-- ✅ 也可script 在 head 但加 defer -->
<head><script src="deck_stage.js" defer></script></head>
<!-- ✅ 也可module 脚本天然 defer -->
<head><script src="deck_stage.js" type="module"></script></head>
```
`deck_stage.js` 本身已内置 `DOMContentLoaded` 延迟收集防御,即使 script 放 head 也不会彻底炸掉——但 `defer` 或放 body 底部仍然是更干净的做法,避免依赖防御分支。
### ⚠️ 单文件架构的 CSS 陷阱(务必阅读)
单文件架构最常见的坑——**`display` 属性被单页样式偷走**。
常见错误姿势 1直接写 display: flex 到 section
```css
/* ❌ 外部 CSS 特异性 2覆盖了 shadow DOM 的 ::slotted(section){display:none}(也是 2*/
deck-stage > section {
display: flex; /* 所有页会同时叠加渲染! */
flex-direction: column;
padding: 80px;
...
}
```
常见错误姿势 2section 有特异性更高的 class
```css
.emotion-slide { display: grid; } /* 特异性: 10更糟 */
```
两种都会让 **所有 slide 同时叠加渲染**——counter 可能显示 `1 / 10` 假装正常,但视觉上第一页盖着第二页盖着第三页。
### ✅ Starter CSS开工直接 copy不踩坑
**section 自身**只管「可见/不可见」;**layoutflex/grid 等)写到 `.active` 上**
```css
/* section 只定义非 display 的通用样式 */
deck-stage > section {
background: var(--paper);
padding: 80px 120px;
overflow: hidden;
position: relative;
/* ⚠️ 不要在这里写 display! */
}
/* 锁死「非激活即隐藏」——特异性+权重双保险 */
deck-stage > section:not(.active) {
display: none !important;
}
/* 激活页才写需要的 display + layout */
deck-stage > section.active {
display: flex;
flex-direction: column;
justify-content: center;
}
/* 打印模式:所有页都要显示,覆盖 :not(.active) */
@media print {
deck-stage > section { display: flex !important; }
deck-stage > section:not(.active) { display: flex !important; }
}
```
替代方案:**把单页的 flex/grid 写到内部 wrapper `<div>` 上**section 本身永远只是 `display: block/none` 的切换器。这是最干净的做法:
```html
<deck-stage>
<section>
<div class="slide-content flex-layout">...</div>
</section>
</deck-stage>
```
### 自定义尺寸
```html
<deck-stage width="1080" height="1920">
<!-- 9:16 竖版 -->
</deck-stage>
```
---
## Slide Labels
Deck_stage 和 deck_index 都会给每页打标签(计数器显示)。给它们**更有意义**的 label
**多文件**:在 `MANIFEST` 里写 `{ file, label: "04 问题陈述" }`
**单文件**:在 section 上加 `<section data-screen-label="04 Problem Statement">`
**关键Slide 编号从 1 开始,不要从 0**
用户说"slide 5"时,他指的是第 5 张,永远不是数组位置 `[4]`。人类不说 0-indexed。
---
## Speaker Notes
**默认不加**,只在用户明确要求时才加。
加了 speaker notes 你就可以把 slide 上的文字减少到最小focus on impactful visuals——notes 承载完整 script。
### 格式
**多文件**:在 `index.html``<head>` 里写:
```html
<script type="application/json" id="speaker-notes">
[
"第1张的 script...",
"第2张的 script...",
"..."
]
</script>
```
**单文件**:同上位置。
### Notes 写作要点
- **完整**:不是提纲,是真要讲的话
- **对话式**:像平时说话,不是书面语
- **对应**:数组第 N 个对应第 N 张 slide
- **长度**200-400 字最佳
- **情绪线**:标注重音、停顿、强调点
---
## Slide 设计模式
### 1. 建立一个系统(必做)
探索完 design context 后,**先口头说你要用的系统**
```markdown
Deck系统
- 背景色最多2种90% 白 + 10% 深色 section divider
- 字型display 用 Instrument Serifbody 用 Geist Sans
- 节奏section divider 用 full-bleed 彩色 + 白字,普通 slide 白底
- 图像hero slide 用 full-bleed 照片data slide 用 chart
我按这个系统做,有问题告诉我。
```
用户确认后再往下做。
### 2. 常用 slide layouts
- **Title slide**:纯色背景 + 巨大标题 + 副标题 + 作者/日期
- **Section divider**:彩色背景 + 章节号 + 章节标题
- **Content slide**:白底 + 标题 + 1-3 bullet points
- **Data slide**:标题 + 大图表/数字 + 简短说明
- **Image slide**full-bleed 照片 + 底部小 caption
- **Quote slide**:留白 + 巨大 quote + attribution
- **Two-column**左右对比vs / before-after / problem-solution
一个 deck 里最多用 4-5 种 layout。
### 3. Scale再次强调
- 正文最小 **24px**,理想 28-36px
- 标题 **60-120px**
- Hero 字 **180-240px**
- 幻灯片是给 10 米外看的,字要够大
### 4. 视觉节奏
Deck 需要 **intentional variety**
- 颜色节奏:大部分白底 + 偶尔彩色 section divider + 偶尔 dark 片段
- 密度节奏:几张 text-heavy 的 + 几张 image-heavy 的 + 几张 quote 留白
- 字号节奏:正常标题 + 偶尔巨型 hero 文字
**不要每张 slide 长一样**——那是 PPT 模板,不是设计。
### 5. 空间呼吸(数据密集页必读)
**新手最容易踩的坑**:把所有能放的信息都塞进一页。
信息密度 ≠ 有效信息传达。学术/演讲类 deck 尤其要克制:
- 列表/矩阵页:不要把 N 个元素都画成同一大小。用 **主次分层**——今天要聊的 5 个放大做主角,剩下 16 个缩小做背景 hint。
- 大数字页:数字本身是视觉主角。周围的 caption 不要超过 3 行,否则观众眼球来回跳。
- 引用页:引语和 attribution 之间要有留白隔开,不要贴在一起。
对照「数据是不是主角」「文字有没有挤在一起」两条自我审查,改到留白让你有点不安为止。
---
## 打印为 PDF
**多文件**`deck_index.html` 已处理 `beforeprint` 事件,按页输出 PDF。
**单文件**`deck_stage.js` 同样处理。
打印样式已写好,不需要额外写 `@media print` CSS。
---
## 导出为 PPTX / PDF自助脚本
HTML 优先是第一公民。但用户经常需要 PPTX/PDF 交付。提供两个通用脚本,**任何多文件 deck 都能用**,位于 `scripts/` 下:
### `export_deck_pdf.mjs` — 导出矢量 PDF多文件架构
```bash
node scripts/export_deck_pdf.mjs --slides <slides-dir> --out deck.pdf
```
**特点**
- 文字**保留矢量**(可复制、可搜索)
- 视觉 100% 保真Playwright 内嵌 Chromium 渲染后打印)
- **不需要改 HTML 任何一个字**
- 每个 slide 独立 `page.pdf()`,再用 `pdf-lib` 合并
**依赖**`npm install playwright pdf-lib`
**限制**PDF 不能再编辑文字——要改回到 HTML 改。
### `export_deck_stage_pdf.mjs` — 单文件 deck-stage 架构专用 ⚠️
**什么时候用**deck 是单 HTML 文件 + `<deck-stage>` web component 包裹 N 个 `<section>`(即路径 B 架构)。此时 `export_deck_pdf.mjs` 那套「每个 HTML 一次 `page.pdf()`」走不通,需要走这个专用脚本。
```bash
node scripts/export_deck_stage_pdf.mjs --html deck.html --out deck.pdf
```
**为什么不能复用 export_deck_pdf.mjs**2026-04-20 真实踩坑记录):
1. **Shadow DOM 赢过 `!important`**deck-stage 的 shadow CSS 里有 `::slotted(section) { display: none }`(只 active 的那张 `display: block`)。即使在 light DOM 用 `@media print { deck-stage > section { display: block !important } }` 也压不住——`page.pdf()` 触发 print 媒体后 Chromium 最终渲染只有 active 那一张,结果**整个 PDF 只有 1 页**(当前 active slide 的重复)。
2. **循环 goto 每页还是只出 1 页**:直觉解法「对每个 `#slide-N` navigate 一次再 `page.pdf({pageRanges:'1'})`」也失败——因为 print CSS 在 shadow DOM 之外也有 `deck-stage > section { display: block }` 规则被 override 后,最终渲染永远是 section 列表的第一个(不是你 navigate 到的那一页)。结果 17 次循环得到 17 张 P01 封面。
3. **absolute 子元素跑到下一页**:即使成功让所有 section 渲染出来section 本身若 `position: static`,其 absolute 定位的 `cover-footer`/`slide-footer` 会相对 initial containing block 定位——当 section 被 print 强制为 1080px 高度absolute footer 可能被推到下一页(表现为 PDF 比 section 数量多 1 页,多出来的那页只含 footer 孤儿)。
**修复策略**(脚本已实现):
```js
// 打开 HTML 后,用 page.evaluate 把 section 从 deck-stage slot 中提出来,
// 直接挂到 body 下一个普通 div 里,并内联 style 确保 position:relative + 固定尺寸
await page.evaluate(() => {
const stage = document.querySelector('deck-stage');
const sections = Array.from(stage.querySelectorAll(':scope > section'));
document.head.appendChild(Object.assign(document.createElement('style'), {
textContent: `
@page { size: 1920px 1080px; margin: 0; }
html, body { margin: 0 !important; padding: 0 !important; }
deck-stage { display: none !important; }
`,
}));
const container = document.createElement('div');
sections.forEach(s => {
s.style.cssText = 'width:1920px!important;height:1080px!important;display:block!important;position:relative!important;overflow:hidden!important;page-break-after:always!important;break-after:page!important;background:#F7F4EF;margin:0!important;padding:0!important;';
container.appendChild(s);
});
// 最后一页禁分页,避免尾部空白页
sections[sections.length - 1].style.pageBreakAfter = 'auto';
sections[sections.length - 1].style.breakAfter = 'auto';
document.body.appendChild(container);
});
await page.pdf({ width: '1920px', height: '1080px', printBackground: true, preferCSSPageSize: true });
```
**为什么这能 work**
- 把 section 从 shadow DOM slot 拔到 light DOM 的普通 div——彻底绕过 `::slotted(section) { display: none }` 规则
- 内联 `position: relative` 让 absolute 子元素相对 section 定位,不会溢出
- `page-break-after: always` 让浏览器 print 时每 section 独立一页
- `:last-child` 不分页避免尾部空白页
**用 `mdls -name kMDItemNumberOfPages` 验证时注意**macOS 的 Spotlight metadata 有缓存PDF 重写后要跑 `mdimport file.pdf` 强制刷新,否则显示旧的页数。用 `pdfinfo``pdftoppm` 数文件数才是真数。
---
### `export_deck_pptx.mjs` — 导出可编辑 PPTX
```bash
# 唯一模式:文本框原生可编辑(字体会回落到系统字体)
node scripts/export_deck_pptx.mjs --slides <dir> --out deck.pptx
```
工作原理:`html2pptx` 逐元素读 computedStyle 把 DOM 翻译成 PowerPoint 对象text frame / shape / picture。文字变成真文本框PPT 里双击即可编辑。
**硬性约束**HTML 必须满足,否则该页 skip详细说明见 `references/editable-pptx.md`
- 所有文字必须在 `<p>`/`<h1>`-`<h6>`/`<ul>`/`<ol>` 里(禁止裸文本 div
- `<p>`/`<h*>` 标签自身不能有 background/border/shadow放外层 div
- 不用 `::before`/`::after` 插入装饰文字(伪元素提不出来)
- inline 元素span/em/strong不能有 margin
- 不用 CSS gradient不可渲染
- div 不用 `background-image`(用 `<img>`
脚本已内置**自动预处理器**——把 "叶子 div 里的裸文本" 自动包成 `<p>`(保留 class。这解决了最常见的违规裸文本。但其他违规p 上有 border、span 上有 margin 等)仍需 HTML 源头合规。
**字体回落 caveat**
- Playwright 用 webfont 测量 text-box 尺寸PowerPoint/Keynote 用本机字体渲染
- 两者不同时会有**溢出或错位**——每页都要肉眼过
- 建议目标机器装好 HTML 里用的字体,或 fallback 到 `system-ui`
**视觉优先场景不要走这条路径** → 改用 `export_deck_pdf.mjs` 出 PDF。PDF 视觉 100% 保真、矢量、跨平台、文字可搜——是视觉优先 deck 的真正归宿,不是什么「不可编辑的妥协」。
### 从一开始就让 HTML 对导出友好
对性能最稳的 deck**从写 HTML 时就按 editable 的 4 条硬约束写**。这样 `export_deck_pptx.mjs` 可以直接全部 pass。额外成本不大
```html
<!-- ❌ 不好 -->
<div class="title">关键发现</div>
<!-- ✅ 好p 包裹class 继承) -->
<p class="title">关键发现</p>
<!-- ❌ 不好border 在 p 上) -->
<p class="stat" style="border-left: 3px solid red;">41%</p>
<!-- ✅ 好border 在外层 div -->
<div class="stat-wrap" style="border-left: 3px solid red;">
<p class="stat">41%</p>
</div>
```
### 何时选哪个
| 场景 | 推荐 |
|------|------|
| 给主办方/档案存档 | **PDF**(通用、高保真、文字可搜) |
| 发给协作者让他们微调文字 | **PPTX editable**(接受字体回落) |
| 要现场演讲、不改内容 | **PDF**(矢量保真,跨平台) |
| HTML 是首选呈现媒介 | 直接浏览器播放,导出只是备份 |
## 导出为可编辑 PPTX 的深度路径(仅长期项目)
如果你的 deck 会长期维护、反复修改、团队协作——建议**一开始就按 html2pptx 约束写 HTML**,这样 `export_deck_pptx.mjs` 可以直接全部 pass。详见 `references/editable-pptx.md`4 条硬约束 + HTML 模板 + 常见错误速查 + 已有视觉稿的 fallback 流程)。
---
## 常见问题
**多文件iframe 里的页打不开 / 白屏**
→ 检查 `MANIFEST``file` 路径是否相对 `index.html` 正确。用浏览器 DevTools 看 iframe 的 src 能否直接访问。
**多文件:某页样式和别页冲突**
→ 不可能iframe 隔离。如果感觉冲突那是缓存——Cmd+Shift+R 强刷。
**单文件:多 slide 同时渲染叠加**
→ CSS 特异性问题。看上面「单文件架构的 CSS 陷阱」一节。
**单文件:缩放看起来不对**
→ 检查是否所有 slide 直接挂在 `<deck-stage>` 下作为 `<section>`。中间不能包 `<div>`
**单文件:想跳到特定 slide**
→ URL 加 hash`index.html#slide-5` 跳到第 5 张。
**两种架构都适用:字在不同屏幕下位置不一致**
→ 用固定尺寸1920×1080`px` 单位,不要用 `vw`/`vh``%`。缩放统一处理。
---
## 验证检查清单(做完 deck 必过)
1. [ ] 浏览器直接打开 `index.html`(或主 HTML检查首页无破图、字体已加载
2. [ ] 按 → 键翻到每一页,没有空白页、没有布局错位
3. [ ] 按 P 键打印预览,每页恰好一张 A4或 1920×1080且无裁切
4. [ ] 随机选 3 页 Cmd+Shift+R 强刷localStorage 记忆正常工作
5. [ ] Playwright 批量截图(单页架构:遍历 `slides/*.html`;单文件架构:用 goTo 切换),人工肉眼过一遍
6. [ ] 搜一下 `TODO` / `placeholder` 残留,确认都清理了

View File

@@ -0,0 +1,313 @@
# Tweaks设计变体实时调参
Tweaks是这个skill里很核心的能力——让用户不改代码就能实时切换variations/调整参数。
**跨 agent 环境适配**:某些 design-agent 原生环境(如 Claude.ai Artifacts依赖 host 的 postMessage 把 tweak 值回写源码做持久化。本 skill 采用**纯前端 localStorage 方案**——效果一致(刷新保留状态),但持久化发生在浏览器 localStorage 而不是源码文件。这个方案在任何 agent 环境Claude Code / Codex / Cursor / Trae / etc.)都能工作。
## 何时加 Tweaks
- 用户明确要求"能调参"/"多个版本切换"
- 设计有多个variations需要对比时
- 用户没明说,但你主观判断**加几个有启发性的tweaks能帮用户看到可能性**
默认推荐:**每个设计都加2-3个tweaks**(颜色主题/字号/layout变体即使用户没要求——让用户看到可能性空间是设计服务的一部分。
## 实现方式(纯前端版)
### 基本结构
```jsx
const TWEAK_DEFAULTS = {
"primaryColor": "#D97757",
"fontSize": 16,
"density": "comfortable",
"dark": false
};
function useTweaks() {
const [tweaks, setTweaks] = React.useState(() => {
try {
const stored = localStorage.getItem('design-tweaks');
return stored ? { ...TWEAK_DEFAULTS, ...JSON.parse(stored) } : TWEAK_DEFAULTS;
} catch {
return TWEAK_DEFAULTS;
}
});
const update = (patch) => {
const next = { ...tweaks, ...patch };
setTweaks(next);
try {
localStorage.setItem('design-tweaks', JSON.stringify(next));
} catch {}
};
const reset = () => {
setTweaks(TWEAK_DEFAULTS);
try {
localStorage.removeItem('design-tweaks');
} catch {}
};
return { tweaks, update, reset };
}
```
### Tweaks面板UI
右下角浮动面板。可折叠:
```jsx
function TweaksPanel() {
const { tweaks, update, reset } = useTweaks();
const [open, setOpen] = React.useState(false);
return (
<div style={{
position: 'fixed',
bottom: 20,
right: 20,
zIndex: 9999,
}}>
{open ? (
<div style={{
background: 'white',
border: '1px solid #e5e5e5',
borderRadius: 12,
padding: 20,
boxShadow: '0 10px 40px rgba(0,0,0,0.12)',
width: 280,
fontFamily: 'system-ui',
fontSize: 13,
}}>
<div style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
}}>
<strong>Tweaks</strong>
<button onClick={() => setOpen(false)} style={{
border: 'none', background: 'none', cursor: 'pointer', fontSize: 16,
}}>×</button>
</div>
{/* 颜色 */}
<label style={{ display: 'block', marginBottom: 12 }}>
<div style={{ marginBottom: 4, color: '#666' }}>主色</div>
<input
type="color"
value={tweaks.primaryColor}
onChange={e => update({ primaryColor: e.target.value })}
style={{ width: '100%', height: 32 }}
/>
</label>
{/* 字号slider */}
<label style={{ display: 'block', marginBottom: 12 }}>
<div style={{ marginBottom: 4, color: '#666' }}>字号 ({tweaks.fontSize}px)</div>
<input
type="range"
min={12} max={24} step={1}
value={tweaks.fontSize}
onChange={e => update({ fontSize: +e.target.value })}
style={{ width: '100%' }}
/>
</label>
{/* 密度选项 */}
<label style={{ display: 'block', marginBottom: 12 }}>
<div style={{ marginBottom: 4, color: '#666' }}>密度</div>
<select
value={tweaks.density}
onChange={e => update({ density: e.target.value })}
style={{ width: '100%', padding: 6 }}
>
<option value="compact">紧凑</option>
<option value="comfortable">舒适</option>
<option value="spacious">宽松</option>
</select>
</label>
{/* 暗黑模式toggle */}
<label style={{
display: 'flex',
alignItems: 'center',
gap: 8,
marginBottom: 16,
}}>
<input
type="checkbox"
checked={tweaks.dark}
onChange={e => update({ dark: e.target.checked })}
/>
<span>暗黑模式</span>
</label>
<button onClick={reset} style={{
width: '100%',
padding: '8px 12px',
background: '#f5f5f5',
border: 'none',
borderRadius: 6,
cursor: 'pointer',
fontSize: 12,
}}>重置</button>
</div>
) : (
<button
onClick={() => setOpen(true)}
style={{
background: '#1A1A1A',
color: 'white',
border: 'none',
borderRadius: 999,
padding: '10px 16px',
fontSize: 12,
cursor: 'pointer',
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
}}
> Tweaks</button>
)}
</div>
);
}
```
### 应用Tweaks
在主组件里用Tweaks
```jsx
function App() {
const { tweaks } = useTweaks();
return (
<div style={{
'--primary': tweaks.primaryColor,
'--font-size': `${tweaks.fontSize}px`,
background: tweaks.dark ? '#0A0A0A' : '#FAFAFA',
color: tweaks.dark ? '#FAFAFA' : '#1A1A1A',
}}>
{/* 你的内容 */}
<TweaksPanel />
</div>
);
}
```
CSS里用变量
```css
button.cta {
background: var(--primary);
color: white;
font-size: var(--font-size);
}
```
## 典型 Tweak 选项
给不同类型的设计加什么tweaks
### 通用
- 主色color picker
- 字号slider 12-24px
- 字型selectdisplay font vs body font
- 暗黑模式toggle
### 幻灯片deck
- 主题light/dark/brand
- 背景样式solid/gradient/image
- 字体对比(更装饰 vs 更克制)
- 信息密度minimal/standard/dense
### 产品原型
- 布局变体layout A / B / C
- 交互速度animation speed 0.5x-2x
- 数据量mock数据条数 5/20/100
- 状态empty/loading/success/error
### 动画
- 速度0.5x-2x
- 循环once/loop/ping-pong
- Easinglinear/easeOut/spring
### Landing page
- Hero风格image/gradient/pattern/solid
- CTA文案几种变体
- 结构single column / two column / sidebar
## Tweaks设计原则
### 1. 有意义的选项,不是折腾人的
每个tweak必须展示**真实的设计选项**。别加那种谁都不会真切换的tweak比如border-radius 0-50px的slider——用户调完发现所有中间值都丑
好的tweak暴露**离散的、有思考的variations**
- "圆角风格":无圆角 / 微圆角 / 大圆角(三个选项)
- 不是:"圆角"0-50px slider
### 2. 少即是多
一个设计的Tweaks面板**最多5-6个**选项。再多就变成"配置页面"失去了快速探索variations的意义。
### 3. 默认值是完成设计
Tweaks是**锦上添花**。默认值必须本身就是一个完整、可发布的设计。用户关闭Tweaks面板后看到的就是产出。
### 4. 合理分组
选项多时分组显示:
```
---- 视觉 ----
主色 | 字号 | 暗黑模式
---- 布局 ----
密度 | 侧栏位置
---- 内容 ----
显示数据量 | 状态
```
## 向前兼容源码级持久化 host
如果你以后想把设计上传到支持源码级 tweaks如 Claude.ai Artifacts的环境也能跑保留 **EDITMODE 标记块**
```jsx
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"primaryColor": "#D97757",
"fontSize": 16,
"density": "comfortable",
"dark": false
}/*EDITMODE-END*/;
```
标记块在 localStorage 方案里**无作用**(只是个普通注释),但在支持源码回写的 host 里会被读取,实现源码级持久化。加上这个对当前环境无害,同时保持向前兼容。
## 常见问题
**Tweaks面板挡住设计内容**
→ 让它可关闭。默认关闭,显示一个小按钮,用户点了才展开。
**用户切换tweaks后还要重复设置**
→ 已经用localStorage。如果刷新后不持久检查localStorage是否可用无痕模式会失败要catch
**多个HTML页面想共享tweaks**
→ 给localStorage key加project name`design-tweaks-[projectName]`
**我想让tweak之间有联动关系**
→ 在`update`里加逻辑:
```jsx
const update = (patch) => {
let next = { ...tweaks, ...patch };
// 联动选dark mode时自动切换字体配色
if (patch.dark === true && !patch.textColor) {
next.textColor = '#F0EEE6';
}
setTweaks(next);
localStorage.setItem(...);
};
```

View File

@@ -0,0 +1,189 @@
# Verification输出验证流程
一些 design-agent 原生环境(如 Claude.ai Artifacts有内置的 `fork_verifier_agent` 起 subagent 用 iframe 截图检查。大部分 agent 环境Claude Code / Codex / Cursor / Trae / 等)里没有这个内置能力——用 Playwright 手动做就能覆盖相同的验证场景。
## 验证清单
每次产出HTML后按这个清单做一遍
### 1. 浏览器渲染检查(必做)
最基础:**HTML能不能打开**在macOS上
```bash
open -a "Google Chrome" "/path/to/your/design.html"
```
或者用Playwright截图下一节
### 2. 控制台错误检查
HTML文件里最常见的问题是JS报错导致白屏。用Playwright跑一遍
```bash
python ~/.claude/skills/claude-design/scripts/verify.py path/to/design.html
```
这个脚本会:
1. 用headless chromium打开HTML
2. 截图保存到项目目录
3. 抓取控制台错误
4. 报告status
详见`scripts/verify.py`
### 3. 多视口检查
如果是响应式设计抓多个viewport
```bash
python verify.py design.html --viewports 1920x1080,1440x900,768x1024,375x667
```
### 4. 交互检查
Tweaks、动画、按钮切换——默认的静态截图看不到。**建议让用户自己开浏览器点一遍**或者用Playwright录屏
```python
page.video.record('interaction.mp4')
```
### 5. 幻灯片逐页检查
Deck类HTML一张张截
```bash
python verify.py deck.html --slides 10 # 截前10张
```
生成 `deck-slide-01.png``deck-slide-02.png`... 方便快速浏览。
## Playwright Setup
首次使用需要:
```bash
# 如果还没装
npm install -g playwright
npx playwright install chromium
# 或者Python版
pip install playwright
playwright install chromium
```
如果用户已经全局安装 Playwright直接用即可。
## 截图最佳实践
### 截完整页面
```python
page.screenshot(path='full.png', full_page=True)
```
### 截viewport
```python
page.screenshot(path='viewport.png') # 默认只截可见区域
```
### 截特定元素
```python
element = page.query_selector('.hero-section')
element.screenshot(path='hero.png')
```
### 高清截图
```python
page = browser.new_page(device_scale_factor=2) # retina
```
### 等动画结束再截
```python
page.wait_for_timeout(2000) # 等2秒让动画settle
page.screenshot(...)
```
## 把截图发给用户
### 本地截图直接打开
```bash
open screenshot.png
```
用户会在自己的 Preview/Figma/VSCode/浏览器 里看。
### 上传图床分享链接
如果需要给远程协作者看(比如 Slack/飞书/微信),让用户用自己的图床工具或 MCP 上传:
```bash
python ~/Documents/写作/tools/upload_image.py screenshot.png
```
返回ImgBB的永久链接可以粘贴到任何地方。
## 验证出错时
### 页面白屏
控制台一定有错。先检查:
1. React+Babel script tag的integrity hash对不对`react-setup.md`
2. 是不是`const styles = {...}`命名冲突
3. 跨文件的组件有没有export到`window`
4. JSX语法错误babel.min.js不报错换babel.js非压缩版
### 动画卡
- 用Chrome DevTools Performance tab录一段
- 找layout thrashing频繁的reflow
- 动效优先用`transform``opacity`GPU加速
### 字体不对
- 检查`@font-face`的url是否可访问
- 检查fallback字体
- 中文字体加载慢先显示fallback加载完再切换
### 布局错位
- 检查`box-sizing: border-box`是否全局应用
- 检查`* margin: 0; padding: 0`reset
- Chrome DevTools里打开gridlines看实际布局
## 验证=设计师的第二双眼
**永远要自己过一遍**。AI写代码时经常出现
- 看起来对但interaction有bug
- 静态截图好但scroll时错位
- 宽屏好看但窄屏崩
- Dark mode忘了测
- Tweaks切换后某些组件没响应
**最后1分钟的验证可以省1小时的返工**
## 常用验证脚本命令
```bash
# 基础:打开+截图+抓错
python verify.py design.html
# 多viewport
python verify.py design.html --viewports 1920x1080,375x667
# 多slide
python verify.py deck.html --slides 10
# 输出到指定目录
python verify.py design.html --output ./screenshots/
# headless=false打开真实浏览器给你看
python verify.py design.html --show
```

View File

@@ -0,0 +1,209 @@
# Video ExportHTML 动画导出为 MP4/GIF
动画 HTML 完成后,用户常想「能导出视频吗」。这份指南给出完整流程。
## 何时导出
**导出时机**
- 动画完整跑通、视觉验证过Playwright 截图确认各时间点状态正确)
- 用户在浏览器里看过至少一次,表示效果 OK
- **不要**在动画 bug 没修完的阶段导出——导出到视频后改起来更贵
**用户可能说的触发语**
- 「能导出成视频吗」
- 「转成 MP4」
- 「做成 GIF」
- 「60fps」
## 产出规格
默认一次给三种格式,让用户选:
| 格式 | 规格 | 适合场景 | 典型大小30s |
|---|---|---|---|
| MP4 25fps | 1920×1080 · H.264 · CRF 18 | 公众号嵌入、视频号、YouTube | 1-2 MB |
| MP4 60fps | 1920×1080 · minterpolate 插帧 · H.264 · CRF 18 | 高帧率展示、B站、作品集 | 1.5-3 MB |
| GIF | 960×540 · 15fps · palette 优化 | Twitter/X、README、Slack 预览 | 2-4 MB |
## 工具链
两个脚本在 `scripts/`
### 1. `render-video.js` — HTML → MP4
录一个 25fps 的 MP4 基础版本。依赖全局 playwright。
```bash
NODE_PATH=$(npm root -g) node /path/to/claude-design/scripts/render-video.js <html文件>
```
可选参数:
- `--duration=30` 动画时长(秒)
- `--width=1920 --height=1080` 分辨率
- `--trim=2.2` 从视频开头裁掉的秒数(去掉 reload + 字体加载时间)
- `--fontwait=1.5` 字体加载等待时间(秒),字体多时调高
输出:与 HTML 同目录,同名 `.mp4`
### 2. `add-music.sh` — MP4 + BGM → MP4
给无声 MP4 混入背景音乐按场景mood从内置 BGM 库里选,也可自带音频。自动匹配时长、加淡入淡出。
```bash
bash add-music.sh <input.mp4> [--mood=<name>] [--music=<path>] [--out=<path>]
```
**内置 BGM 库**(在 `assets/bgm-<mood>.mp3`
| `--mood=` | 风格 | 适配场景 |
|-----------|------|---------|
| `tech`(默认) | Apple Silicon / 苹果发布会,极简合成器+钢琴 | 产品发布、AI工具、Skill 宣传 |
| `ad` | upbeat 现代电子,有 build + drop | 社交媒体广告、产品预告、促销片 |
| `educational` | 温暖明亮、轻吉他/电钢琴inviting | 科普、教程介绍、课程预告 |
| `educational-alt` | 同类备选,换一首试试 | 同上 |
| `tutorial` | lo-fi 环境音,几乎无存在感 | 软件演示、编程教程、长演示 |
| `tutorial-alt` | 同类备选 | 同上 |
**行为**
- 音乐按视频时长裁剪
- 0.3s 淡入 + 1s 淡出(避免硬切)
- 视频流 `-c:v copy` 不重编码,音频 AAC 192k
- `--music=<path>` 优先级高于 `--mood`,可以直接指定任意外部音频
- 传错 mood 名会列出所有可用选项,不会静默失败
**典型流水线**(动画导出三件套 + 配乐):
```bash
node render-video.js animation.html # 录屏
bash convert-formats.sh animation.mp4 # 派生 60fps + GIF
bash add-music.sh animation-60fps.mp4 # 加默认 tech BGM
# 或针对不同场景:
bash add-music.sh tutorial-demo.mp4 --mood=tutorial
bash add-music.sh product-promo.mp4 --mood=ad --out=promo-final.mp4
```
### 3. `convert-formats.sh` — MP4 → 60fps MP4 + GIF
从已有 MP4 生成 60fps 版本和 GIF。
```bash
bash /path/to/claude-design/scripts/convert-formats.sh <input.mp4> [gif_width] [--minterpolate]
```
输出(与输入同目录):
- `<name>-60fps.mp4` — 默认用 `fps=60` 帧复制(兼容性广);加 `--minterpolate` 启用高质量插帧
- `<name>.gif` — palette 优化的 GIF默认 960 宽,可改)
**60fps 模式选择**
| 模式 | 命令 | 兼容性 | 使用场景 |
|---|---|---|---|
| 帧复制(默认)| `convert-formats.sh in.mp4` | QuickTime/Safari/Chrome/VLC 全通 | 通用交付、上传平台、社交媒体 |
| minterpolate 插帧 | `convert-formats.sh in.mp4 --minterpolate` | macOS QuickTime/Safari 可能拒打 | B站等需要真插帧的展示场景**交付前必须本地测**目标播放器 |
为什么默认改成帧复制minterpolate 输出的 H.264 elementary stream 有 known compat bug——之前默认 minterpolate 时多次踩到「macOS QuickTime 打不开」的问题。详见 `animation-pitfalls.md` §14。
`gif_width` 参数:
- 960默认—— 社交平台通用
- 1280 —— 更清晰但文件更大
- 600 —— Twitter/X 优先加载
## 完整流程(标准推荐)
用户说「导出视频」后:
```bash
cd <项目目录>
# 假设 $SKILL 指向本 skill 的根目录(自行按安装位置替换)
# 1. 录 25fps 基础 MP4
NODE_PATH=$(npm root -g) node "$SKILL/scripts/render-video.js" my-animation.html
# 2. 派生 60fps MP4 和 GIF
bash "$SKILL/scripts/convert-formats.sh" my-animation.mp4
# 产出清单:
# my-animation.mp4 (25fps · 1-2 MB)
# my-animation-60fps.mp4 (60fps · 1.5-3 MB)
# my-animation.gif (15fps · 2-4 MB)
```
## 技术细节(排错用)
### Playwright recordVideo 的坑
- 帧率固定 25fps无法直接录 60fpsChromium headless 的 compositor 上限)
- 从 context 创建就开始录,必须用 `trim` 裁掉前面的加载时间
- 默认 webm 格式,需要 ffmpeg 转 H.264 MP4 才能通用播放
`render-video.js` 已处理以上问题。
### ffmpeg minterpolate 参数
当前配置:`minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1`
- `mi_mode=mci` — motion compensation interpolation运动补偿
- `mc_mode=aobmc` — adaptive overlapped block motion compensation
- `me_mode=bidir` — 双向运动估计
- `vsbmc=1` — 可变 size block motion compensation
对 CSS **transform 动画**translate/scale/rotate效果好。
对**纯 fade** 可能产生轻微 ghosting——如果用户嫌弃退化为简单帧复制
```bash
ffmpeg -i input.mp4 -r 60 -c:v libx264 ... output.mp4
```
### GIF palette 为何要两阶段
GIF 只能 256 色。一次 pass 的 GIF 会把全动画色彩压到 256 色通用 palette对米色底+橙色这种细腻配色会糊。
两阶段:
1. `palettegen=stats_mode=diff` —— 先扫描全片,生成**针对此动画的 optimal palette**
2. `paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle` —— 用这个 palette 编码rectangle diff 只更新变化区域,大幅减小文件
对 fade 过渡用 `dither=bayer``none` 更平滑,但文件大一点。
## Pre-flight check导出前
导出前 30 秒自检:
- [ ] HTML 在浏览器里完整跑过一遍,无控制台错误
- [ ] 动画第 0 帧是完整初始状态(不是空白加载中)
- [ ] 动画最后一帧是稳定的收尾状态(不是半截)
- [ ] 字体/图片/emoji 全部正常渲染(参考 `animation-pitfalls.md`
- [ ] Duration 参数与 HTML 里的实际动画时长匹配
- [ ] HTML 中 Stage 检测 `window.__recording` 强制 loop=false手写 Stage 必查;用 `assets/animations.jsx` 自带)
- [ ] 结尾 Sprite 的 `fadeOut={0}`(视频末帧不淡出)
- [ ] 含「Created by Huashu-Design」水印仅动画场景必加第三方品牌作品加「非官方出品 · 」前缀。详见 SKILL.md §「Skill 推广水印」)
## 交付时附带的说明
导出完成后给用户的标准说明格式:
```
**完整交付**
| 文件 | 格式 | 规格 | 大小 |
|---|---|---|---|
| foo.mp4 | MP4 | 1920×1080 · 25fps · H.264 | X MB |
| foo-60fps.mp4 | MP4 | 1920×1080 · 60fps运动插帧· H.264 | X MB |
| foo.gif | GIF | 960×540 · 15fps · palette 优化 | X MB |
**说明**
- 60fps 用 minterpolate 做运动估计插帧transform 动画效果好
- GIF 用 palette 优化30s 动画可压到 3MB 左右
要换尺寸或帧率说一声。
```
## 常见用户追加需求
| 用户说 | 应对 |
|---|---|
| 「太大了」 | MP4提高 CRF 到 23-28GIF降分辨率到 600 或 fps 到 10 |
| 「GIF 太糊」 | 提高 `gif_width` 到 1280或者建议用 MP4 代替(微信朋友圈也支持) |
| 「要竖屏 9:16」 | 改 HTML 源的 `--width=1080 --height=1920`,重新录 |
| 「加水印」 | ffmpeg 加 `-vf "drawtext=..."``overlay=` 一个 PNG |
| 「要透明背景」 | MP4 不支持 alpha用 WebM VP9 + alpha 或 APNG |
| 「要无损」 | CRF 改 0 + preset veryslow文件会大 10 倍) |

View File

@@ -0,0 +1,397 @@
# Voiceover Pipeline · 解说驱动动画
> 把动画从「无声画面 + 后期配音」升级为「**先有解说词,再按音频实测时长驱动画面**」的工作流。
> 适用5-20 分钟概念解说视频、教程视频、长篇知识科普。
>
> 配套 `references/animation-best-practices.md` 使用——本文件管 **怎么把解说和画面对上**
> animation-best-practices 管 **每一帧画面怎么动**。
---
## 🛑 铁律 · 在写一行代码之前必读
> **强调多少遍都不够:解说动画的失败模式 #1 是做成了带配音的 PowerPoint。**
### 第一条 · 整片是一个连续的运动叙事,不是一组独立场景
PowerPoint 是 7 张幻灯片。我们做的是 **1 段持续 X 分钟的电影**
**身份切换**
- ❌ 你不是「在做 7 个 scene 的内容」
- ✅ 你是「在屏幕上让一个或几个 hero element 演 X 分钟的戏」
**视觉骨架 = 一个或几个贯穿全片的 hero element**
- 它从 t=0 出现,到结束才离场
- 每个 cue 是它的**状态变化**(位置 / 大小 / 颜色 / 透视 / 形态),不是「换一个新元素」
- scene 边界在剧本里有,**在画面里不应该有**——观众看不出"这是第 3 个 scene",只看到一段连续的运动
**反例(本 skill v1 实战踩坑 · 2026-05-10**
- 7 个 `<Scene>` 各自独立 layoutscene 切换 = 整页 opacity 1→0 切到下一页
- 每个 cue = `opacity: p, transform: translateY((1-p)*30px)`fade-up 单调使用)
- 结果:观众看完第一反应「像一页页 keynote」整片质感归零
**正确模式**
- 选定 1-2 个 hero element如本文章 demo 应选「md」「html」两个字符作为骨架
- 这两个字符**从片头到片尾**一直在屏幕上
- 每段「scene」实际是 hero element 的一次状态变化
- opening两字符在屏幕中央对峙
- md-sidemd 变大变粗占据画面html 退到角落小字;数据围绕 md 涌入
- html-sidehtml 反转为主角md 退到角落
- the-real-question两字符回到中央但中间出现「≠」分隔
- the-split两字符向两侧推开中间空白展开
- activity-proof两字符在 timeline 上交替闪烁
- closing两字符落地为最终答案位置
- 这样整片是「md 和 html 在屏幕上演了 X 分钟」,不是 7 张独立 PPT
**最小实现骨架**(直接抄改):
```jsx
// ── Step 1: 定义 hero 在每个 scene 的目标状态(位置/大小/不透明度)──
const HERO_KEYS = {
opening: { md: { x: 50, y: 35, scale: 1.0, opacity: 1 }, html: { x: 50, y: 65, scale: 1.0, opacity: 1 } },
'md-side': { md: { x: 78, y: 50, scale: 1.6, opacity: 1 }, html: { x: 92, y: 8, scale: 0.25, opacity: 0.4 } },
'html-side':{ md: { x: 8, y: 8, scale: 0.25, opacity: 0.4 }, html: { x: 22, y: 50, scale: 1.6, opacity: 1 } },
// ... 每段一个 entry连贯的运动从前一段的 final → 本段的 from
};
// ── Step 2: easing + lerp 工具 ──
const expoOut = t => t === 1 ? 1 : 1 - Math.pow(2, -10 * t);
const lerp = (a, b, t) => a + (b - a) * t;
const lerpPos = (from, to, t) => ({
x: lerp(from.x, to.x, t), y: lerp(from.y, to.y, t),
scale: lerp(from.scale, to.scale, t),
opacity: lerp(from.opacity ?? 1, to.opacity ?? 1, t),
});
// ── Step 3: HeroAnchor 组件 —— 直接挂在 <NarrationStage> 子级,不放进 <Scene> ──
const HeroAnchor = () => {
const { time, scene, timeline } = useNarration();
if (!scene) return null;
const idx = timeline.scenes.findIndex(s => s.id === scene.id);
const prevId = idx > 0 ? timeline.scenes[idx - 1].id : scene.id;
const from = HERO_KEYS[prevId];
const to = HERO_KEYS[scene.id];
// 段内前 ~45% 时间用于从 prev 状态 morph 到本段状态,剩余 hold
const transitionDur = Math.min(2.0, scene.duration * 0.45);
const t = expoOut(Math.min(1, (time - scene.start) / transitionDur));
const md = lerpPos(from.md, to.md, t);
const html = lerpPos(from.html, to.html, t);
// 加 subtle breathing 让任意一帧都有运动(对应铁律第三条)
const breath = 1 + Math.sin(time * 0.6) * 0.012;
const renderHero = (label, pos, color) => (
<div style={{
position: 'absolute', left: `${pos.x}%`, top: `${pos.y}%`,
transform: `translate(-50%, -50%) scale(${pos.scale * breath})`,
opacity: pos.opacity, color, fontSize: 360, fontWeight: 800,
lineHeight: 1, willChange: 'transform, opacity', pointerEvents: 'none',
}}>{label}</div>
);
return <>
{renderHero('md', md, '#1B4965')}
{renderHero('html', html, '#C04A1A')}
</>;
};
// ── Step 4: 主组件 —— hero 在 NarrationStage 子级scene 内辅助元素另外管 ──
const App = () => (
<NarrationStage timeline={TIMELINE} audioSrc="_narration/voiceover.mp3" width={1920} height={1080}>
<HeroAnchor /> {/* ← 跨 scene 持续存在,整片视觉骨架 */}
{/* scene 内辅助元素用 useSceneFade 控制软淡入淡出,不要硬切 */}
<MdSideAux />
<HtmlSideAux />
{/* ... */}
</NarrationStage>
);
```
**完整可运行参考**`demos/md-html-narration/md-html-demo.html`3 分 21 秒7 段21 cue已实战验证
### 第二条 · 场景之间不能「硬切」
| 错误模式PowerPoint slop | 正确模式(电影感) |
|---|---|
| scene A 整体 `opacity 1→0` 同时 scene B `opacity 0→1` | scene A 的核心元素 **morph 进** B位置/大小/颜色平滑变换) |
| 每个 scene 独立 layout元素出现/消失 | 元素在屏幕上**持续存在**,只是位置和形态在变 |
| `keepMounted=false`scene 切换瞬间组件被卸载 | hero 用 `keepMounted=true`,跨 scene 共享 DOM 节点 |
| 字幕条/数据卡片各自 fade in fade out | 字幕条作为画面唯一的"非 hero" 入场hold 后**配合 hero 的运动一起退出** |
实现层面:
- **共享元素跨 scene** → 把 hero 提到 `<NarrationStage>` 直接子级,**不放在任何 `<Scene>` 里**
-`useNarration()` hook 在 hero 里读 `time``scene``isCueTriggered`,自己根据当前时间决定形态
- `<Scene>` 只用来管那些只在该段出现的辅助元素(数据卡、引用块等),并且**这些辅助元素也不要硬切**——出场用 expoOut + stagger退场用 fade overlap 跟下一段叠
### 第三条 · 每一帧画面都必须有运动
**自检方法**:在录制中**任意截一帧**(不是 cue 触发那一秒)。
- 如果画面看起来「**完全静止**」→ 错。回去加底层运动background drift / hero subtle scale / camera pan / parallax
- 永远有一个**底层运动**在跑(即使不是焦点):
- hero element 的 `scale: 1 ↔ 1.02` 5 秒呼吸循环
- 背景 `translateX: 0 ↔ -20px` 缓慢漂移
- 数据卡片入场后保留 `translateY` 微抖Perlin noise
- 一个完全静止的画面 = PowerPoint slop
### 第四条 · Easing / Stagger / Hold 是底线
| 项 | 必须 | 禁止 |
|---|---|---|
| Easing | `expoOut` 主轴(`cubic-bezier(0.16, 1, 0.3, 1)``overshoot` 强调,`spring` 落位 | `linear``ease`、CSS 默认 |
| 多元素入场 | 30ms stagger每个晚 30ms 进) | 一刀切全部出现 |
| 关键 cue 前 | hold 0.3-0.5s 让观众"看见"(前一段元素先静止 0.3s,再触发 cue | 一段说完无缝切下一段 |
| 收尾 | 戛然而止,最后一帧 hold 1s | fade to black |
详细规则参考 `animation-best-practices.md` 的 §1-§4。
### 自检 · 第一观众反应
做完拿给一个没看过的人看(或自己 24 小时后再看),**他们的第一反应**是什么?
| 反应 | 评级 | 行动 |
|---|---|---|
| 「这是带配音的 PPT」 | 失败 | 回去重做 |
| 「画面跟着声音在切换」 | 不及格 | 缺连续叙事hero element 不存在或没贯穿 |
| 「这个东西在动」 | 合格 | 但没记忆点 |
| 「我想看完」 | 良 | 节奏对了 |
| 「这一段我想截图」 | great | 你做到了 |
---
## 工作流(高层)
```
┌──────────────────────────┐
│ 解说稿 .md## scene + │
│ [[cue:xx]] 标关键句) │
└──────────────┬───────────┘
narrate-pipeline.mjs
┌──────────────────────────────┐
│ voiceover.mp3 (拼接的整段) │
│ timeline.json (实测时长) │
└──────────────┬───────────────┘
┌────────────┴────────────┐
▼ ▼
┌─────────────────┐ ┌──────────────────┐
│ HTML 动画 │ │ 录制 MP4 + 混音 │
│ (NarrationStage)│ │ render-narration │
│ 实播带 audio 同步│ │ → 最终发布 MP4 │
└─────────────────┘ └──────────────────┘
交付形态 1 交付形态 2
```
## 解说稿格式
放在项目目录下任意位置,文件名建议 `script.md`
```markdown
---
title: 什么是 LLM
voice: S_JSdgdWk22 # 可选,覆盖 .env 默认音色
speed: 1.0 # 可选0.5-2.0
gap: 0.4 # 段间静音秒数,默认 0.3
---
## intro
大家好,今天我们 5 分钟讲清楚 LLM 是什么。
## what-is
LLM 全称 Large Language Model[[cue:bigmodel]]它是一个有几千亿参数的神经网络。
本质是一个文字接龙的预测器。
## demo
比如你输入「今天天气」,[[cue:input]]模型会预测下一个字最可能是什么。
[[cue:predict]]也许是「真好」,也许是「不错」。
```
**规则**
- 段标题 `## scene-id` 是英文/数字 + 连字符(如 `## what-is``## scene-1`
- `[[cue:xx]]` 标在**关键句中间**——脚本运行时会在该位置切割文本cue 之后那一刻就是画面的触发点
- cue id 在动画 HTML 里用 `<Cue id="xx">` 监听
- 写解说时**关注节奏 + 短句**,长句 TTS 出来会平淡
## timeline.json schema
```ts
{
title: string,
voice: string | null,
speed: number,
gap: number,
totalDuration: number, // 整段 voiceover.mp3 的实测秒数
voiceover: 'voiceover.mp3', // 相对 timeline.json 的路径
scenes: [
{
id: string,
start: number, // 该段在整段音频里的开始时间
end: number,
duration: number,
audio: 'audio/<id>.mp3', // 该段单独音频(合并前的子段已 concat
text: string, // 已剥离 [[cue:xx]] 标记的整段文本
// chunks 是字幕显示的源——每个 chunk 是被 cue 切开的子段,含 TTS 实测时间窗
chunks: [
{
text: string, // 子段文本
start: number, // 段内相对时间
end: number,
absoluteStart: number, // 整轨绝对时间(对齐 voiceover.mp3
absoluteEnd: number,
}
],
cues: [
{
id: string,
offset: number, // 段内相对时间
absoluteTime: number, // 整段时间轴上的绝对时间
}
]
}
]
}
```
`absoluteTime``absoluteStart/End` 都是**真实测出来的**——pipeline 把段内文本按 cue 切成子段分别 TTS时间 = 累加前面子段的实测时长。**不是按字符数线性估算的近似值**。
## 字幕Subtitles
> **字幕是默认带的**——长解说视频没字幕留存率会显著下降。NarrationStage 提供 `<Subtitles />` 开箱即用。
### 用法(一行)
```jsx
const { NarrationStage, Subtitles } = NarrationStageLib;
<NarrationStage timeline={TIMELINE} audioSrc="...">
{/* 你的 hero / scene 内容 */}
<Subtitles /> {/* ← 自动从 timeline.scenes[].chunks 取活动文本 */}
</NarrationStage>
```
### 视觉规则B 站风 · 反 PowerPoint
| 项 | 规则 | 反例 |
|---|---|---|
| 背景 | **无背景**(不要黑色横条不要 backdrop-blur| 半透明黑底 + blur = 字幕条压住画面 = PPT 感 |
| 字色 | **浅底用深墨 `#1a1a1a` + 白光晕**;深底用白字 + 黑光晕 | 浅底白字+黑描边 = 字糊 |
| 字号 | 32px1080p 视频)| <24px 看不清>40px 抢主视觉 |
| 字体 | `PingFang SC` / `Noto Sans SC`无衬线B 站标准)| 衬线字体 = 像电影字幕 |
| 位置 | bottom: 90px不贴边| 贴底边显得廉价 |
| 单行长度 | **≤ 12-13 字**(中英混合时英文按 0.5 字算)| >15 字一行手机端读不完 |
| 切句规则 | **绝不跨句号截断**:先按 `。!?` 切句,每句再按 `,、;:` 合并到 ≤maxLen | 按字数硬切,把「这是好的」切成「这是好」+「的」 |
`<Subtitles />` 默认按以上规则跑,不需要传 props。深底场景`<Subtitles color="#fff" haloColor="rgba(0,0,0,0.85)" />`
### 切句算法(已在 narration_stage.jsx 内置)
```js
splitChunkToLines(text, maxLen = 13)
// 1. 强标点切句(。!?\n
// 2. 每句 ≤ maxLen 直接保留
// 3. 否则按弱标点(,、;:)切片,合并到 ≤ maxLen
// 4. 兜底硬切(罕见)
// 中英混合:英文/数字按 0.5 字算视觉宽度
```
如果 chunk 切完后某行明显太长或太短,**改解说稿里 cue 位置**cue 把段切得更细),不要在前端调切句逻辑。
## NarrationStage API
```jsx
import 'assets/narration_stage.jsx';
const { NarrationStage, Scene, Cue, useNarration } = NarrationStageLib;
<NarrationStage
timeline={TIMELINE} // timeline.json 内容
audioSrc="_narration/voiceover.mp3" // 相对当前 HTML 的路径
width={1920} height={1080}
background="#f5f1e8"
controls={true} // 实播时显示底部播放条
>
{/* hero element跨 scene 持续存在 —— 直接放在 NarrationStage 子级 */}
<HeroAnchor />
{/* scene 内辅助元素:只在该段出现 */}
<Scene id="intro">
<Cue id="bigmodel">{(triggered, progress) => (
<SomeElement style={{ opacity: progress }} />
)}</Cue>
</Scene>
</NarrationStage>
```
**Hooks**
- `useNarration()` 返回 `{ time, scene, sceneTime, isCueTriggered, cueProgress }`
- 在自定义组件里直接读,不需要传 props
**Scene 组件**
- 默认只在 `scene.id === id` 时挂载
-`keepMounted` 持续挂载(跨 scene 动画连续时用)
**Cue 组件**
- children 必须是 `(triggered, progress) => ReactNode`
- progress 是 cue 触发后 0→1 的渐进值(默认 0.6s ramp
## 时间源(双轨)
NarrationStage 自动检测 `window.__recording`
- **实播模式**(默认):跟随 audio 元素的 currentTime用户暂停/拖动 seek 都能同步
- **录视频模式**render-video.js 设置 `window.__recording = true`rAF wall-clock 自驱动从 0 开始,暴露 `window.__seek(t)` 给 render-video.js 复位
## 三个脚本
| 脚本 | 输入 | 输出 |
|---|---|---|
| `scripts/tts-doubao.mjs` | 单段文本 | 单个 mp3 + 实测时长 |
| `scripts/narrate-pipeline.mjs` | 解说稿 .md | voiceover.mp3 + timeline.json |
| `scripts/mix-voiceover.sh` | 视频 + voiceover.mp3 [+ BGM] | 带音频的 MP4 |
| `scripts/render-narration.sh` | 解说 HTML + timeline.json | 最终 MP4录制 + 混音一条龙)|
## .env 配置
skill 根目录下 `.env`(已 gitignore
```
DOUBAO_TTS_API_KEY=<your_key>
DOUBAO_TTS_VOICE_ID=<your_clone_voice_id>
DOUBAO_TTS_CLUSTER=volcano_icl
DOUBAO_TTS_ENDPOINT=https://openspeech.bytedance.com/api/v1/tts
```
参考 `.env.example` 模板。豆包语音克隆音色 ID 在火山引擎控制台获取。
## 标准工作流10 步)
1. **写解说稿**:解说稿是源代码。先把整段口播写完整,标段标题 `## scene-id`,关键句前加 `[[cue:xx]]`
2. **跑 narrate-pipeline**`node scripts/narrate-pipeline.mjs --script script.md --out-dir _narration`
3. **听整段 voiceover.mp3**:节奏不对回去改稿。**这一步决定整片质量上限**
4. **🛑 设计前先回答铁律**hero element 是什么?它在每段是什么状态?跨场景怎么 morph答不上不要写代码
5. **写动画 HTML**:用 NarrationStage + 一个或几个 hero element 跨 scene 演戏
6. **实播预览**:浏览器打开 HTML点 ▶ Play听画面+解说同步
7. **第一观众自检**:用上面「自检 · 第一观众反应」表打分。失败回到 Step 4 重做
8. **录视频**`bash scripts/render-narration.sh demo.html --timeline=_narration/timeline.json`(自动录无声 MP4 + 混入 voiceover
9. **可选 BGM**:在 render-narration 加 `--bgm-mood=educational`(或 tech / tutorial 等)
10. **交付**:浏览器 HTML实时演示用+ 最终 MP4发布用
## 异常处理
| 问题 | 解决 |
|---|---|
| TTS API 报错 | 检查 .env 里 `DOUBAO_TTS_API_KEY` 是否正确 |
| 某段音频明显比脚本长/短 | 该段文本里有奇怪标点或 emojiTTS 解析异常 → 改稿 |
| cue absoluteTime 不准 | 段内子段拼接时 ffmpeg 有问题 → 检查 mp3 编码一致性 |
| 录视频结果有黑屏 | render-video.js 没拿到 `window.__ready` 信号 → 检查 NarrationStage 是否正常挂载 |
| 录视频画面卡顿 | 动画里有重 layout大量 box-shadow / blur→ 简化或预合成 |
| 实播音画不同步 | audio 元素加载延迟 → 加 `preload="auto"` 或本地预加载 |
## 何时不用这套 pipeline
- **<60s 短动画**直接做无声动画 + 后期配音add-music.sh + 一段单独 TTS即可不需要 timeline 驱动
- ** BGM 视频** `add-music.sh` 加预设 BGM
- **真人录音替换 TTS** `voiceover.mp3` 替换成真人录音timeline 自己手写或用 ffprobe 测段时长 + 工具脚本生成 流程其余部分通用
---
**最后一次提醒**写代码前回到铁律。**别做带配音的 PowerPoint**。

View File

@@ -0,0 +1,216 @@
# Workflow从接到任务到交付
你是用户的junior designer。用户是manager。按这个流程工作能产出好设计的概率会显著提升。
## 问问题的艺术
大多数情况下开工前要问至少10个问题。不是走过场是真的要把需求摸清。
**什么时候必须问**新任务、模糊任务、没有design context、用户只说了一句模糊的要求。
**什么时候可以不问**小修小补、follow-up任务、用户已经给了明确PRD+截图+上下文。
**怎么问**:大部分 agent 环境没有结构化问题 UI在对话里用 markdown 清单问即可。**一次性把问题列完让用户批量答**,不要一来一回一个个问——那会浪费用户时间、打断用户思路。
## 必问清单
每个设计任务都必须问清这5类问题
### 1. Design Context最重要
- 有没有现成的design system、UI kit、组件库在哪
- 有没有品牌指南、色彩规范、字体规范?
- 有没有可以参考的现有产品/页面截图?
- 有没有codebase可以读
**如果用户说"没有"**
- 帮他找——翻项目目录、看有没有参考品牌
- 还没有?明确说:"我会基于通用直觉做,但这通常做不出符合你品牌的作品。你考虑下是否先提供一些参考?"
- 实在要做,就按`references/design-context.md`的fallback策略办
### 2. Variations维度
- 想要几种variations推荐3+
- 在哪些维度上变?视觉/交互/色彩/布局/文案/动画?
- 希望variations都"接近预期"还是"一张地图,从保守到疯狂"
### 3. Fidelity和Scope
- 多高保真?线框图 / 半成品 / 真实data的full hi-fi
- 覆盖多少flow一屏 / 一个flow / 整个产品?
- 有没有具体的「必须包含」元素?
### 4. Tweaks
- 希望能实时调整哪些参数?(颜色/字号/间距/layout/文案/feature flag
- 用户自己要不要在做完后继续调?
### 5. 问题专属至少4个
针对具体任务问4+个细节。例如:
**做landing page**
- 目标转化动作是什么?
- 主要受众?
- 竞品参考?
- 文案谁提供?
**做iOS App onboarding**
- 几步?
- 需要用户做什么?
- 跳过路径?
- 目标留存率?
**做动画**
- 时长?
- 最终用途(视频素材/官网/社交)?
- 节奏(快/慢/分段)?
- 必须出现的关键帧?
## 问题模板示例
遇到新任务时,可以抄这个结构在对话里问:
```markdown
开始前想跟你对齐几个问题,一次列齐你批量回答就行:
**Design Context**
1. 有设计系统/UI kit/品牌规范吗?如果有在哪?
2. 有可以参考的现有产品或竞品截图吗?
3. 项目里有codebase可以读吗
**Variations**
4. 想要几种variations在哪些维度上变视觉/交互/色彩/...
5. 希望都是"接近答案"还是从保守到疯狂的一张地图?
**Fidelity**
6. 保真度:线框 / 半成品 / 带真数据full hi-fi
7. Scope一屏 / 一整个flow / 整个产品?
**Tweaks**
8. 希望做完后能实时调哪些参数?
**具体任务**
9. [任务专属问题1]
10. [任务专属问题2]
...
```
## Junior Designer模式
这是整个workflow最重要的环节。**不要接到任务就闷头冲**。步骤:
### Pass 1Assumptions + Placeholders5-15分钟
HTML文件头部先写你的**assumptions+reasoning comments**像junior给manager汇报
```html
<!--
我的假设:
- 这是给XX受众看的
- 整体tone我理解为XX基于用户说的"专业但不严肃"
- 主要flow是A→B→C
- 色彩我想用品牌蓝+暖灰不确定你想不想要accent色
未解的问题:
- 第3步的数据从哪里来先用placeholder
- 背景图用抽象几何还是真照片?先占位
如果你看到这里觉得方向不对,现在是成本最低的时候改。
-->
<!-- 然后是带placeholder的结构 -->
<section class="hero">
<h1>[主标题位 - 等用户提供]</h1>
<p>[副标题位]</p>
<div class="cta-placeholder">[CTA按钮]</div>
</section>
```
**保存 → show用户 → 等反馈再走下一步**
### Pass 2真实组件+Variations主力工作量
用户批准方向后,开始填充。这时:
- 写React组件替换placeholder
- 做variations用design_canvas或Tweaks
- 如果是幻灯片/动画用starter components起手
**做到一半再show一次**——不要等全做完。设计方向错了晚show等于白做。
### Pass 3细节打磨
用户满意整体后,打磨:
- 字号/间距/对比度微调
- 动画timing
- 边界case
- Tweaks面板完善
### Pass 4验证+交付
- 用Playwright截图`references/verification.md`
- 打开浏览器肉眼确认
- 总结**极简**只说caveats和next steps
## Variations的深度逻辑
给variations不是给用户制造选择困难是**探索可能性空间**。让用户mix and match出最终版本。
### 好的variations长什么样
- **维度明确**每个variation在不同维度上变A vs B只换配色C vs D只换layout
- **有梯度**从「by-the-book保守版」到「大胆novel版」逐级递进
- **有记号**每个variation有短label说明它在探索什么
### 实现方式
**纯视觉对比**(静态):
→ 用`assets/design_canvas.jsx`网格布局并排展示。每个cell带label。
**多选项/交互差异**
→ 做完整原型用Tweaks切换。例如做登录页"布局"是tweak的一个选项
- 左文案右表单
- 顶部logo+中央表单
- 背景全屏图+浮层表单
用户开关Tweaks就能切换不需要打开多个HTML文件。
### 探索矩阵思考
每次设计脑内过一遍这些维度挑2-3个来给variations
- 视觉minimal / editorial / brutalist / organic / futuristic / retro
- 色彩monochrome / dual-tone / vibrant / pastel / high-contrast
- 字型sans-only / sans+serif对比 / 全衬线 / 等宽
- Layout对称 / 非对称 / 不规则grid / full-bleed / 窄栏
- Density稀疏呼吸 / 中等 / 信息密集
- 交互极简hover / 丰富micro-interaction / 夸张大动画
- 材质flat / 有阴影层次 / 纹理 / noise / 渐变
## 遇到不确定的情况
- **不知道怎么做**坦白说你不确定问用户或先做个placeholder继续。**不要编**。
- **用户的描述矛盾**:指出矛盾,让用户选一个方向。
- **任务太大一次吃不下**拆成steps先做第一步让用户看再推进。
- **用户要求的效果技术上很难**:说清技术边界,提供替代方案。
## 总结规则
交付时summary **极短**
```markdown
✅ 幻灯片已完成10张带Tweaks可切换"夜/日模式"。
注意:
- 第4页的数据是假的等你提供真数据我替换
- 动画用了CSS transition不需要JS
下一步建议:先你浏览器打开看一遍,有问题告诉我哪页哪处。
```
不要:
- 罗列每一页的内容
- 重复讲你用了什么技术
- 夸自己设计多好
Caveats + next steps结束。