docs: add huashu design agent guide

This commit is contained in:
2026-06-11 03:39:35 +08:00
parent 5f555bf342
commit 33a9a132ce
177 changed files with 48486 additions and 0 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,402 @@
# 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 能发现。
## 17. 离线/无 CDN 的真·自包含 —— React/Babel 全内联,且引擎也要 transpile
**踩的坑2026-05 觅游宣传动画)**:动画 HTML 用 `<script src="https://unpkg.com/react...">` + `<script src=".../@babel/standalone">` 走 CDN。本机有全局代理Playwright 录制时 chromium 连 unpkg / Google Fonts 全部 `net::ERR_CONNECTION_CLOSED`
1. React/ReactDOM 没加载 → `window.React undefined`
2. Babel 没加载 → `<script type="text/babel">` 里的 JSX 当普通 JS 跑 → `Unexpected token '<'`
修了 React/Babel 后又踩第二个坑:**把 `animations.jsx` 引擎当普通 `<script>` 内联,依然报 `Unexpected token '<'` → `window.Animations is undefined`**。根因:**`animations.jsx` 引擎本身含 JSX**`Stage`/`Sprite` 组件 `return (<div>...)`),它原设计是用 `<script type="text/babel">` 由 Babel 转译加载的。只 transpile 了 app 代码、忘了 transpile 引擎 → 引擎那段 JSX 没被编译。
**规则**(要做「双击即开 / 离线 / 能被 Playwright 录」的真自包含单文件时):
- **React + ReactDOM 本地内联**`curl` 下载 `react.production.min.js`~10KB+ `react-dom.production.min.js`~131KB到本地inline 进 `<script>`,不走 CDN
- **构建期 Babel 预编译,运行期不带 Babel**:用 `@babel/standalone`(下载一次,仅构建用)在 node 里 `Babel.transform(src,{presets:['react']}).code`,把 JSX → `React.createElement`。**app 和 `animations.jsx` 引擎两段都要过 transform**——引擎含 JSX漏了它必报 `Unexpected token '<'`
- **字体改系统字体**Google Fonts CDN 同样会被代理掐断。中文动画用 `'PingFang SC'`sans/ `'Songti SC'`serif系统字体不依赖网络。`document.fonts.ready` 对系统字体立即 resolve录制不卡
- **base64 内联图片素材**`<img src="png/x.png">` 相对路径在 `file://` 能渲染,但要真便携(移动文件不丢图)就 base64 data URL 内联;背景大图先转 JPEG 压一下再 base64
- **构建模板化**HTML 模板留 `__REACT__/__REACTDOM__/__ASSETS__/__ENGINE__` token + 一段 `type="text/jsx-source"` 的 app 源码node 构建脚本读 token 注入vendor 原样、引擎+app 过 Babel→ 写出最终单文件。改动画只改模板重跑构建
**验证**Playwright `page.evaluate(()=>({React:typeof window.React, Animations:typeof window.Animations}))`——两个都该是 `object`。任一 `undefined` → 对应 `<script>` 抛了错(多半是没 transpile 的 JSX
**和坑 #15 的关系**#15 讲「单文件别用 `src=` 外链 `.jsx`file:// CORS本坑更进一步——连 React/Babel/字体的**远程 CDN 在受限网络下也会断**,要做到真自包含必须全内联 + 构建期 transpile。
## 快速自查清单(开工前 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 底色下都可见?
- [ ] 要离线/真自包含React+ReactDOM 本地内联、**app 和 `animations.jsx` 引擎都过 Babel transpile**、字体用系统字体?(见坑 #17引擎含 JSX漏 transpile 必报 `Unexpected token '<'`

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 三栈 |
---
## 引用
- 完整实现样本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 三支片子深度音频分析AUDIO-BEST-PRACTICES.md作者本地资料未随仓库分发
- huashu-design v9 实战案例hero-animation-v9-final.mp4作者本地样本未随仓库分发

View File

@@ -0,0 +1,250 @@
# 核心资产协议(完整版)
> 从 SKILL.md「核心哲学 #1.a」下沉的完整协议2026-06 瘦身。SKILL.md 留了触发条件 + 5 步标题 + 自检;这里是 5 步详细操作、下载命令、brand-spec 模板、全流程失败兜底、反例与代价对比。
> 触发:任务涉及具体品牌/产品时强制执行。回 SKILL.md 看精简版与上下文。
#### 1.a 核心资产协议(涉及具体品牌时强制执行)
> **这是 v1 最核心的约束,也是稳定性的生命线。** Agent 是否走通这个协议,直接决定输出质量是 40 分还是 90 分。不要跳过任何一步。
>
> **v1.1 重构2026-04-20**:从「品牌资产协议」升级为「核心资产协议」。之前的版本过度聚焦色值和字体,漏掉了设计中最基础的 logo / 产品图 / UI 截图。花叔的原话:「除了所谓的品牌色,显然我们应该找到并且用上大疆的 logo用上 pocket4 的产品图。如果是网站或者 app 等非实体产品的话logo 至少该是必须的。这可能是比所谓的品牌设计的 spec 更重要的基本逻辑。否则,我们在表达什么呢?」
**触发条件**:任务涉及具体品牌——用户提了产品名/公司名/明确客户Stripe、Linear、Anthropic、Notion、Lovart、DJI、自家公司等不论用户是否主动提供了品牌资料。
**前置硬条件**:走协议前必须已通过「#0 事实验证先于假设」确认品牌/产品存在且状态已知。如果你还不确定产品是否已发布/规格/版本,先回去搜。
##### 核心理念:资产 > 规范
**品牌的本质是「它被认出来」**。认出来靠什么?按识别度排序:
| 资产类型 | 识别度贡献 | 必需性 |
|---|---|---|
| **Logo** | 最高 · 任何品牌出现 logo 就一眼识别 | **任何品牌都必须有** |
| **产品图/产品渲染图** | 极高 · 实体产品的"主角"就是产品本身 | **实体产品(硬件/包装/消费品)必须有** |
| **UI 截图/界面素材** | 极高 · 数字产品的"主角"是它的界面 | **数字产品App/网站/SaaS必须有** |
| **色值** | 中 · 辅助识别,脱离前三项时经常撞衫 | 辅助 |
| **字体** | 低 · 需配合前述才能建立识别 | 辅助 |
| **气质关键词** | 低 · agent 自检用 | 辅助 |
**翻译成执行规则**
- 只抽色值 + 字体、不找 logo / 产品图 / UI → **违反本协议**
- 用 CSS 剪影/SVG 手画替代真实产品图 → **违反本协议**(生成的就是「通用科技动画」,任何品牌都长一样)
- 找不到资产不告诉用户、也不 AI 生成,硬做 → **违反本协议**
- 宁可停下问用户要素材,也不要用 generic 填充
##### 5 步硬流程(每步有 fallback绝不静默跳过
##### Step 1 · 问(资产清单一次问全)
不要只问「有 brand guidelines 吗?」——太宽泛,用户不知道该给什么。按清单逐项问:
```
关于 <brand/product>,你手上有以下哪些资料?我按优先级列:
1. LogoSVG / 高清 PNG—— 任何品牌必备
2. 产品图 / 官方渲染图 —— 实体产品必备(如 DJI Pocket 4 的产品照)
3. UI 截图 / 界面素材 —— 数字产品必备(如 App 主要页面截图)
4. 色值清单HEX / RGB / 品牌色盘)
5. 字体清单Display / Body
6. Brand guidelines PDF / Figma design system / 品牌官网链接
有的直接发我,没有的我去搜/抓/生成。
```
##### Step 2 · 搜官方渠道(按资产类型)
| 资产 | 搜索路径 |
|---|---|
| **Logo** | `<brand>.com/brand` · `<brand>.com/press` · `<brand>.com/press-kit` · `brand.<brand>.com` · 官网 header 的 inline SVG |
| **产品图/渲染图** | `<brand>.com/<product>` 产品详情页 hero image + gallery · 官方 YouTube launch film 截帧 · 官方新闻稿附图 |
| **UI 截图** | App Store / Google Play 产品页截图 · 官网 screenshots section · 产品官方演示视频截帧 |
| **色值** | 官网 inline CSS / Tailwind config / brand guidelines PDF |
| **字体** | 官网 `<link rel="stylesheet">` 引用 · Google Fonts 追踪 · brand guidelines |
`WebSearch` 兜底关键词:
- Logo 找不到 → `<brand> logo download SVG``<brand> press kit`
- 产品图找不到 → `<brand> <product> official renders``<brand> <product> product photography`
- UI 找不到 → `<brand> app screenshots``<brand> dashboard UI`
##### Step 3 · 下载资产 · 按类型三条兜底路径
**3.1 Logo任何品牌必需**
> ⚠️ **别只试 `curl <brand>.com/logo.svg` 就放弃**——现在的官网大多是 SPA直连静态路径基本返回空壳 HTML2026-06-06 实测 Trae 官网 5 条直连路径全是空壳)。**数字产品 / SaaS / AI 工具优先用图标聚合源**,命中率最高、直出干净 SVG。
按成功率递减:
0. **图标聚合源(知名数字产品/SaaS/AI 工具首选,命中率最高)**
```bash
unset ALL_PROXY HTTP_PROXY HTTPS_PROXY all_proxy http_proxy https_proxy # 清代理,否则 TLS 易炸
# svgl —— AI/开发者品牌覆盖最全Claude/Cursor/OpenAI/Copilot/Anthropic/Vercel…含 light/dark + wordmark
curl -s "https://api.svgl.app?search=<brand>" # 返回 JSON取 route(.light/.dark) 的 svg URL 再下载
# simpleicons —— 单色 glyph可直接按品牌色上色
curl -o logo.svg "https://cdn.simpleicons.org/<slug>/<hexcolor>"
```
1. 独立 SVG/PNG 文件 / 官方 brand 页(如 `<brand>.com/brand`、`/press`
```bash
curl -A "Mozilla/5.0" -L -o assets/<brand>-brand/logo.svg "<official-logo-url>"
```
2. 官网 HTML 全文提取 inline SVG
```bash
curl -A "Mozilla/5.0" -L https://<brand>.com -o assets/<brand>-brand/homepage.html
# 然后 grep <svg>...</svg> 提取 logo 节点
```
3. **Google favicon 服务(站点真实 mark 兜底,几乎不失败)**
```bash
curl -o logo.png "https://www.google.com/s2/favicons?domain=<brand-domain>&sz=256" # 256px 官方站点图标
```
4. 官方社交媒体 avatar最后手段GitHub/Twitter/LinkedIn 的公司头像通常是 400×400 或 800×800 透明底 PNG
下载后**逐个核对**`file <logo>` 确认是真 SVG/PNG不是 106 字节占位或 HTML 空壳),`head -c 90 <logo.svg>` 看是否 `<svg`。
**3.2 产品图/渲染图(实体产品必需)**
按优先级:
1. **官方产品页 hero image**(最高优先级):右键查看图片地址 / curl 获取。分辨率通常 2000px+
2. **官方 press kit**`<brand>.com/press` 常有高清产品图下载
3. **官方 launch video 截帧**:用 `yt-dlp` 下载 YouTube 视频ffmpeg 抽几帧高清图
4. **Wikimedia Commons**:公共领域常有
5. **AI 生成兜底**nano-banana-pro把真实产品图作为参考发给 AI让它生成符合动画场景的变体。**不要用 CSS/SVG 手画代替**
```bash
# 示例:下载 DJI 官网产品 hero image
curl -A "Mozilla/5.0" -L "<hero-image-url>" -o assets/<brand>-brand/product-hero.png
```
**3.3 UI 截图(数字产品必需)**
- App Store / Google Play 的产品截图(注意:可能是 mockup 而非真实 UI要对比
- 官网 screenshots section
- 产品演示视频截帧
- 产品官方 Twitter/X 的发布截图(常是最新版本)
- 用户有账号时,直接截屏真实产品界面
**3.4 · 素材质量门槛「5-10-2-8」原则铁律**
> **Logo 的规则不同于其他素材**。Logo 有就必须用(没有就停下问用户);其他素材(产品图/UI/参考图/配图遵循「5-10-2-8」质量门槛。
>
> 2026-04-20 花叔原话:「我们的原则是搜索 5 轮,找到 10 个素材,选择 2 个好的。每个需要评分 8/10 以上,宁可少一些,也不为了完成任务滥竽充数。」
| 维度 | 标准 | 反模式 |
|---|---|---|
| **5 轮搜索** | 多渠道交叉搜(官网 / press kit / 官方社媒 / YouTube 截帧 / Wikimedia / 用户账号截屏),不是一轮抓前 2 个就停 | 第一页结果直接用 |
| **10 个候选** | 至少凑 10 个备选才开始筛 | 只抓 2 个,没得选 |
| **选 2 个好的** | 从 10 个里精选 2 个作为最终素材 | 全都用 = 视觉过载 + 品位稀释 |
| **每个 8/10 分以上** | 不够 8 分**宁可不用**,用诚实 placeholder灰块+文字标签)或 AI 生成nano-banana-pro 以官方参考为基底)| 凑数 7 分素材进 brand-spec.md |
**8/10 评分维度**(打分时记录在 `brand-spec.md`
1. **分辨率** · ≥2000px印刷/大屏场景 ≥3000px
2. **版权清晰度** · 官方来源 > 公共领域 > 免费素材 > 疑似盗图(疑似盗图直接 0 分)
3. **与品牌气质契合度** · 和 brand-spec.md 里的「气质关键词」一致
4. **光线/构图/风格一致性** · 2 个素材放一起不打架
5. **独立叙事能力** · 能单独表达一个叙事角色(不是装饰)
**为什么这个门槛是铁律**
- 花叔的哲学:**宁缺毋滥**。滥竽充数的素材比没有更糟——污染视觉品味、传递「不专业」信号
- **「一个细节做到 120%,其他做到 80%」的量化版**8 分是"其他 80%" 的底线,真正 hero 素材要 9-10 分
- 消费者看作品时,每一个视觉元素都在**积分或扣分**。7 分素材 = 扣分项,不如留空
**Logo 例外**重申有就必须用不适用「5-10-2-8」。因为 logo 不是「多选一」问题,而是「识别度根基」问题——就算 logo 本身只有 6 分,也比没有 logo 强 10 倍。
##### Step 4 · 验证 + 提取(不只是 grep 色值)
| 资产 | 验证动作 |
|---|---|
| **Logo** | 文件存在 + SVG/PNG 可打开 + 至少两个版本(深底/浅底用)+ 透明背景 |
| **产品图** | 至少一张 2000px+ 分辨率 + 去背或干净背景 + 多个角度(主视角、细节、场景) |
| **UI 截图** | 分辨率真实1x / 2x+ 是最新版本(不是旧版)+ 无用户数据污染 |
| **色值** | `grep -hoE '#[0-9A-Fa-f]{6}' assets/<brand>-brand/*.{svg,html,css} \| sort \| uniq -c \| sort -rn \| head -20`,过滤黑白灰 |
**警惕示范品牌污染**:产品截图里常有用户 demo 的品牌色(如某工具截图演示喜茶红),那不是该工具的色。**同时出现两种强色时必须区分**。
**品牌多切面**:同一品牌的官网营销色和产品 UI 色经常不同Lovart 官网暖米+橙,产品 UI 是 Charcoal + Lime。**两套都是真的**——根据交付场景选合适的切面。
##### Step 5 · 固化为 `brand-spec.md` 文件(模板必须覆盖所有资产)
```markdown
# <Brand> · Brand Spec
> 采集日期YYYY-MM-DD
> 资产来源:<列出下载来源>
> 资产完整度:<完整 / 部分 / 推断>
## 🎯 核心资产(一等公民)
### Logo
- 主版本:`assets/<brand>-brand/logo.svg`
- 浅底反色版:`assets/<brand>-brand/logo-white.svg`
- 使用场景:<片头/片尾/角落水印/全局>
- 禁用变形:<不能拉伸/改色/加描边>
### 产品图(实体产品必填)
- 主视角:`assets/<brand>-brand/product-hero.png`2000×1500
- 细节图:`assets/<brand>-brand/product-detail-1.png` / `product-detail-2.png`
- 场景图:`assets/<brand>-brand/product-scene.png`
- 使用场景:<特写/旋转/对比>
### UI 截图(数字产品必填)
- 主页:`assets/<brand>-brand/ui-home.png`
- 核心功能:`assets/<brand>-brand/ui-feature-<name>.png`
- 使用场景:<产品展示/Dashboard 渐现/对比演示>
## 🎨 辅助资产
### 色板
- Primary: #XXXXXX <来源标注>
- Background: #XXXXXX
- Ink: #XXXXXX
- Accent: #XXXXXX
- 禁用色: <品牌明确不用的色系>
### 字型
- Display: <font stack>
- Body: <font stack>
- Mono数据 HUD 用): <font stack>
### 签名细节
- <哪些细节是「120% 做到」的>
### 禁区
- <明确不能做的:比如 Lovart 不用蓝色、Stripe 不用低饱和暖色>
### 气质关键词
- <3-5 个形容词>
```
**写完 spec 后的执行纪律(硬要求)**
- 所有 HTML 必须**引用** `brand-spec.md` 里的资产文件路径,不允许用 CSS 剪影/SVG 手画代替
- Logo 作为 `<img>` 引用真实文件,不重画
- 产品图作为 `<img>` 引用真实文件,不用 CSS 剪影代替
- CSS 变量从 spec 注入:`:root { --brand-primary: ...; }`HTML 只用 `var(--brand-*)`
- 这让品牌一致性从「靠自觉」变成「靠结构」——想临时加色要先改 spec
##### 全流程失败的兜底
按资产类型分别处理:
| 缺失 | 处理 |
|---|---|
| **Logo 完全找不到** | **停下问用户**不要硬做logo 是品牌识别度的根基) |
| **产品图(实体产品)找不到** | 优先 nano-banana-pro AI 生成(以官方参考图为基底)→ 次选向用户索取 → 最后才是诚实 placeholder灰块+文字标签,明确标注"产品图待补" |
| **UI 截图(数字产品)找不到** | 向用户索取自己账号的截屏 → 官方演示视频截帧。不用 mockup 生成器凑 |
| **色值完全找不到** | 按「设计方向顾问模式」走,向用户推荐 3 个方向并标注 assumption |
**禁止**:找不到资产就静默用 CSS 剪影/通用渐变硬做——这是协议最大的反 pattern。**宁可停下问,也不要凑**。
##### 反例(真实踩过的坑)
- **Kimi 动画**:凭记忆猜「应该是橙色」,实际 Kimi 是 `#1783FF` 蓝色——返工一遍
- **Lovart 设计**:把产品截图里演示品牌的喜茶红当成 Lovart 自己的色——差点毁整个设计
- **DJI Pocket 4 发布动画2026-04-20触发本协议升级的真实案例**:走了旧版只抽色值的协议,没下载 DJI logo、没找 Pocket 4 产品图,用 CSS 剪影代替产品——做出来是「通用黑底+橙 accent 的科技动画」,没有大疆识别度。花叔原话:「否则,我们在表达什么呢?」→ 协议升级。
- 抽完色没写进 brand-spec.md第三页就忘了主色数值临场加了个「接近但不是」的 hex——品牌一致性崩溃
- **五大 Coding Agent 对比 PPT2026-06-06触发触发条件扩展的真实案例**agent 把任务判成「PPT + 没风格参考」走 Fallback 设计方向顾问,只抽了五家品牌色就 spawn 三套设计逻辑,**五个产品 logoClaude Code / Cursor / Codex / Copilot / Trae一个没取**——被花叔抓现行「我们为什么没去取这些产品的 logo」。根因把「对比 / 榜单 deck」误判为不触发 §1.a以为 §1.a 只管「为单一客户做物料」),且 Fallback 路径里没有任何 logo 检查点。→ 修复:①触发条件扩成两类(含「设计里点名/并列真实产品」②Fallback 不豁免取 logo ③Phase 3.5 加「具名产品 logo 子门」spawn 前必过 ④Step 3.1 补 svgl/simpleicons/Google favicon 可靠取图链。
##### 协议代价 vs 不做代价
| 场景 | 时间 |
|---|---|
| 正确走完协议 | 下载 logo 5 min + 下载 3-5 张产品图/UI 10 min + grep 色值 5 min + 写 spec 10 min = **30 分钟** |
| 不做协议的代价 | 做出没识别度的通用动画 → 用户返工 1-2 小时,甚至重做 |
**这是稳定性最便宜的投资**。尤其对商单/发布会/重要客户项目30 分钟的资产协议是保命钱。

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,370 @@
# 设计风格库:网页 20 种 + PPT 20 种HTML 原生优先)
> **2026-06 重构**。基于对全球 10 大网站类型 + 10 大演示类型、各 top5 公认最佳设计(共 100 个真实案例)的调研反推。
> 旧版 20 种「平面/装置设计师哲学」库的致命问题:大胆风格几乎全是 AI-生成-only粒子/光影/手绘),**用户默认无生图能力、default 全走 HTML 时大胆半场直接清零只剩极简——这是「default 千篇一律」的根因**。本库每一种都标了「纯 HTML/CSS 无生图」下的**还原度**。
>
> ⚖️ **但记住定位**:这是**「没思路时翻的弹药」,不是「必须从这里选」的清单**。用户给了内容/品牌/参考设计就从那里展开别套库。skill 的职责是帮用户规避最差,不是规定好设计长什么样——好设计从用户的真实需求里长出来。
## 这个库怎么用
1. **先按输出类型选半区**:做网页/落地页/官网 → 网页 20 种;做 PPT/deck/演示 → PPT 20 种。
2. **温度体系**:每种标了 `大胆 / 中性 / 安静`。**故意让大胆款占多数**——模型的确定性偏差天然偏安静极简,库的配比要把它往大胆推。
- 方向 A稳妥底盘从安静/中性里按需求选;方向 B 取不同温度拉反差;**方向 C 由 SKILL 的「秒数轮盘」强制注入大胆款**。
- ❌ 三个方向不要都落在「米白+留白+一个点缀色」——那是最常见的失败模式。
3. **还原度**≥90% 闭眼做70-90% 主体可做、个别细节降级;<70%(如 Memphis 做旧纹理)必须在产出里**明确标注哪部分用纯色块降级**,不假装能做出原版质感。
4. **字体**每种给了开源替代Inter/Geist/Manrope/Space Grotesk/Fraunces/Playfair 等不要写付费字体Söhne/Circular 等)。
5. 配套SKILL「设计方向顾问」Phase 3-5 用本库推 3 方向;`assets/showcases/` 有预制截图画廊。
---
## 网页风格库(20种)
#### 大胆派
**媒体级粗野主义 Editorial Brutalism巨号Helvetica压小正文** `大胆·还原98%`
- 参考:Bloomberg BusinessweekRichard Turley 2010-2014 改版Code and Theory操刀Neue Haas Grotesk谱系
- 适配:媒体/内容出版、AI产品发布、品牌官网hero、调研报告封面、观点型长文头图
- 视觉DNA:配色纯黑#000+纯白#FFF+超链接蓝#0000EE,点缀信号橙红#FF433D/终端绿#00A33E。字体Helvetica/Neue Haas Grotesk120px+巨号headline左对齐紧字距直接压住14px小正文极端字号反差。布局模块化网格+1px规则线分栏切割高信息密度刻意不留白。标志元素rule line分栏、超链接蓝下划线、黑白底大色块。
- HTML实现:纯CSS可1:1还原。CSS Grid做模块网格+border做规则线分栏clamp()做超大响应式字号+letter-spacing收紧系统Helvetica/Arial栈或Inter兜底超链接直接#0000EE下划线。零素材依赖。
- 字体:Inter替Helvetica/Neue Haas Grotesk代码用Geist Mono
**新粗野主义撞色信息流 Neo-Brutalism粗黑描边卡片+高饱和撞色)** `大胆·还原95%`
- 参考:The Verge 2022 redesignin-house teamPolySans + Mānuka
- 适配:媒体/内容站、AI产品聚合页、活动landing、社区榜单页、小红书风信息卡
- 视觉DNA:配色电光紫#5200FF~品红#E1306C高饱和主色+亮黄#F8E000强调+纯黑#08080D+白,大面积撞色块刻意不柔和。字体几何无衬线大标题+衬线正文反差。布局卡片化feed流、2-4px粗黑描边、硬色块分区、近乎无圆角。标志元素粗描边卡片hover撞色翻转、未完成界面气质。
- HTML实现:纯CSS强项。border:3px solid #000粗描边+box-shadow硬投影偏移(4px 4px 0 #000)+grid/flex卡片流+:hover切换background撞色翻转。无3D/光影障碍。
- 字体:Space Grotesk替PolySans+ 任一衬线如Fraunces
**孟菲斯复古拼贴最大化 Memphis Maximalism撞色块+错位叠放+复古字体)** `大胆·还原72%`
- 参考:Gucci Vault概念店Alessandro MicheleMemphis设计运动 / Sagmeister叛逆基因
- 适配:电商概念店、创意活动页、品牌实验campaign、Y2K复古主题、节日营销页
- 视觉DNA:配色复古红/芥末黄/宝蓝/紫/橄榄绿大面积撞色并置+做旧米色暖底,浓烈刻意不和谐。字体复古衬线+装饰字混用、印刷质感、打破网格错位叠放。布局反网格拼贴策展、模块大小不一错落叠压、像逛数字房间。标志元素:撞色块、错位叠放、非常规导航彩蛋。
- HTML实现:transform:rotate()做错位叠放+position:absolute叠压+高饱和background撞色块+复古Google Fonts。真实做旧纹理无法CSS还原降级为纯色块+mix-blend-mode/contrast滤镜模拟肌理几何拼贴版成立、archival做旧版会降级。
- 字体:DM Serif Display + Bungee装饰+ Space Mono
**糖果色凸起立体按钮游戏化 Friendly Geometric Candy** `大胆·还原85%`
- 参考:DuolingoJohnson Banks + MonotypeFeather Bold字体反硅谷极简
- 适配:教育语言学习、消费级App landing、游戏化产品、面向大众亲和产品、活动报名页
- 视觉DNA:配色Duo绿#58CC02+鸭子黄#FFC800+天蓝#1CB0F6糖果高饱和+白底圆润友好。字体超粗圆体Feather Bold感。布局大圆角卡片、凸起3D按钮底部硬阴影=可按压感)、吉祥物位+进度气泡。标志元素3px实底阴影立体按钮、按下位移动画、超圆角。
- HTML实现:纯CSS。box-shadow:0 4px 0生硬底阴影做凸起按钮+:active translateY(4px)消阴影模拟按压border-radius大圆角纯色块。吉祥物无生图时用CSS几何形或emoji占位轻微降级
- 字体:Baloo 2 / Nunito超粗圆体替Feather
**纯CSS几何插画+响应式变形彩蛋 Pure-CSS Art** `大胆·还原80%`
- 参考:Lynn Fisherlynnandtonic.com纯CSS艺术传奇Adobe专文报道
- 适配:个人主页、创意404/彩蛋页、品牌玩味landing、技术博客头图、设计师自我展示
- 视觉DNA:配色2-4色高对比扁平面每个breakpoint换调色。字体粗几何无衬线标题。布局核心是「图随视口变形」——一组CSS形状在不同断点重组成不同画面如建筑随屏宽变换层数。标志元素纯CSS绘制的几何插画、断点驱动的重排彩蛋、零图片。
- HTML实现:纯CSS的炫技战场零素材是优势。div+border-radius/clip-path/transform/box-shadow堆叠几何形@media断点改变形状尺寸位置实现变形。难度在设计构思而非技术,但需要精心手搓每个形状。
- 字体:Rubik / Archivo粗几何替自定义
**巨型字黑白高对比时装大字报 Bold Big-Type Editorial** `大胆·还原88%`
- 参考:Jacquemus官网 / Rik Oostenbroek / Domestika时装杂志大字报
- 适配:电商时尚、作品集、媒体专题、品牌宣言页、视频课程封面、调研报告大字版
- 视觉DNA:配色极简黑白+单一克制点缀色(裸粉#E8C4C0或正红。字体超大Display无衬线/高反差衬线标题占满整屏。布局全幅网格、巨字与负空间博弈、图文1:1分割。标志元素屏占比巨型headline、奢侈级留白、左右对位排版。
- HTML实现:纯CSS完美还原。clamp()巨号字+CSS Grid全幅分割+大量padding留白+vh单位让标题占满视口。无图时用纯色块/文字块替代时装大片占位(轻降级但版式成立)。
- 字体:Archivo Expanded / AntonDisplay+ Playfair Display高反差衬线
**复古未来太空图录 Cosmic Retro-Futurism** `大胆·还原75%`
- 参考:Perplexity Comet浏览器发布站The Brand IdentityBlack/Blue/Cream《2001太空漫游》气质
- 适配:AI产品发布站、科技品牌宣言页、活动倒计时页、未来感landing、概念发布会
- 视觉DNA:配色纯黑#0A0A0A+奶油纸白cream#F0EAD8+一抹钴蓝-孔雀蓝#2B4F91,低饱和像老式天文图录。字体高反差衬线(古典天文图册感)+留白。布局线描轨道/抛物线SVG、行星圆点、奶油底压黑字、古籍式排印。标志元素SVG天体轨道线、奶油+蓝+黑三色、复古衬线大字、天文图录质感。
- HTML实现:纯CSS+SVG还原静态版八成气质。SVG path画轨道抛物线+CSS径向定位行星圆点+三色变量+高反差衬线。缺口是「太空落到地球」的全屏视频转场灵魂部分——降级为CSS scroll视差+SVG轨道旋转近似。
- 字体:Cormorant Garamond / EB Garamond高反差衬线+ Space Mono
**电影感声波可视化 Cinematic Sound-Viz Dark** `大胆·还原72%`
- 参考:ElevenLabs电影片头title sequenceSaul Bass式极简动态× 音频工程界面
- 适配:音频/语音AI产品、音乐科技站、播客平台、媒体发布页、影院级品牌hero
- 视觉DNA:配色纯黑#000底+纯白文字+蓝紫渐变accent波形。字体大号无衬线标题Saul Bass式极简。布局全幅暗场、声波/频谱可视化贯穿、巨标题压波形、卡片功能区。标志元素彩色audio-waveform波形带、电影片头式极简、高对比黑白+单渐变、声音可视化母题。
- HTML实现:纯CSS+SVG还原70%气质骨架完美波形是降级点。SVG polyline画静态波形或多条不等高div柱阵+CSS animation做『假波形』跳动近似。缺口随声音实时跳动的Web Audio/Canvas频谱不可纯CSS还原静态版像、动态灵魂还不了。
- 字体:Inter / Sora大号无衬线
**像素游戏横版叙事 Pixel-Game Side-Scroller** `大胆·还原70%`
- 参考:Robby Leonardi交互简历8/16-bit平台动作游戏叙事致敬任天堂SNES
- 适配:创意简历/作品集、品牌玩味campaign、游戏化landing、活动彩蛋页、个人趣味主页
- 视觉DNA:配色复古游戏多段分区——森林绿#4CAF50草地+天蓝#5DADE2,过渡太空紫#2C2A4A、火山橙红#E8743B、海底青#1ABC9C每『关卡』换一套高饱和卡通调色。字体像素字体8-bit感+粗无衬线。布局横版/纵向滚动分关卡场景、视差分层、scroll触发位移。标志元素分关卡换色、像素美学、视差滚动、游戏HUD式UI。
- HTML实现:纯CSS+少量JS还原骨架原作就是HTML+CSS+jQuery无WebGL。视差分层position+scroll位移、image-rendering:pixelated、CSS逐帧background-position做sprite动画、分段背景色。缺口原创角色/场景手绘像素插画——无生图时用CSS方块拼简易像素图标替代美术降级技术不降
- 字体:Press Start 2P / VT323像素字+ Inter
#### 中性派
**包豪斯几何标志+扁平插画系统 Bauhaus Geometric** `中性·还原90%`
- 参考:Khan Academy rebrand六边形+花瓣logomark + Wonder Blocks设计系统Bauhaus几何构成
- 适配:教育课程站、品牌logo系统、信息图、儿童亲和向产品、活动KV
- 视觉DNA:配色三原色谱系——包豪斯红#E63946/黄#FFB703/蓝#0077B6+黑白,纯色块拼接。字体几何无衬线(圆润几何感)。布局圆/三角/方基本几何单元搭建插画对齐栅格、模块化拼图。标志元素纯几何形态logomark、扁平无渐变插画、原色块构成。
- HTML实现:纯CSS几何全能。border-radius:50%做圆、clip-path/border三角形、方块div拼几何插画CSS Grid栅格对齐纯色fill无需素材。插画用CSS形状或内联SVG几何路径手搓。
- 字体:Poppins / Manrope几何圆润替Wonder Blocks
**暗色双色侧栏开发者作品集 Dark Editorial深底+单荧光accent+等宽字)** `中性·还原96%`
- 参考:Brittany Chiangbrittanychiang.com v4dev portfolio事实标准
- 适配:作品集个人主页、开发者向产品、技术品牌站、简历页、AI工具landing
- 视觉DNA:配色深墨绿/海军底#0A192F+板岩灰文字#8892B0+单一荧光青绿accent#64FFDA。字体无衬线正文+等宽字(编号/标签)。布局左固定侧栏导航+右滚动主区双栏section编号01/02、链接hover下划线滑入。标志元素单accent色、等宽编号标签、侧栏锚点高亮。
- HTML实现:纯CSS完全还原。position:sticky做固定侧栏+CSS Grid双栏+单accent变量+等宽字标签+:hover下划线transform滑入。零素材纯版式与微交互。
- 字体:Inter + JetBrains Mono等宽
**暖色出版物 Warm Editorial奶油纸底+赤陶橙+衬线无衬线混排)** `中性·还原97%`
- 参考:Anthropic / ClaudeDBCo + Geist StudioStyrene×TiemposPenguin/Pelican平装书排印
- 适配:AI产品站、品牌官网、长文阅读页、橙皮书电子书、调研报告、培训材料
- 视觉DNA:配色奶油纸底#F5F0E8+赤陶橙#CC785C/#D97757点缀+近黑文字#191919温暖低饱和。字体衬线标题Tiempos感×无衬线正文Styrene感混排。布局书籍式单栏阅读流、舒适行高、节制分隔线。标志元素纸感暖底、赤陶橙、出版级排印节奏。
- HTML实现:纯CSS 100%还原,零素材。背景色变量+衬线无衬线字体栈混排+max-width限制阅读宽度+line-height 1.7舒适行高。这是Anthropic赤陶橙暖色版的安全主场。
- 字体:Fraunces / Newsreader替Tiempos衬线+ Inter替Styrene
**Linear暗色发光+Bento网格 Glassmorphism Bento** `中性·还原85%`
- 参考:Linear / Cursor'The Linear Look'现象级流派Frontend Horse有代码配方
- 适配:SaaS/AI产品站、开发者工具、技术品牌hero、产品功能展示、深色dashboard演示
- 视觉DNA:配色近黑底#08090A+去饱和蓝紫品牌#5E6AD2+低饱和青紫微光渐变#4EA7FC#B59AFF。字体几何无衬线负字距紧凑。布局便当盒bento网格分块、发丝分割线、玻璃拟态卡片。标志元素暗底发光渐变边框、bento分块、流光streamer、磨砂玻璃。
- HTML实现:纯CSS强还原。box-shadow/filter blur+radial-gradient做发光晕backdrop-filter:blur玻璃拟态conic/linear-gradient边框CSS Grid拼bento。缺口仅「真实产品UI截图」——用色块+文字拼简化假UI替代这部分降级
- 字体:Inter / Geist负字距+ Geist Mono
**斜切流体渐变带 Angled Fluid Gradient** `中性·还原92%`
- 参考:Stripe标志性angled gradient bannerKlim定制Söhne字体
- 适配:SaaS/Fintech落地页、品牌官网hero、产品发布页、活动banner、AI产品营销页
- 视觉DNA:配色多色流体渐变(靛蓝#635BFF→青→粉→橙暖调做hero背景+纯白内容区+近黑文字。字体精致无衬线Söhne感。布局倾斜分割色块skew切角分区、渐变hero压结构化栅格正文。标志元素angled斜切边界、多色流体渐变、理性栅格压表达渐变。
- HTML实现:纯CSS。transform:skewY()或clip-path:polygon()做斜切分区linear-gradient多色叠加可加CSS animation缓慢流动做流体渐变带Grid做下方结构化正文。零素材。
- 字体:Inter / Hanken Grotesk替Söhne
**实用主义彩虹分类文档 Utility-First Colorful Docs** `中性·还原98%`
- 参考:Tailwind CSS DocsSky/Cyan品牌色+功能分类彩虹色相条)
- 适配:技术文档、API参考、设计系统站、教程站、开发者knowledge base、SaaS帮助中心
- 视觉DNA:配色Sky蓝#38BDF8品牌+teal→cyan→sky青蓝渐变+Slate灰阶#0F172A/#64748B/#F8FAFC,文档用彩虹色相条区分功能分类(粉#EC4899/紫#A855F7/绿#10B981/橙)。字体清爽无衬线+等宽代码。布局左侧栏导航+中正文+右TOC三栏彩色高亮代码块、分类色标。标志元素青蓝渐变hero、彩虹分类色、三栏文档骨架、语法高亮代码块。
- HTML实现:纯CSS 98%还原它本身就是CSS框架文档。Grid三栏+linear-gradient青蓝hero+分类色变量+代码块语法色用span着色。Inter开源唯暗色切换/copy需轻量JS。零光影/3D/手绘。
- 字体:Inter + JetBrains Mono / Fira Code代码
**终端核软未来 Terminal-Core Soft-Futurism等宽字+等距立方)** `中性·还原80%`
- 参考:Cursor (Anysphere);开发者终端美学 × Teenage Engineering工业极简
- 适配:AI编程工具站、CLI产品landing、开发者基础设施、技术品牌hero、终端类产品
- 视觉DNA:配色炭黑#0B0D14底+暖白文字#F2F0EF+克制蓝紫渐变accent点缀按钮与光晕。字体等宽字为主角命令行感+无衬线辅助。布局命令行/代码块前景、bento分区、2.5D等距cube示意。标志元素等宽字命令行、等距投影立方体、暖白×炭黑、克制渐变光晕、工业极简。
- HTML实现:纯CSS 80%还原。等宽字代码块+暗色bento+box-shadow光晕2.5D等距cube用CSS 3D transform(rotateX/Y+skew)或SVG等距投影手搓。缺口可点击切换的多界面demo需JS+假UI拼接。无WebGL刚需。
- 字体:Geist Mono / JetBrains Mono主角+ Inter辅助
#### 安静派
**功能主义网格社区 Functional Brutalism灰线分割+系统字+蓝链接)** `安静·还原98%`
- 参考:Are.na / Lobsters / QuartzMüller-Brockmann栅格数字落地 + Tufte信息密度
- 适配:社区/UGC平台、内容聚合站、文档知识库、移动优先内容流、极客向产品
- 视觉DNA:配色近白底#FBFBFB+黑文字+1px灰分割线#E0E0E0+经典链接蓝#0000EE/已访问紫。字体系统字栈(-apple-system/无装饰)。布局高密度信息列表、细灰线分栏、极小留白、紧凑行距。标志元素:发丝灰分割线、蓝链接、系统字、信息密度优先。
- HTML实现:纯CSS最易还原这是Brutalist Web的本色。border-bottom:1px灰线列表+system-ui字栈+紧凑padding+蓝链接。几乎不需要任何素材或JS纯结构。
- 字体:system-ui系统字栈 / IBM Plex Sans兜底
**深色画廊裱框 Gallery Dark深黑负空间+单列大图+EXIF小字** `安静·还原75%`
- 参考:Glass (glass.photo) / Bottega Veneta美术馆暗房 + Apple Photos内容至上
- 适配:摄影作品集、奢侈品电商、视觉内容沉浸展示、个人画廊页、高端产品陈列
- 视觉DNA:配色纯黑底#0A0A0A+作品图本身提供唯一色彩+极淡灰EXIF小字#666。字体极细无衬线小字。布局单列居中大图、巨幅负空间裱框、图下metadata小字。标志元素暗房黑底、内容至上UI退隐、EXIF式小字注脚、大图独占视口。
- HTML实现:纯CSS还原版式骨架。纯黑底+居中max-width单列+巨幅padding裱框留白+小字metadata。缺口是「真实摄影作品」本身——用占位图/纯色块代替则失灵魂但暗房氛围与版式100%可搭。
- 字体:Inter细字重300/ Cormorant衬线奢侈感可选
**Swiss极致黑白 Swiss MonochromeVercel式纯黑白+Geist+锐利边角)** `安静·还原98%`
- 参考:Vercel / Next.js Docs自研Geist已开源Massimo Vignelli少即是多
- 适配:开发者工具文档、技术品牌官网、AI产品站、SaaS落地页、极简调研报告
- 视觉DNA:配色纯黑#000+纯白#FFF+灰阶#888零彩色或仅一抹蓝链接。字体Geist几何无衬线+Geist Mono。布局锐利直角无圆角或极小、高对比、精密栅格、克制留白。标志元素纯黑白、锐利边角、Geist字体、三角/箭头几何标记。
- HTML实现:纯CSS 100%还原Geist开源可直接引。CSS Grid精密栅格+纯黑白变量+border-radius:0锐角+发丝边框。这是HTML最舒适的极简主场零素材依赖。
- 字体:Geist + Geist MonoVercel开源原版
**日式留白白盒画廊 Kenya Hara White Gallery** `安静·还原80%`
- 参考:Cosmos (cosmos.so) / Aesop伊索官网原研哉『白』的空寂 + 瑞士网格混血
- 适配:高端电商、创意画廊、内容策展平台、设计师作品集、品牌精品店、moodboard站
- 视觉DNA:配色近全白#FAFAFA底+纯黑文字#0A0A0A+极淡灰分割#EFEFEF内容图提供全部色彩、UI退到背景。字体极简系统/几何无衬线小字、大字距。布局masonry瀑布网格、极致留白、淡灰发丝分隔、东方空寂。标志元素白盒美学、奢侈留白、内容至上UI隐退、瀑布流策展。
- HTML实现:纯CSS还原静态版式与暗色画廊区分在『白』。CSS columns或Grid做masonry+近白变量+大padding留白+淡灰分隔。缺口是Lenis/GSAP丝滑惯性滚动与图片入场缓动高级感60%在此CSS仅基础transition动效层降级。
- 字体:Inter细字重/ Cooper HewittAesop同款开源
## PPT风格库(20种)
#### 大胆派
**新瑞士大字报 / Neo-Swiss Billboard Editorial** `大胆·还原98%`
- 参考:Scribe $75M、Flock Safety $47M 等 AI/SaaS 路演 deck 的 Big-Number Editorial 流派Bloomberg Businessweek 信息图Pentagram
- 适配:融资路演、QBR/业务回顾、年度趋势复盘、产品发布关键页
- 视觉DNA:配色=纯白(#FFFFFF)或近黑(#0A0A0A)底+单一高饱和强调色(电光蓝#2D5BFF/荧光绿#00E676/品牌橙#FF6B2C)+中性网格线#E5E5E5。字体=超大粗体无衬线标题占半屏数字tabular-nums等宽收紧字距。母版=①大色块章节页一个词②巨型数字占半屏(3.2x)+小注③左右分栏对比④全幅扁平折线/柱状。标志=billboarding大字、严格基线网格、大色块章节页
- HTML实现:超大数字用clamp()严格网格用CSS Grid大色块章节页background-color折线柱状用纯div+CSS或内联SVG(比贴图更锐利)数字对齐font-variant-numeric:tabular-nums。零插画零3D
- 字体:Inter / Geist / Söhne替代Neue Haas Grotesk数字配Geist Mono
**黑底巨型数字剧场 / Black Big-Number Stage** `大胆·还原97%`
- 参考:Steve Jobs 2007 iPhone Keynote、小米SU7 Ultra雷军发布会、Spotify Wrapped、Presentation Zen(Garr Reynolds)
- 适配:产品发布主题演讲、思想演示、全员town hall、情绪向年度回顾
- 视觉DNA:配色=纯黑#000000底+纯白#FFFFFF字高反差,一页只一个品牌强调色高亮(小米橙#FF6900/Spotify绿#1ED760/Apple蓝#2997FF)。字体=几何无衬线粗体,一屏一词或一个超大数字占满视野,字距收紧。母版=①标题页黑底居中一行大字②数据高潮页巨型数字+单位+一行注③左右参数对比双栏(强调色vs灰)④slogan单页。大量负空间
- HTML实现:黑底白字几行CSS巨型数字clamp()+flex居中强调色highlight单独span左右对比CSS Grid两列+条形高亮tabular-nums。去掉产品照改纯文字反而更接近Zen本质
- 字体:Geist / Inter / 思源黑替代SF Pro
**高饱和单色品牌撞色海报 / Mono-Brand Type-as-Hero** `大胆·还原96%`
- 参考:Spotify Wrapped视觉系统、Mailchimp Brand Book(Collins)、Netflix红黑现代复刻、COLLINS品牌系统
- 适配:品牌/营销策略、campaign宣讲、town hall文化页、活动主视觉
- 视觉DNA:配色=单一品牌主色满版铺底(Spotify绿#1ED760/Mailchimp黄#FFE01B/Netflix红#E50914)+黑或白反差字,撞色两层。字体=超大字体即主视觉(type-as-hero)顶天立地。母版=①满色块底+反白巨字②双色块上下/左右分割③巨型数字撑满。标志=单色满版、字体当图、高对比撞色
- HTML实现:满版background-color超大字clamp()占满双色用两个100vh色块字体当图靠font-weight900+负letter-spacing。纯色块零素材HTML原生最爽
- 字体:Inter / Manrope / Archivo(超粗)替代Circular/Cavendish
**全幅渐变宣言版式 / Full-Bleed Gradient Manifesto** `大胆·还原82%`
- 参考:Zuora『Tell a Different Story』销售deck(Andy Raskin拆解)、Nike『Just Do It』campaign、National Geographic跨页
- 适配:销售提案愿景页、品牌宣言、keynote转折页、使命愿景单页
- 视觉DNA:配色=满版CSS渐变(暖橙→品红/深蓝→青)或纯色出血+反白宣言大字+hashtag口号(#shifthappens)。字体=厚重无衬线全大写标语横贯。母版=①满幅渐变+居中反白宣言②应许之地愿景页③客户logo墙。标志=full-bleed出血、反白大标语、hashtag口号
- HTML实现:linear-gradient/radial-gradient满版(不做粒子/光影纯CSS渐变是允许的)反白字position居中logo墙用grid灰度SVG/文字占位。原本靠纪实大照片的部分降级为CSS渐变铺底+大字照片缺失这一项还原度降约15%
- 字体:Archivo / Anton / Manrope(超粗)
**CS50单概念糖果舞台 / Candy-Color Lecture Stage** `大胆·还原94%`
- 参考:Harvard CS50(David Malan)、Lessig Method/高桥流、Presentation Zen
- 适配:教育课件、技术讲座、概念解释、代码教学
- 视觉DNA:配色=深黑底#0A0A0A+高饱和糖果色大字轮换(品红#FF2D95/青#00E5FF/明黄#FFD500/绿#39FF14)。字体=无衬线超大字漂浮居中,一屏一概念,文字极少。母版=①深黑底单个糖果色大词②等宽代码块语法高亮③舞台聚光感大字。标志=深黑漂浮糖果色大字、等宽代码高亮、强舞台聚光、极少文字
- HTML实现:深黑背景+单色超大字clamp()居中代码块用pre+等宽字+span上色做语法高亮聚光感用极淡radial-gradient暗角(非粒子光效)。还原度高
- 字体:Inter超粗 + JetBrains Mono(代码)
**玩味手绘极简 / Playful Maximalist Editorial (Collins式)** `大胆·还原75%`
- 参考:Mailchimp Brand Book(Collins 2018)、New Yorker漫画气质、Cooper圆润衬线、Cavendish荧光黄
- 适配:有态度的品牌deck、创意机构提案、文化向town hall、反SaaS极简的营销页
- 视觉DNA:配色=Cavendish荧光黄#FFE01B大面积+黑+少量撞色反SaaS极简。字体=Cooper式圆润衬线大标题(playful)+杂志式留白编排。母版=①荧光黄满底+怪诞标题②杂志式不规则留白排版③大字玩梗文案。标志=荧光黄、圆润衬线、playful编排、怪诞手绘气质(降级为几何色块/emoji替代真插画)
- HTML实现:荧光黄background圆润衬线font-family杂志留白用非对称Grid。手绘猩猩/插画这一核心元素无AI生图无法做降级为CSS几何色块+大号emoji+不规则transform旋转的文字块替代插画缺失还原度降约20%
- 字体:Fraunces(可调圆润)/ Bree Serif替代Cooper正文Inter
**不羁玩梗流行版 / Irreverent Pop (Reddit式)** `大胆·还原80%`
- 参考:Reddit Ads销售deck(被Dock列为最有性格)、David Carson式不羁排版、90年代web复古、Memphis玩味
- 适配:Z世代品牌、玩梗营销deck、社区/创作者向、敢于不正经的提案
- 视觉DNA:配色=Reddit橙红#FF4500+撞色90s web复古色。字体=混排/打破网格的David Carson式排版玩梗口语文案。母版=①fun页玩梗大字②facts页节奏转折严肃数据③口语标题。标志=打破网格混排、橙红、玩梗口语、fun→facts节奏反转、复古web质感
- HTML实现:故意打破网格用transform旋转/重叠定位/混合字号;橙红+撞色块复古质感用粗黑边border+硬阴影box-shadow(无blur)。自定义meme插画降级为emoji+几何拼贴但混排排版本身HTML可还原
- 字体:Archivo / Space Grotesk + 混搭Inter制造对比
**Y2K膨胀大字 / Maximalist 3D-Type (Wrapped式)** `大胆·还原78%`
- 参考:Spotify Wrapped 2022/2023/2025、Memphis撞色、Y2K/Maximalism、duotone人像渐变
- 适配:年度回顾(情绪出圈向)、个性化数据卡、社交分享竖屏卡、品牌年终
- 视觉DNA:配色=高饱和撞色满版背景(品红+青+橙)+Spotify绿点睛+duotone双色渐变。字体=顶天立地巨型数字,年份/数字做3D膨胀/金属质感。母版=①撞色满版+巨型膨胀数字②duotone人像/色块底+反白大字③竖屏可分享卡。标志=巨型膨胀3D数字、撞色满版、duotone渐变、年份金属质感、竖屏story卡
- HTML实现:撞色满版background3D膨胀数字用CSS text-shadow多层叠加+transform:perspective或SVG+stroke制造立体(非真3D渲染)duotone用mix-blend-mode+渐变叠在灰度图占位块上。金属质感降级为渐变填充文字background-clip:text还原度降约15%
- 字体:Archivo Black / Anton超粗 + 数字Clash Display
#### 中性派
**Bento便当格模块网格 / Bento Grid** `中性·还原95%`
- 参考:Apple Keynote Bento Grid时代、新一代MBB Bento/Big-Type deck(2024-2026)、Stripe年报指标卡矩阵、Pitch.com QBR模板
- 适配:产品功能汇总、咨询/QBR数据汇报、销售成果页、town hall指标页
- 视觉DNA:配色=浅灰/奶白底(#F5F5F7/cream)或近黑底+品牌主色+1-2强调色卡片浅色分区底+圆角+微描边/微阴影。字体=超大display标题+常规正文字重对比强烈KPI数字tabular figures。母版=①标题页巨型单句+留白②bento页2×2/3列不等高卡片每卡一洞见(数字/线性icon/sparkline)③one-insight超大数字页。标志=不等高卡片网格、圆角微描边、呼吸感
- HTML实现:CSS Grid的grid-template-areas做不等高bento卡片border-radius+box-shadow微阴影+1px hairlinesparkline用内联SVG线性icon用inline SVG stroke。零贴图
- 字体:Inter / Geist + 数字Geist Mono
**Neo-Swiss暗色终端美学 / Dark Hairline Terminal** `中性·还原94%`
- 参考:Linear pitch deck、Vercel设计语言、CS50深黑舞台课件字体Inter Tight+JetBrains Mono
- 适配:开发者工具/技术产品发布、技术路演、工程向汇报
- 视觉DNA:配色=近黑底(#0D0D0F/#111113)+hairline细线#262629网格+单一紫蓝强调(#5B5BD6/#7C7CFF)。字体=Inter Tight大标题+JetBrains Mono做标签/数据。母版=①极简标题页一句话+mono小标②hairline分隔的数据网格③mono标签的特性列表。标志=1px细线网格、mono单等宽标签、极致留白、近黑非纯黑
- HTML实现:近黑背景+border:1px solid的hairline网格mono标签用等宽font-family微光用极淡box-shadow/border highlight而非真光效(降级避开赛博霓虹禁区)。注意避开#0D1117深蓝禁区,用中性近黑
- 字体:Inter Tight + JetBrains Mono / IBM Plex Mono
**双字体咨询版 / Two-Font Consulting (Bower式)** `中性·还原90%`
- 参考:McKinsey 2019品牌系统(Wolff Olins设计Bower衬线+无衬线)、BCG Executive Perspectives、深蓝细线pattern
- 适配:咨询报告、高管汇报、行业研究、权威机构提案
- 视觉DNA:配色=深蓝(#051C2C/McKinsey深蓝)×白二元+单一品牌色高亮(BCG绿#00805A),暖灰底带呼吸感。字体=characterful衬线大标题(Bower式)与无衬线正文高对比并置。母版=①左上角结论式action-title②蓝色细线pattern装饰③杂志式左右分工(结论文字+视觉)④大数字data-point卡。标志=衬线×无衬线高对比、深蓝细线pattern、action-title、暖灰高级感
- HTML实现:双字体font-family并置(衬线标题+无衬线正文)细线pattern用repeating-linear-gradient或SVG linedata-point卡纯CSS照片灰度处理这一项无照片可省。蓝紫edge shimmer降级为纯色边
- 字体:Playfair Display / Fraunces衬线标题 + Inter正文(替代Bower)
**图谱箭头企业版 / Diagram-Driven Isotype** `中性·还原88%`
- 参考:Salesforce销售deck、Isotype(Otto Neurath)谱系、Gene Zelazny《Say It With Charts》、Hans Rosling/Gapminder
- 适配:平台/架构讲解、客户旅程、流程方法论、生态地图
- 视觉DNA:配色=企业蓝色块+产品线分色区分+图标化能力网格。字体=清晰无衬线。母版=①横向客户旅程箭头流②分层平台架构图③图标化能力网格④2×2/瀑布/金字塔结构图。标志=箭头流程、分层架构盒、Isotype图标网格、流程即叙事
- HTML实现:箭头流程用Flexbox+CSS clip-path三角或SVG arrow架构分层用嵌套带边框div图标用inline SVG stroke统一描边瀑布/金字塔用Grid+斜切。气泡图可用CSS圆形+定位。纯矢量绘制
- 字体:Inter / IBM Plex Sans(图表友好)
**单图母图概念图解 / Diagrammatic Minimalism** `中性·还原95%`
- 参考:Simon Sinek黄金圆环(Golden Circle)TED、Bauhaus几何抽象、信息建筑『一图定全场』
- 适配:理论框架讲解、TED式思想传播、模型/方法论可视化、单概念keynote
- 视觉DNA:配色=极简白/浅底+黑+1个强调色几何纯色。字体=无衬线,标签大写嵌入图形。母版=①唯一几何母图(同心圆/三角/矩阵)承载全部概念②由内向外箭头③对比案例。标志=单一几何母图、嵌套同心圆/三角、大写标签、一图承载概念
- HTML实现:同心圆用border-radius:50%嵌套div或SVG circle三角用clip-path/SVG polygon箭头SVG marker标签absolute定位贴在图形上。纯几何HTML完美还原
- 字体:Manrope / Futura系(Jost开源替代)几何感
**Sparkline叙事波形 / Narrative Sparkline (Duarte式)** `中性·还原91%`
- 参考:Nancy Duarte《Resonate》Sparkline叙事图谱、Al Gore《An Inconvenient Truth》、Duarte Inc.数据叙事
- 适配:演讲结构设计、变革叙事、before/after对照、数据故事弧线
- 视觉DNA:配色=深底或白底+品牌橙强调转折点+灰化对照。字体=无衬线annotation标注点。母版=①横贯全屏的振荡波形线②波形上text标注点③上下并置对照波形④全黑底孤悬一条数据线⑤逐步reveal。标志=横贯波形线、波形标注点、橙色转折、对照波形、爬出画面的曲线
- HTML实现:波形线用内联SVG path(平滑贝塞尔)标注点用SVG circle+text定位对照波形上下两条pathreveal用CSS动画stroke-dashoffset。纯SVG绘制无素材
- 字体:Inter + 数字Geist Mono
#### 安静派
**断言-证据 / Tufte信息设计** `安静·还原93%`
- 参考:Michael Alley Assertion-Evidence(Penn State实证)、McKinsey/BCG action-title、Edward Tufte数据墨水比、Barbara Minto金字塔原理
- 适配:学术/工程汇报、数据严谨型咨询页、政策研报、技术评审
- 视觉DNA:配色=白/极浅灰底+黑正文+单一克制强调色(深蓝/砖红)。字体=整句话标题(非名词短语),标题下独占一张图,文字标注嵌进图里。母版=①整句action-title②标题下单图证据③零bullet。标志=整句标题、单图证据、嵌入式标注、零chartjunk、高数据墨水比
- HTML实现:整句标题靠排版层级图表用纯CSS/内联SVG画极简折线散点(去网格线去图例标注直接text定位在数据点旁)零装饰。Tufte的克制正是HTML强项
- 字体:Source Serif / Lora标题 + Inter正文(双字体阅读级)
**瑞士机构极简 / Institutional Swiss Minimal** `安静·还原96%`
- 参考:Sequoia官方10页pitch模板、Airbnb 2009种子轮deck、Müller-Brockmann网格、Massimo Vignelli
- 适配:投资路演、标准商业提案、问题-解法叙事、品牌去装饰提案
- 视觉DNA:配色=纯白底+黑灰正文+单一品牌强调色(Airbnb珊瑚红#FF5A3C/中性蓝)。字体=Helvetica系无衬线标题中号粗体一句话正文短句大间距。母版=①居中logo+slogan②顶部一句话标题带+下方3栏对仗(Problem/Solution三点)③TAM大数字分层④2×2竞品矩阵。标志=顶部标题带、三栏对仗、单色强调、2×2矩阵
- HTML实现:Flexbox三栏对仗2×2矩阵纯CSS Grid+border画TAM分层用嵌套div或同心方块一页一信息。几乎纯排版网格HTML理想对象
- 字体:Inter / Helvetica Now替代Helvetica正文Inter
**杂志编辑长文流 / Editorial Longform** `安静·还原95%`
- 参考:Stripe Annual Letter($1.9T)、Amazon六页叙事备忘录、Benedict Evans『X eats the world』、Stripe Press
- 适配:年度信/复盘叙事、深度思想长文、内部更新、研报型阅读物
- 视觉DNA:配色=奶白/米白底(#FBFAF8)+深墨字+品牌色点睛(Stripe紫#635BFF)。字体=衬线或高品质无衬线,散文体段落+内联数据卡超大display数字穿插。母版=①刊头大标题②多栏散文+内联指标卡③超大数字段落锚点。标志=出版物阅读节奏、内联数据卡、克制留白、散文体而非bullet
- HTML实现:多栏column-count或Grid内联数据卡float/inline-block嵌入正文衬线正文max-width控制行宽65ch超大数字穿插。纯排版零素材
- 字体:Newsreader / Source Serif正文 + Inter辅助数字tabular
**人文圆角卡片 / Humanist Rounded Cards (Khan式)** `安静·还原80%`
- 参考:Khan Academy Wonder Blocks设计系统、Source Serif Pro衬线、森林绿品牌、友善人文主义
- 适配:教育产品、亲和力课件、公益/非盈利deck、温暖品牌提案
- 视觉DNA:配色=森林绿#14BF96/#0A5C4B+米白底+暖色辅助,柔和不刺眼。字体=Source Serif衬线标题(人文气)+无衬线正文。母版=①圆角卡片组件组②衬线标题+亲和正文③真实摄影位(降级为绿色系几何/圆角色块)。标志=森林绿、衬线标题、大圆角卡片、人文温暖、不完美亲和质感
- HTML实现:大圆角border-radius卡片+柔和box-shadow衬线标题font-family暖米白底。真实师生摄影这一项无AI生图降级为绿色系几何插画块/大圆角纯色占位+emoji人物照片缺失还原度降约18%
- 字体:Source Serif 4标题 + Nunito Sans / Inter正文(Nunito圆润呼应人文)
**研报密集图表 / Dense Research Report (Meeker式)** `安静·还原92%`
- 参考:Mary Meeker《Internet Trends》(BOND)、CB Insights《State of AI》、McKinsey Global Institute《Year in Charts》、FT/Bloomberg数据新闻
- 适配:趋势研报、行业数据复盘、密集数据汇报、市场地图
- 视觉DNA:配色=白底+品牌色(BOND/CB Insights亮蓝#0066FF)阶梯单色高亮其余灰化,几乎零留白。字体=结论式句子标题每页1图密度极小来源脚注。母版=①结论句标题+满页单图②logo网格market map③大数字KPI卡④密集多图网格+脚注。标志=结论句标题、零留白研报感、单色阶梯高亮、logo市场地图、来源脚注规范
- HTML实现:密集图表全用纯CSS/内联SVG画(柱/折线/堆叠/散点)logo market map用Grid+文字/SVG占位格KPI卡CSS脚注小字。极致信息密度正是HTML擅长零素材
- 字体:Inter + IBM Plex Sans + 数字tabular Geist Mono
**纯文字宣言备忘录 / All-Text Manifesto (Netflix/Amazon式)** `安静·还原97%`
- 参考:Netflix Culture Deck(2009125页)、Amazon六页叙事备忘录(Bezos)、Tufte反PowerPoint主张、Matthew Carter阅读级排印
- 适配:文化宣言、价值观宣讲、深度备忘录、反PPT的纯文档演示
- 视觉DNA:配色=纯白或纯黑底+单一强调色(Netflix红#E50914)做唯一高亮,极致克制。字体=阅读级排印,一页一观点金句断言/纯散文零bullet零图。母版=①满版底+金句断言②口语化坦诚段落③制度名词高亮(Keeper Test)④六页散文+附录表。标志=纯文字一页一观点、零图零bullet、单色高亮金句、口语坦诚、silent-read文档感
- HTML实现:纯排版金句用大字clamp()左对齐层级散文max-width控制行宽唯一强调色span高亮关键短语附录用极简table。零素材零图纯文字是HTML最稳的还原
- 字体:Newsreader / Source Serif(阅读级)或Inter(宣言式)标题可Archivo超粗
---
## ⚠️ AI 生图专用风格仅在确认用户有生图能力时才推default 不可选)
下面这些风格的灵魂在**动态生成视觉 / 3D / 粒子 / 电影级光影 / 手绘插画**,纯 HTML/CSS 无生图下只能做出严重劣化的 mock**从 default 推荐池剔除**。用户明确有生图能力(走 `huashu-gpt-image`)时才作为候选:
| 风格 | 灵魂 | 为什么 HTML 做不了 |
|------|------|------------------|
| Active TheoryWebGL 粒子) | 3D 粒子系统/实时渲染 | 纯 CSS 无法 |
| Field.io生成艺术 | 算法生成图形 | 静态 SVG 只能做僵化简化版 |
| Resn插画交互 | 角色插画+游戏化 | 依赖手绘素材 |
| Zach Lieberman实时生成 | creative coding 笔触 | 依赖实时生成 |
| Raven Kwok分形参数 | 递归分形 | CSS 做不出复杂度 |
| Ash Thorp电影光影 | 电影级体积光/概念美术 | CSS 光影是劣化 |
| Territory StudioFUI 全息) | 科幻全息界面 | 依赖大量发光层叠素材 |
| Neo Shen水墨晕染 | 水墨有机晕染 | CSS 渐变≠水墨 |
| Sagmeister & Walsh色彩爆发 | 手作实物+实验排版 | 撞色骨架可做已并入网页「Memphis/孟菲斯」与 PPT「单色撞色海报」手作质感做不了 |
> 这些款不是「不好」,是「载体不对」——它们的原生载体是 AI 直出图,不是浏览器 DOM。
---
## 默认审美禁区(用户可按自己品牌 override
-**GitHub-dark 偷懒解**:均匀深蓝底(#0D1117+ 通用青/紫霓虹 glow——只禁这一种烂大街组合不是「暗色一律禁」
-**不在禁区**电影级戏剧光影、暖色赛博Ash Thorp 橙/青、运动诗学暗场叙事——有作者意图的暗色保留本库「Linear 暗色发光」「黑底数字剧场」「CS50 糖果舞台」都是合法暗色)
- ❌ 激进紫渐变万能公式、emoji 当图标、圆角卡片+左彩 border accent除非品牌本身用
- ❌ 封面图加个人署名/水印
---
## 有生图能力时的提示词心法Mood, Not Layout
> 仅当走 AI 生图路径时适用HTML 路径直接按上面各风格的「HTML 实现」写代码。
短提示词 > 长提示词。描述情绪和内容,比堆 30 行布局细节有效。
| 杀死多样性的写法 | 激发创造力的写法 |
|----------------|----------------|
| 指定颜色比例60%/25%/15% | 描述情绪("warm like Sunday morning" |
| 规定布局位置 | 引用具体美学("Pentagram editorial feel" |
| 列出所有视觉元素 | 描述观众应该感受到什么 |
完整 AI 生图方法论 → `huashu-gpt-image` skill。
---
**版本**v3.02026-06 全面重构为 HTML 原生 40 种库)
**适用**:网页/PPT/PDF/信息图/封面/App 等所有视觉设计的 default HTML 路径

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 component、CSS 渐变、复杂 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 prompt、dir-card 选中、cursor、brand hyphen、focus 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 blur。v4 试过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 + 关键帧):
- v5-director-notes.mddirector's notes作者本地未随仓库分发
- v5-six-forms.htmlHTML 实施,作者本地,未随仓库分发)
- 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 基线)
- 实战项目位置:作者本地 demos 目录(含 6 + 1 视角全套文件,未随仓库分发)
- 审校 review作者本地 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,745 @@
# 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
#### 两种概览模式(自适应 + 防踩坑2026-06 重写)
打开 deck 默认进**概览**,用户未指定时按秒数随机:**网格 grid 60% / 无限画廊 gallery 40%**(可用 URL `?ov=grid|gallery``window.DECK_OVERVIEW='grid'|'gallery'` 固定)。
- **网格 grid默认主力**:用 **iframe 渲染真实子页面**(清晰、所见即所得、无需缩略图)。**自适应**:能一屏放下→对角倾斜居中铺满;页多放不下→卡片保持舒适大小、**竖向滚动**(绝不把几十页硬塞一屏缩成邮票)。
- **无限画廊 gallery**:所有页**无缝无限平铺 + 缓慢漂移 + 轻微呼吸缩放**,一个 tile 含全部页(洗牌排布,看完所有页才重复)。瓦片多,**必须用 `<img>` 缩略图**扛性能(见下),没 thumb 时回退 iframe。
🛑 **三条来自实战的硬约束(改这个文件前必读,否则会重蹈覆辙)**
1. **概览墙绝不用 `transform-style: preserve-3d` 做卡片墙**。preserve-3d 的 3D 场景里浏览器对「往后退的卡片」(顶排)命中测试不可靠 → 顶排点不到、中排时好时坏。**正解**:整墙作**单个被 3D 倾斜的平面**(不开 preserve-3d所有卡片共面点击反投影到一个平面 → 可靠。hover 用 2D `scale` 不用 `translateZ`
2. **任意页数都要自适应**:固定列数 + 给整墙写死强倾斜,页一多就溢出塌角/透视失真。必须按页数+视口算列数、行多则倾斜变平、一屏放不下就滚动。
3. **缩略图分辨率别太低**:画廊缩略图 < 1000pxhover 放大后发虚。默认 1600px。
**为画廊生成缩略图**:用 `scripts/gen_deck_thumbs.mjs`playwright 截每页 + sharp 降采样):
```bash
npm install playwright sharp
node gen_deck_thumbs.mjs --slides slides --out thumbs --width 1600
```
然后给 MANIFEST 每项加 `thumb: "thumbs/<同名>.jpg"`。网格模式忽略 thumb始终 iframe只有画廊模式用它。
### 单页验证(这是多文件架构的杀手级优势)
每张 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,183 @@
# 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/huashu-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 上传截图,拿到一个永久链接,可以粘贴到任何地方。
## 验证出错时
### 页面白屏
控制台一定有错。先检查:
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,232 @@
# 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 优先加载
### 4. `render-video-seek.js` — 真 60fps / 确定性渲染(推荐高质量交付)
`render-video.js` 的 recordVideo 路径有三个固有限制:帧率被 Chromium compositor 锁死 25fps、开头有加载黑帧需 trim、60fps 只能靠事后 minterpolate 插帧(有 ghosting + macOS QuickTime 兼容 bug`animation-pitfalls.md §14`)。需要**真 60fps、确定性输出、或交付 B站/作品集**时,改用 seek 渲染。
它逐帧 seek 到时间戳截图、再用 ffmpeg 把 PNG 序列编码成 MP4。技术内核借鉴 HeyGen HyperFramesApache 2.0)的「冻结时钟 + seek 截图」思路,但不引入任何第三方包——只用本 skill 已有的 playwright + ffmpegruntime 中立。
```bash
NODE_PATH=$(npm root -g) node /path/to/claude-design/scripts/render-video-seek.js <html文件> --fps=60
```
参数:`--duration` · `--fps`(默认 60· `--width` · `--height` · `--concurrency`(默认 4 个 worker 并行)· `--settle`seek 后等几个 rAF 再截图,默认 2重 layout 动画可调高)· `--keep-chrome`。输出与 HTML 同目录、同名 `.mp4`
正面解决 recordVideo 三死结:
- **真原生任意帧率**`--fps=60` 出真 60fps每帧都是真实 seek 画面),不再经 `convert-formats.sh` 的 minterpolate 插帧,绕开 ghosting + macOS 兼容 bug
- **无开头黑帧**:不录屏,根本没有加载期黑帧,不需要 `--trim` / `--fontwait`
- **确定性**seek 到时间戳截图,同输入同输出,不受机器负载/丢帧影响
**适用边界(重要)**:只支持走 Stage 时钟的动画——`assets/animations.jsx``<Stage>``narration_stage.jsx``<NarrationStage>`,它们会响应 `window.__seekRender` 冻结自驱时钟并暴露 `window.__seek(t)`。纯 CSS `@keyframes` / Lottie / 手写非 Stage 动画不吃 `__seek`,这类继续用 `render-video.js`(脚本检测不到 `__seek` 会报错并提示)。
**代价**:逐帧截图,长视频总耗时可能比 recordVideo 实时录更久(靠 `--concurrency` 多 worker 缓解);大量临时 PNG 占盘,渲染前建议关其他大内存 App。
**二选一策略**:默认仍用 `render-video.js`(零风险、覆盖所有动画类型);需要真 60fps / 确定性 / 高质量交付、且动画走 Stage 时钟时,用 `render-video-seek.js`。带解说的长动画用 `render-narration.sh --seek` 一键走 seek 渲染 + 混音。
## 完整流程(标准推荐)
用户说「导出视频」后:
```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结束。