chore: ruler files update
Signed-off-by: Dmytro Stanchiev <git@dmytros.dev>
This commit is contained in:
@@ -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)³`)**不够锐**——起步不够快、停顿不够稳。
|
||||
|
||||
### 三个核心 Easing(animations.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.1,opacity 保持,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 → 1,blur 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 / 代码 / 数据**:无衬线 + 等宽
|
||||
|
||||
**单一字体都是不对的**。衬线给「品位」,无衬线给「功能」。
|
||||
|
||||
具体字体选择走品牌 spec(brand-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-up(30ms 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° → 像「错切」而不是「透视」
|
||||
- 8° × -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/s),SFX 音高调到 BGM 音阶
|
||||
**BGM**:IDM / 极简科技电子,冷静+精密
|
||||
**收束**:镜头急拉远 → drop → Logo 形变 → 空灵单音 → 戛然而止
|
||||
|
||||
### 配方 B · 一镜到底工具式(Claude Code 类)
|
||||
|
||||
**适合**:开发者工具、生产力 App、心流场景
|
||||
**节奏**:持续稳定 flow,没有明显峰值
|
||||
**Easing**:`spring` 物理 + `expoOut`
|
||||
**SFX 密度**:**0**(纯靠 BGM 驱动剪辑节奏)
|
||||
**BGM**:Lo-fi Hip-hop / Boom-bap,85-90 BPM
|
||||
**核心技巧**:关键 UI 动作踩在 BGM kick/snare 瞬态上——「**音乐律动即交互音效**」
|
||||
|
||||
### 配方 C · 办公效率叙事式(Claude for Word 类)
|
||||
|
||||
**适合**:企业软件、文档/表格/日历类、专业感优先
|
||||
**节奏**:多 scene 硬切 + Dolly In/Out
|
||||
**Easing**:`overshoot`(toggle)+ `expoOut`(面板)
|
||||
**SFX 密度**:中(~0.3/s),UI 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 种设计哲学。
|
||||
380
.claude/skills/huashu-design/references/animation-pitfalls.md
Normal file
380
.claude/skills/huashu-design/references/animation-pitfalls.md
Normal file
@@ -0,0 +1,380 @@
|
||||
# Animation Pitfalls:HTML 动画踩过的坑与规则
|
||||
|
||||
做动画时最常踩的 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) 之间,主句子已经 hidden,zoom1 fade out(0.6s)+ zoom2 fade in(0.6s)+ stagger delay(0.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 in(cross-fade)
|
||||
```
|
||||
- 或者用一个「锚点元素」(如主句子)作为场景之间的视觉连接,zoom 切换期间它短暂回显
|
||||
- 配 CSS transition 的 duration 算清楚,避免 transition 还没结束就触发下一个
|
||||
|
||||
## 5. Pure Render 原则 —— 动画状态应可 seek
|
||||
|
||||
**踩的坑**:用 `setTimeout` + `fireOnce(key, fn)` 链式触发动画状态。正常播放没问题,但做逐帧录制/seek到任意时间点时,之前的 setTimeout 已经执行过就无法「回到过去」。
|
||||
|
||||
**规则**:
|
||||
- `render(t)` 函数理想上是 **pure function**:给定 t 输出唯一 DOM 状态
|
||||
- 如果必须用副作用(如 class 切换),用 `fired` set 配合显式 reset:
|
||||
```js
|
||||
const fired = new Set();
|
||||
function fireOnce(key, fn) { if (!fired.has(key)) { fired.add(key); fn(); } }
|
||||
function reset() { fired.clear(); /* 清所有 .show class */ }
|
||||
```
|
||||
- 暴露 `window.__seek(t)` 供 Playwright / 调试用:
|
||||
```js
|
||||
window.__seek = (t) => { reset(); render(t); };
|
||||
```
|
||||
- 动画相关的 setTimeout 不要跨越 >1 秒,否则 seek 回跳时会乱套
|
||||
|
||||
## 6. 字体加载前测量 = 测错
|
||||
|
||||
**踩的坑**:页面一 DOMContentLoaded 就调用 `charRect(idx)` 测量 bracket 位置,字体还没加载,每个字符宽度是 fallback 字体的宽度,位置全错。等字体一加载(约 500ms 后),bracket 的 `left: Xpx` 还是老值,永久偏移。
|
||||
|
||||
**规则**:
|
||||
- 任何依赖 DOM 测量(`getBoundingClientRect`、`offsetWidth`)的布局代码,**必须**包在 `document.fonts.ready.then()` 里
|
||||
```js
|
||||
document.fonts.ready.then(() => {
|
||||
requestAnimationFrame(() => {
|
||||
buildBrackets(...); // 此时字体已就绪,测量准确
|
||||
tick(); // 动画开始
|
||||
});
|
||||
});
|
||||
```
|
||||
- 额外的 `requestAnimationFrame` 给浏览器一帧时间提交 layout
|
||||
- 如果用 Google Fonts CDN,`<link rel="preconnect">` 加速首次加载
|
||||
|
||||
## 7. 录制准备 —— 为视频导出预留抓手
|
||||
|
||||
**踩的坑**:Playwright `recordVideo` 默认 25fps,从 context 创建就开始录。页面加载、字体加载的前 2 秒都被录进去。交付时视频前面 2 秒空白/闪白。
|
||||
|
||||
**规则**:
|
||||
- 提供 `render-video.js` 工具处理:warmup navigate → reload 重启动画 → 等 duration → ffmpeg trim head + 转 H.264 MP4
|
||||
- 动画的**第 0 帧**要是最终布局已就位的完整初始状态(不是空白或加载中)
|
||||
- 想要 60fps?用 ffmpeg `minterpolate` 后处理,不指望浏览器源帧率
|
||||
- 想要 GIF?两阶段 palette(`palettegen` + `paletteuse`),对 30s 1080p 动画能压到 3MB
|
||||
|
||||
参见 `video-export.md` 获取完整脚本调用方式。
|
||||
|
||||
## 8. 批量导出 —— tmp 目录必须带 PID 防并发冲突
|
||||
|
||||
**踩的坑**:用 `render-video.js` 3 个进程并行录 3 个 HTML。因为 TMP_DIR 只用 `Date.now()` 命名,3 个进程同毫秒启动时共用同一个 tmp 目录。最先完成的进程清理 tmp,另外两个读目录时 `ENOENT`,全部崩溃。
|
||||
|
||||
**规则**:
|
||||
- 任何多进程可能共用的临时目录,命名必须带 **PID 或随机后缀**:
|
||||
```js
|
||||
const TMP_DIR = path.join(DIR, '.video-tmp-' + Date.now() + '-' + process.pid);
|
||||
```
|
||||
- 如果确实想多文件并行,用 shell 的 `&` + `wait` 而不是在一个 node 脚本里 fork
|
||||
- 批量录多个 HTML 时,保守做法:**串行**运行(2 个以内可并行,3 个以上老实排队)
|
||||
|
||||
## 9. 录屏里有进度条/重播按钮 —— Chrome 元素污染视频
|
||||
|
||||
**踩的坑**:动画 HTML 加了 `.progress` 进度条、`.replay` 重播按钮、`.counter` 时间戳,方便人类调试播放。录成 MP4 交付时这些元素出现在视频底部,像把开发者工具截进去了一样。
|
||||
|
||||
**规则**:
|
||||
- HTML 里给人类用的「chrome 元素」(progress bar / replay button / footer / masthead / counter / phase labels)和视频内容本体分开管理
|
||||
- **约定 class 名** `.no-record`:任何带这个 class 的元素,录屏脚本自动隐藏
|
||||
- 脚本端(`render-video.js`)默认注入 CSS 隐藏常见 chrome class 名:
|
||||
```
|
||||
.progress .counter .phases .replay .masthead .footer .no-record [data-role="chrome"]
|
||||
```
|
||||
- 用 Playwright 的 `addInitScript` 注入(会在每次 navigate 前生效,reload 也稳)
|
||||
- 想看原样 HTML(带 chrome)时加 `--keep-chrome` flag
|
||||
|
||||
## 10. 录屏开头几秒动画重复 —— Warmup 帧泄漏
|
||||
|
||||
**踩的坑**:`render-video.js` 的旧流程 `goto → wait fonts 1.5s → reload → wait duration`。录制从 context 创建就开始,warmup 阶段动画已经播了一段,reload 后从 0 重启。结果视频前几秒是「动画中段 + 切换 + 动画从 0 开始」,重复感强。
|
||||
|
||||
**规则**:
|
||||
- **Warmup 和 Record 必须用独立的 context**:
|
||||
- Warmup context(无 `recordVideo` 选项):只负责 load url、等字体、然后 close
|
||||
- Record context(有 `recordVideo`):fresh 状态开始,animation 从 t=0 开始录
|
||||
- ffmpeg `-ss trim` 只能裁 Playwright 的一点点 startup latency(~0.3s),**不能**用来掩盖 warmup 帧;源头要干净
|
||||
- 录制 context 关闭 = webm 文件写入磁盘,这是 Playwright 的约束
|
||||
- 相关代码模式:
|
||||
```js
|
||||
// Phase 1: warmup (throwaway)
|
||||
const warmupCtx = await browser.newContext({ viewport });
|
||||
const warmupPage = await warmupCtx.newPage();
|
||||
await warmupPage.goto(url, { waitUntil: 'networkidle' });
|
||||
await warmupPage.waitForTimeout(1200);
|
||||
await warmupCtx.close();
|
||||
|
||||
// Phase 2: record (fresh)
|
||||
const recordCtx = await browser.newContext({ viewport, recordVideo });
|
||||
const page = await recordCtx.newPage();
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(DURATION * 1000);
|
||||
await page.close();
|
||||
await recordCtx.close();
|
||||
```
|
||||
|
||||
## 11. 画面内别画「伪 chrome」—— 装饰版 player UI 与真 chrome 撞车
|
||||
|
||||
**踩的坑**:动画用 `Stage` 组件,已经自带 scrubber + 时间码 + 暂停按钮(属于 `.no-record` chrome,导出时自动隐藏)。我又在画面底部画了一条「`00:60 ──── CLAUDE-DESIGN / ANATOMY`」的"杂志页码感装饰进度条",自我感觉良好。**结果**:用户看到两条进度条——一条是 Stage 控制器,一条是我画的装饰。视觉上完全撞车,认定为 bug。「视频内还有个进度条是怎么回事?」
|
||||
|
||||
**规则**:
|
||||
|
||||
- Stage 已经提供:scrubber + 时间码 + 暂停/重播按钮。**画面内不要再画**进度指示、当前时间码、版权署名条、章节计数器——它们要么和 chrome 撞车,要么就是 filler slop(违反「earn its place」原则)。
|
||||
- 「页码感」「杂志感」「底部署名条」这些**装饰诉求**,是 AI 自动加上的高频 filler。每一个出现都要警觉——它真的传达了不可替代的信息吗?还是单纯填满空白?
|
||||
- 如果你坚信某个底部条带必须存在(例如:动画主题就是讲 player UI),那它必须**叙事必要**,且**视觉上和 Stage scrubber 显著区分**(不同位置、不同形式、不同色调)。
|
||||
|
||||
**元素归属测试**(每个画进 canvas 的元素必须能回答):
|
||||
|
||||
| 它属于什么 | 处理 |
|
||||
|------------|------|
|
||||
| 某一幕的叙事内容 | OK,留着 |
|
||||
| 全局 chrome(控制/调试用) | 加 `.no-record` class,导出时隐藏 |
|
||||
| **既不属于任何幕,又不是 chrome** | **删**。这就是无主之物,必然是 filler slop |
|
||||
|
||||
**自检(交付前 3 秒)**:截一张静态图,问自己——
|
||||
|
||||
- 画面里有没有「看起来像 video player UI 的东西」(横线进度条、时间码、控制按钮模样)?
|
||||
- 如果有,删掉它叙事是否有损?无损就删。
|
||||
- 同一类信息(进度/时间/署名)有没有出现两次?合并到 chrome 一处。
|
||||
|
||||
**反例**:底部画 `00:42 ──── PROJECT NAME`、画面右下角画"CH 03 / 06"章节计数、画面边缘画版本号"v0.3.1"——都是伪 chrome filler。
|
||||
|
||||
## 12. 录屏前置空白 + 录屏起点偏移 —— `__ready` × tick × lastTick 三联陷阱
|
||||
|
||||
**踩的坑(A · 前置空白)**:60 秒动画导出 MP4,前 2-3 秒是空白页面。`ffmpeg --trim=0.3` 剪不掉。
|
||||
|
||||
**踩的坑(B · 起点偏移,2026-04-20 真实事故)**:导出 24 秒视频,用户观感「视频 19 秒才开始播第一帧」。实际上动画从 t=5 开始录,录到 t=24 后 loop 回 t=0,再录 5 秒到 end——所以视频最后 5 秒才是动画真正的开头。
|
||||
|
||||
**根因**(两个坑共享一个根因):
|
||||
|
||||
Playwright `recordVideo` 从 `newContext()` 那一刻就开始写 WebM,此时 Babel/React/字体加载共耗时 L 秒(2-6s)。录屏脚本等 `window.__ready = true` 作为「动画从这里开始」的锚点——它和动画 `time = 0` 必须严格 pair。有两种常见错法:
|
||||
|
||||
| 错法 | 症状 |
|
||||
|------|------|
|
||||
| `__ready` 在 `useEffect` 或同步 setup 阶段设(在 tick 第一帧之前) | 录屏脚本以为动画开始了,实际 WebM 还在录空白页 → **前置空白** |
|
||||
| tick 的 `lastTick = performance.now()` 在**脚本顶层**初始化 | 字体加载 L 秒被算进首帧 `dt`,`time` 瞬间跳到 L → 录屏全程滞后 L 秒 → **起点偏移** |
|
||||
|
||||
**✅ 正确的完整 starter tick 模板**(手写动画必须用这个骨架):
|
||||
|
||||
```js
|
||||
// ━━━━━━ state ━━━━━━
|
||||
let time = 0;
|
||||
let playing = false; // ❗ 默认不播,等字体 ready 再启动
|
||||
let lastTick = null; // ❗ sentinel——tick 首帧时 dt 强制为 0(别用 performance.now())
|
||||
const fired = new Set();
|
||||
|
||||
// ━━━━━━ tick ━━━━━━
|
||||
function tick(now) {
|
||||
if (lastTick === null) {
|
||||
lastTick = now;
|
||||
window.__ready = true; // ✅ pair:「录屏起点」与「动画 t=0」同一帧
|
||||
render(0); // 再渲一次确保 DOM 就绪(此时字体已 ready)
|
||||
requestAnimationFrame(tick);
|
||||
return;
|
||||
}
|
||||
const dt = (now - lastTick) / 1000; // 首帧之后 dt 才开始推进
|
||||
lastTick = now;
|
||||
|
||||
if (playing) {
|
||||
let t = time + dt;
|
||||
if (t >= DURATION) {
|
||||
t = window.__recording ? DURATION - 0.001 : 0; // 录制时不 loop,留 0.001s 保留末帧
|
||||
if (!window.__recording) fired.clear();
|
||||
}
|
||||
time = t;
|
||||
render(time);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
// ━━━━━━ boot ━━━━━━
|
||||
// 不要在顶层立即 rAF——等字体加载完才启动
|
||||
document.fonts.ready.then(() => {
|
||||
render(0); // 先把初始画面画出来(字体已就绪)
|
||||
playing = true;
|
||||
requestAnimationFrame(tick); // 首次 tick 会 pair __ready + t=0
|
||||
});
|
||||
|
||||
// ━━━━━━ seek 接口(供 render-video 防御性矫正用)━━━━━━
|
||||
window.__seek = (t) => { fired.clear(); time = t; lastTick = null; render(t); };
|
||||
```
|
||||
|
||||
**为什么这个模板对**:
|
||||
|
||||
| 环节 | 为什么必须这样 |
|
||||
|------|-------------|
|
||||
| `lastTick = null` + 首帧 `return` | 避免「脚本加载到 tick 首次执行」的 L 秒被算进动画时间 |
|
||||
| `playing = false` 默认 | 字体加载期间 `tick` 即使运行也不推进 time,避免渲染错位 |
|
||||
| `__ready` 在 tick 首帧设 | 录屏脚本此刻开始计时,对应的画面是动画真正的 t=0 |
|
||||
| `document.fonts.ready.then(...)` 里才启动 tick | 规避字体 fallback 宽度测量、避免首帧字体跳变 |
|
||||
| `window.__seek` 存在 | 让 `render-video.js` 可以主动矫正——第二道防线 |
|
||||
|
||||
**录屏脚本端的对应防御**:
|
||||
1. `addInitScript` 注入 `window.__recording = true`(先于 page goto)
|
||||
2. `waitForFunction(() => window.__ready === true)`,记录此刻偏移作为 ffmpeg trim
|
||||
3. **额外**:`__ready` 之后主动 `page.evaluate(() => window.__seek && window.__seek(0))`,把 HTML 可能的 time 偏差强制归零——这是第二道防线,对付不严格遵守 starter 模板的 HTML
|
||||
|
||||
**验证方法**:导出 MP4 后
|
||||
```bash
|
||||
ffmpeg -i video.mp4 -ss 0 -vframes 1 frame-0.png
|
||||
ffmpeg -i video.mp4 -ss $DURATION-0.1 -vframes 1 frame-end.png
|
||||
```
|
||||
首帧必须是动画 t=0 的初始状态(不是中段,不是黑),末帧必须是动画终态(不是第二轮 loop 的某个时刻)。
|
||||
|
||||
**参考实现**:`assets/animations.jsx` 的 Stage 组件、`scripts/render-video.js` 都已按此协议实现。手写 HTML 必须套 starter tick 模板——每一行都是防过具体 bug。
|
||||
|
||||
## 13. 录制时禁止 loop —— `window.__recording` 信号
|
||||
|
||||
**踩的坑**:动画 Stage 默认 `loop=true`(浏览器里方便看效果)。`render-video.js` 录完 duration 秒还多等 300ms 缓冲才停止,这 300ms 让 Stage 进入下一循环。ffmpeg `-t DURATION` 截取时,最后 0.5-1s 落入下一循环——视频结尾突然回到第一帧(Scene 1),观众以为视频出 bug。
|
||||
|
||||
**根因**:录制脚本和 HTML 之间没有"我在录制"的握手协议。HTML 不知道自己被录,依然按浏览器交互场景循环。
|
||||
|
||||
**规则**:
|
||||
|
||||
1. **录制脚本**:在 `addInitScript` 里注入 `window.__recording = true`(先于 page goto):
|
||||
```js
|
||||
await recordCtx.addInitScript(() => { window.__recording = true; });
|
||||
```
|
||||
|
||||
2. **Stage 组件**:识别这个信号,强制 loop=false:
|
||||
```js
|
||||
const effectiveLoop = (typeof window !== 'undefined' && window.__recording) ? false : loop;
|
||||
// ...
|
||||
if (next >= duration) return effectiveLoop ? 0 : duration - 0.001;
|
||||
// ↑ 留 0.001 防止 Sprite end=duration 被关掉
|
||||
```
|
||||
|
||||
3. **结尾 Sprite 的 fadeOut**:录制场景下应设 `fadeOut={0}`,否则视频末尾会渐变到透明/暗色——用户期望停在清晰的最后一帧,不是淡出。手写 HTML 时建议结尾 Sprite 都用 `fadeOut={0}`。
|
||||
|
||||
**参考实现**:`assets/animations.jsx` 的 Stage / `scripts/render-video.js` 都已内置握手。手写 Stage 必须实现 `__recording` 检测——否则录制必踩这个坑。
|
||||
|
||||
**验证**:导出 MP4 后 `ffmpeg -ss 19.8 -i video.mp4 -frames:v 1 end.png`,检查倒数 0.2 秒是否还是预期最后一帧,没有突然切换到另一个 scene。
|
||||
|
||||
## 14. 60fps 视频默认用帧复制 —— minterpolate 兼容性差
|
||||
|
||||
**踩的坑**:`convert-formats.sh` 用 `minterpolate=fps=60:mi_mode=mci...` 生成的 60fps MP4,在 macOS QuickTime / Safari 部分版本下无法打开(一片黑或直接拒打)。VLC / Chrome 能打开。
|
||||
|
||||
**根因**:minterpolate 输出的 H.264 elementary stream 包含某些播放器解析有问题的 SEI / SPS 字段。
|
||||
|
||||
**规则**:
|
||||
|
||||
- 默认 60fps 用简单 `fps=60` filter(帧复制),兼容性广(QuickTime/Safari/Chrome/VLC 都能开)
|
||||
- 高质量插帧用 `--minterpolate` flag 显式启用——但**必须本地测过**目标播放器再交付
|
||||
- 60fps 标签价值是**上传平台的算法识别**(Bilibili / YouTube 上 60fps 标记会优先推流),实际感知流畅度对 CSS 动画来说提升微弱
|
||||
- 加 `-profile:v high -level 4.0` 提升 H.264 通用兼容性
|
||||
|
||||
**`convert-formats.sh` 已默认改成兼容模式**。如果你需要插帧高质量,加 `--minterpolate` flag:
|
||||
```bash
|
||||
bash convert-formats.sh input.mp4 --minterpolate
|
||||
```
|
||||
|
||||
## 15. `file://` + 外部 `.jsx` 的 CORS 陷阱 —— 单文件交付必须内联引擎
|
||||
|
||||
**踩的坑**:动画 HTML 里用 `<script type="text/babel" src="animations.jsx"></script>` 外部加载引擎。本机双击打开(`file://` 协议)→ Babel Standalone 走 XHR 拉 `.jsx` → Chrome 报 `Cross origin requests are only supported for protocol schemes: http, https, chrome, chrome-extension...` → 整页黑屏,不报 `pageerror` 只报 console error,很容易当"动画没触发"误诊。
|
||||
|
||||
启 HTTP server 也未必救得了——本机有全局代理时 `localhost` 也会走代理,返回 502 / 连接失败。
|
||||
|
||||
**规则**:
|
||||
|
||||
- **单文件交付(双击打开即用的 HTML)** → `animations.jsx` 必须**内联**到 `<script type="text/babel">...</script>` 标签内,不要用 `src="animations.jsx"`
|
||||
- **多文件项目(起 HTTP server 演示)** → 可以外部加载,但交付时明确写清 `python3 -m http.server 8000` 命令
|
||||
- 判断标准:交付给用户的是"HTML 文件"还是"带 server 的项目目录"?前者用内联
|
||||
- Stage 组件 / animations.jsx 经常 200+ 行——贴进 HTML `<script>` 块完全可接受,别怕体积
|
||||
|
||||
**最小验证**:双击你生成的 HTML,**不要**通过任何 server 打开。如果 Stage 正常显示动画首帧,才算通过。
|
||||
|
||||
## 16. 跨 scene 反色上下文 —— 画面内元素不要硬编码颜色
|
||||
|
||||
**踩的坑**:做多场景动画时,`ChapterLabel` / `SceneNumber` / `Watermark` 等**跨 scene 都出现**的元素,在组件里写死 `color: '#1A1A1A'`(深色文字)。前 4 个 scene 浅底 OK,到第 5 个黑底 scene 时"05"和水印直接消失——不报错、不触发任何检查、关键信息隐形。
|
||||
|
||||
**规则**:
|
||||
|
||||
- **跨多 scene 复用的画面内元素**(chapter 标签 / scene 编号 / 时间码 / 水印 / 版权条)**禁止硬编码颜色值**
|
||||
- 改用三种方式之一:
|
||||
1. **`currentColor` 继承**:元素只写 `color: currentColor`,父 scene 容器设 `color: 计算值`
|
||||
2. **invert prop**:组件接受 `<ChapterLabel invert />` 手动切换深浅
|
||||
3. **基于底色自动计算**:`color: contrast-color(var(--scene-bg))`(CSS 4 新 API,或 JS 判断)
|
||||
- 交付前用 Playwright 抽**每个 scene 的代表帧**,人眼过一遍"跨 scene 元素"是否都可见
|
||||
|
||||
这条坑的隐蔽性在于——**没有 bug 报警**。只有人眼或 OCR 能发现。
|
||||
|
||||
## 快速自查清单(开工前 5 秒)
|
||||
|
||||
- [ ] 每个 `position: absolute` 的父元素都有 `position: relative`?
|
||||
- [ ] 动画里的特殊字符(`␣` `⌘` `emoji`)都在字体里存在?
|
||||
- [ ] Grid/Flex 模板的 count 和 JS 数据的 length 一致?
|
||||
- [ ] 场景切换之间有 cross-fade,没有 >0.3s 的纯空白?
|
||||
- [ ] DOM 测量代码包在 `document.fonts.ready.then()` 里?
|
||||
- [ ] `render(t)` 是 pure 的,或有明确的 reset 机制?
|
||||
- [ ] 第 0 帧是完整初始状态,不是空白?
|
||||
- [ ] 画面内没有「伪 chrome」装饰(进度条/时间码/底部署名条与 Stage scrubber 撞车)?
|
||||
- [ ] 动画 tick 第一帧同步设 `window.__ready = true`?(用 animations.jsx 自带;手写 HTML 自己加)
|
||||
- [ ] Stage 检测 `window.__recording` 强制 loop=false?(手写 HTML 必加)
|
||||
- [ ] 结尾 Sprite 的 `fadeOut` 设为 0(视频末尾停清晰帧)?
|
||||
- [ ] 60fps MP4 默认用帧复制模式(兼容性),高质量插帧才加 `--minterpolate`?
|
||||
- [ ] 导出后抽第 0 帧 + 末帧验证是动画初始/最终状态?
|
||||
- [ ] 涉及具体品牌(Stripe/Anthropic/Lovart/...):走完了「品牌资产协议」(SKILL.md §1.a 五步)?有没有写 `brand-spec.md`?
|
||||
- [ ] 单文件交付的 HTML:`animations.jsx` 是内联的,不是 `src="..."`?(file:// 下 external .jsx 会 CORS 黑屏)
|
||||
- [ ] 跨 scene 出现的元素(chapter 标签/水印/scene 编号)没有硬编码颜色?在每个 scene 底色下都可见?
|
||||
249
.claude/skills/huashu-design/references/animations.md
Normal file
249
.claude/skills/huashu-design/references/animations.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Animations:时间轴动画引擎
|
||||
|
||||
做动画/motion design HTML时读这个。原理、用法、典型模式。
|
||||
|
||||
## 核心模式:Stage + Sprite
|
||||
|
||||
我们的动画系统(`assets/animations.jsx`)提供一个时间轴驱动的引擎:
|
||||
|
||||
- **`<Stage>`**:整个动画的容器,自动提供auto-scale(fit 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-in,signal就失效。
|
||||
|
||||
### 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 MP4(Playwright + 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%的情况够用。
|
||||
@@ -0,0 +1,338 @@
|
||||
# Apple Gallery Showcase · 画廊展示墙动画风格
|
||||
|
||||
> 灵感来源:Claude Design 官网 hero 视频 + 苹果产品页「作品墙」式陈列
|
||||
> 实战出处:huashu-design 发布 hero v5
|
||||
> 适用场景:**产品发布 hero 动画、skill 能力演示、作品集展示**——任何需要把「多件高质量产出」同时展陈并引导观众注意力的场景
|
||||
|
||||
---
|
||||
|
||||
## 触发判断:什么时候用这个风格
|
||||
|
||||
**适合**:
|
||||
- 有10张以上真实产出要同屏展示(PPT、App、网页、信息图)
|
||||
- 观众是专业受众(开发者、设计师、产品经理),对「质感」敏感
|
||||
- 希望传递的气质是「克制、展览式、高级、有空间感」
|
||||
- 需要焦点和全局同时存在(看细节但不失整体)
|
||||
|
||||
**不适合**:
|
||||
- 单产品聚焦(用 frontend-design 的产品 hero 模板)
|
||||
- 情绪向/故事性强的动画(用时间轴叙事模板)
|
||||
- 小屏幕 / 竖屏(倾斜视角在小画面上会糊)
|
||||
|
||||
---
|
||||
|
||||
## 核心视觉 Token
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 浅色画廊调板 */
|
||||
--bg: #F5F5F7; /* 主画布底 — 苹果官网灰 */
|
||||
--bg-warm: #FAF9F5; /* 温暖米白变体 */
|
||||
--ink: #1D1D1F; /* 主字色 */
|
||||
--ink-80: #3A3A3D;
|
||||
--ink-60: #545458;
|
||||
--muted: #86868B; /* 次级文字 */
|
||||
--dim: #C7C7CC;
|
||||
--hairline: #E5E5EA; /* 卡片1px边框 */
|
||||
--accent: #D97757; /* 赤陶橙 — Claude brand */
|
||||
--accent-deep:#B85D3D;
|
||||
|
||||
--serif-cn: "Noto Serif SC", "Songti SC", Georgia, serif;
|
||||
--serif-en: "Source Serif 4", "Tiempos Headline", Georgia, serif;
|
||||
--sans: "Inter", -apple-system, "PingFang SC", system-ui;
|
||||
--mono: "JetBrains Mono", "SF Mono", ui-monospace;
|
||||
}
|
||||
```
|
||||
|
||||
**关键原则**:
|
||||
1. **绝不用纯黑底**。黑底会让作品看起来像电影、不像「可以被采用的工作成果」
|
||||
2. **赤陶橙是唯一色相accent**,其他全部是灰阶 + 白
|
||||
3. **三字体栈**(serif英+serif中+sans+mono)营造「出版物」而非「互联网产品」的气质
|
||||
|
||||
---
|
||||
|
||||
## 核心布局模式
|
||||
|
||||
### 1. 悬浮卡片(整个风格的基本单元)
|
||||
|
||||
```css
|
||||
.gallery-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 14px;
|
||||
padding: 6px; /* 内边距是「装裱纸」 */
|
||||
border: 1px solid var(--hairline);
|
||||
box-shadow:
|
||||
0 20px 60px -20px rgba(29, 29, 31, 0.12), /* 主阴影,软且长 */
|
||||
0 6px 18px -6px rgba(29, 29, 31, 0.06); /* 第二层近光,制造浮感 */
|
||||
aspect-ratio: 16 / 9; /* 统一 slide 比例 */
|
||||
overflow: hidden;
|
||||
}
|
||||
.gallery-card img {
|
||||
width: 100%; height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 9px; /* 比卡片圆角略小,视觉嵌套 */
|
||||
}
|
||||
```
|
||||
|
||||
**反面教材**:不要贴边瓷砖(无padding无border无shadow)——那是信息图密度表达,不是展览。
|
||||
|
||||
### 2. 3D倾斜作品墙
|
||||
|
||||
```css
|
||||
.gallery-viewport {
|
||||
position: absolute; inset: 0;
|
||||
overflow: hidden;
|
||||
perspective: 2400px; /* 深一些的透视,倾斜不夸张 */
|
||||
perspective-origin: 50% 45%;
|
||||
}
|
||||
.gallery-canvas {
|
||||
width: 4320px; /* 画布 = 2.25× viewport */
|
||||
height: 2520px; /* 留出pan空间 */
|
||||
transform-origin: center center;
|
||||
transform: perspective(2400px)
|
||||
rotateX(14deg) /* 向后倾 */
|
||||
rotateY(-10deg) /* 向左转 */
|
||||
rotateZ(-2deg); /* 轻微倾斜,去掉太规整 */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(8, 1fr);
|
||||
gap: 40px;
|
||||
padding: 60px;
|
||||
}
|
||||
```
|
||||
|
||||
**参数 sweet spot**:
|
||||
- rotateX: 10-15deg(再多就像开酒会 VIP 背景板)
|
||||
- rotateY: ±8-12deg(左右对称感)
|
||||
- rotateZ: ±2-3deg(「这不是机器摆的」的人味)
|
||||
- perspective: 2000-2800px(小于2000会鱼眼,大于3000接近正投影)
|
||||
|
||||
### 3. 2×2 四角汇聚(选择场景)
|
||||
|
||||
```css
|
||||
.grid22 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 800px);
|
||||
gap: 56px 64px;
|
||||
align-items: start;
|
||||
}
|
||||
```
|
||||
|
||||
每张卡片从对应角落(tl/tr/bl/br)向中心滑入 + fade in。对应的 `cornerEntry` 向量:
|
||||
|
||||
```js
|
||||
const cornerEntry = {
|
||||
tl: { dx: -700, dy: -500 },
|
||||
tr: { dx: 700, dy: -500 },
|
||||
bl: { dx: -700, dy: 500 },
|
||||
br: { dx: 700, dy: 500 },
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五种核心动画模式
|
||||
|
||||
### 模式 A · 四角汇聚(0.8-1.2s)
|
||||
|
||||
4 个元素从视口四角滑入,同时缩放 0.85→1.0,对应 ease-out。适合「展示多方向选择」的开场。
|
||||
|
||||
```js
|
||||
const inP = easeOut(clampLerp(t, start, end));
|
||||
card.style.transform = `translate3d(${(1-inP)*ce.dx}px, ${(1-inP)*ce.dy}px, 0) scale(${0.85 + 0.15*inP})`;
|
||||
card.style.opacity = inP;
|
||||
```
|
||||
|
||||
### 模式 B · 选中放大 + 其他滑出(0.8s)
|
||||
|
||||
被选中的卡片放大 1.0→1.28,其他卡片 fade out + blur + 向四角漂回:
|
||||
|
||||
```js
|
||||
// 被选中
|
||||
card.style.transform = `translate3d(${cellDx*outP}px, ${cellDy*outP}px, 0) scale(${1 + 0.28*easeOut(zoomP)})`;
|
||||
// 未选中
|
||||
card.style.opacity = 1 - outP;
|
||||
card.style.filter = `blur(${outP * 1.5}px)`;
|
||||
```
|
||||
|
||||
**关键**:未选中的要 blur,不是纯 fade。blur 模拟景深,视觉上把被选中的「推出来」。
|
||||
|
||||
### 模式 C · Ripple 涟漪展开(1.7s)
|
||||
|
||||
从中心向外,按距离 delay,每张卡片依次淡入 + 从 1.25x 缩到 0.94x(「镜头拉远」):
|
||||
|
||||
```js
|
||||
const col = i % COLS, row = Math.floor(i / COLS);
|
||||
const dc = col - (COLS-1)/2, dr = row - (ROWS-1)/2;
|
||||
const dist = Math.sqrt(dc*dc + dr*dr);
|
||||
const delay = (dist / maxDist) * 0.8;
|
||||
const localT = Math.max(0, (t - rippleStart - delay) / 0.7);
|
||||
card.style.opacity = easeOut(Math.min(1, localT));
|
||||
|
||||
// 同时整体 scale 1.25→0.94
|
||||
const galleryScale = 1.25 - 0.31 * easeOut(rippleProgress);
|
||||
```
|
||||
|
||||
### 模式 D · Sinusoidal Pan(持续漂移)
|
||||
|
||||
用正弦波 + 线性漂移组合,避免 marquee 那种「有起点有终点」的循环感:
|
||||
|
||||
```js
|
||||
const panX = Math.sin(panT * 0.12) * 220 - panT * 8; // 横向左漂
|
||||
const panY = Math.cos(panT * 0.09) * 120 - panT * 5; // 纵向上漂
|
||||
const clampedX = Math.max(-900, Math.min(900, panX)); // 防止露边
|
||||
```
|
||||
|
||||
**参数**:
|
||||
- 正弦周期 `0.09-0.15 rad/s`(慢,约30-50秒一个摆动)
|
||||
- 线性漂移 `5-8 px/s`(比观众眨眼慢)
|
||||
- 振幅 `120-220 px`(大到能感觉,小到不会晕)
|
||||
|
||||
### 模式 E · Focus Overlay(焦点切换)
|
||||
|
||||
**关键设计**:focus overlay 是一个**平面元素**(不倾斜),浮在倾斜画布之上。被选中的 slide 从瓦片位置(约400×225)缩放到屏幕中央(960×540),背景画布不倾斜变化但**变暗到 45%**:
|
||||
|
||||
```js
|
||||
// Focus overlay (flat, centered)
|
||||
focusOverlay.style.width = (startW + (endW - startW) * focusIntensity) + 'px';
|
||||
focusOverlay.style.height = (startH + (endH - startH) * focusIntensity) + 'px';
|
||||
focusOverlay.style.opacity = focusIntensity;
|
||||
|
||||
// 背景卡片变暗,但依然可见(关键!不要100%遮罩)
|
||||
card.style.opacity = entryOp * (1 - 0.55 * focusIntensity); // 1 → 0.45
|
||||
card.style.filter = `brightness(${1 - 0.3 * focusIntensity})`;
|
||||
```
|
||||
|
||||
**清晰度铁律**:
|
||||
- Focus overlay 的 `<img>` 必须 `src` 直连原图,**不要复用 gallery 里的压缩缩略**
|
||||
- 提前 preload 所有原图到 `new Image()[]` 数组
|
||||
- overlay 自身 `width/height` 按帧计算,浏览器每帧 resample 原图
|
||||
|
||||
---
|
||||
|
||||
## 时间轴架构(可复用骨架)
|
||||
|
||||
```js
|
||||
const T = {
|
||||
DURATION: 25.0,
|
||||
s1_in: [0.0, 0.8], s1_type: [1.0, 3.2], s1_out: [3.5, 4.0],
|
||||
s2_in: [3.9, 5.1], s2_hold: [5.1, 7.0], s2_out: [7.0, 7.8],
|
||||
s3_hold: [7.8, 8.3], s3_ripple: [8.3, 10.0],
|
||||
panStart: 8.6,
|
||||
focuses: [
|
||||
{ start: 11.0, end: 12.7, idx: 2 },
|
||||
{ start: 13.3, end: 15.0, idx: 3 },
|
||||
{ start: 15.6, end: 17.3, idx: 10 },
|
||||
{ start: 17.9, end: 19.6, idx: 16 },
|
||||
],
|
||||
s4_walloff: [21.1, 21.8], s4_in: [21.8, 22.7], s4_hold: [23.7, 25.0],
|
||||
};
|
||||
|
||||
// 核心 easing
|
||||
const easeOut = t => 1 - Math.pow(1 - t, 3);
|
||||
const easeInOut = t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2;
|
||||
function lerp(time, start, end, fromV, toV, easing) {
|
||||
if (time <= start) return fromV;
|
||||
if (time >= end) return toV;
|
||||
let p = (time - start) / (end - start);
|
||||
if (easing) p = easing(p);
|
||||
return fromV + (toV - fromV) * p;
|
||||
}
|
||||
|
||||
// 单一 render(t) 函数读时间戳、写所有元素
|
||||
function render(t) { /* ... */ }
|
||||
requestAnimationFrame(function tick(now) {
|
||||
const t = ((now - startMs) / 1000) % T.DURATION;
|
||||
render(t);
|
||||
requestAnimationFrame(tick);
|
||||
});
|
||||
```
|
||||
|
||||
**架构精髓**:**所有状态由时间戳 t 推导**,没有状态机、没有 setTimeout。这样:
|
||||
- 播放到任意时刻 `window.__setTime(12.3)` 立刻跳转(方便 playwright 逐帧截)
|
||||
- 循环天然无缝(t mod DURATION)
|
||||
- Debug 时能冻结任意一帧
|
||||
|
||||
---
|
||||
|
||||
## 质感细节(容易被忽略但致命)
|
||||
|
||||
### 1. SVG noise texture
|
||||
|
||||
浅色底最怕「太平」。叠加一层极弱的 fractalNoise:
|
||||
|
||||
```html
|
||||
<style>
|
||||
.stage::before {
|
||||
content: '';
|
||||
position: absolute; inset: 0;
|
||||
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.078 0 0 0 0 0.078 0 0 0 0 0.074 0 0 0 0.035 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>");
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
z-index: 30;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
看上去没区别,去掉就知道有了。
|
||||
|
||||
### 2. 角落品牌标识
|
||||
|
||||
```html
|
||||
<div class="corner-brand">
|
||||
<div class="mark"></div>
|
||||
<div>HUASHU · DESIGN</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```css
|
||||
.corner-brand {
|
||||
position: absolute; top: 48px; left: 72px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
```
|
||||
|
||||
只在作品墙 scene 显示,淡入淡出。像美术馆展签。
|
||||
|
||||
### 3. 品牌收束 wordmark
|
||||
|
||||
```css
|
||||
.brand-wordmark {
|
||||
font-family: var(--sans);
|
||||
font-size: 148px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.045em; /* 负字距是关键,让字紧凑成标志 */
|
||||
}
|
||||
.brand-wordmark .accent {
|
||||
color: var(--accent);
|
||||
font-weight: 500; /* accent字符反而细一点,视觉差 */
|
||||
}
|
||||
```
|
||||
|
||||
`letter-spacing: -0.045em` 是苹果产品页大字的标准做法。
|
||||
|
||||
---
|
||||
|
||||
## 常见失败模式
|
||||
|
||||
| 症状 | 原因 | 解法 |
|
||||
|---|---|---|
|
||||
| 看起来像 PPT 模板 | 卡片没有 shadow / hairline | 加上两层 box-shadow + 1px border |
|
||||
| 倾斜感廉价 | 只用了 rotateY 没加 rotateZ | 加 ±2-3deg rotateZ 打破工整 |
|
||||
| Pan 感觉「卡顿」 | 用了 setTimeout 或 CSS keyframes 循环 | 用 rAF + sin/cos 连续函数 |
|
||||
| Focus 时字看不清 | 复用了 gallery 瓦片的低分图 | 独立 overlay + 原图 src 直连 |
|
||||
| 背景太空 | 纯色 `#F5F5F7` | 叠加 SVG fractalNoise 0.5 opacity |
|
||||
| 字体太"互联网" | 只有 Inter | 加 Serif(中英各一)+ mono 三栈 |
|
||||
|
||||
---
|
||||
|
||||
## 引用
|
||||
|
||||
- 完整实现样本:`/Users/alchain/Documents/写作/01-公众号写作/项目/2026.04-huashu-design发布/配图/hero-animation-v5.html`
|
||||
- 原始灵感:claude.ai/design hero 视频
|
||||
- 参考审美:Apple 产品页、Dribbble shot 集合页
|
||||
|
||||
遇到「多件高质量产出要陈列」的动画需求,直接从此文件 copy 骨架,换内容 + 调 timing 即可。
|
||||
260
.claude/skills/huashu-design/references/audio-design-rules.md
Normal file
260
.claude/skills/huashu-design/references/audio-design-rules.md
Normal 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 数 | 产品性格 | 场景 |
|
||||
|---|---|---|---|
|
||||
| Artifacts(ref-1) | **~9个/10s** | 功能密集、信息多 | 复杂工具演示 |
|
||||
| Code Desktop(ref-2) | **0个** | 纯氛围、冥想感 | 开发工具专注状态 |
|
||||
| Word(ref-3) | **~4个/10s** | 平衡、办公节奏 | 生产力工具 |
|
||||
|
||||
**启发式**:
|
||||
- 产品性格冷静/专注 → SFX 密度低(0-3个/10s),BGM 为主
|
||||
- 产品性格活泼/信息多 → SFX 密度高(6-9个/10s),SFX 驱动节奏
|
||||
- **不要填满每个视觉 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.mp3(minimal synth + piano)
|
||||
├─ 教程讲解 / 工具使用 → bgm-tutorial.mp3(warm, instructional)
|
||||
├─ 教育学习 / 原理解释 → bgm-educational.mp3(curious, thoughtful)
|
||||
├─ 营销广告 / 品牌宣传 → bgm-ad.mp3(upbeat, promotional)
|
||||
└─ 同类风格需要变体 → bgm-*-alt.mp3(各自替代版)
|
||||
```
|
||||
|
||||
### 无 BGM 的场景(值得考虑)
|
||||
参考 Anthropic Code Desktop(ref-2):**0 SFX + 纯 Lo-fi BGM** 也能很高级。
|
||||
|
||||
**何时选无BGM**:
|
||||
- 动画时长 <10s(BGM 建立不起来)
|
||||
- 产品性格是「专注/冥想」
|
||||
- 场景本身有环境音/讲解声
|
||||
- SFX 密度很高时(避免听觉过载)
|
||||
|
||||
---
|
||||
|
||||
## 场景配方(开箱即用)
|
||||
|
||||
### 配方 A · 产品发布 hero(huashu-design v9 同款)
|
||||
```
|
||||
时长:25 秒
|
||||
BGM:bgm-tech.mp3 · 45% · 频段 <4kHz
|
||||
SFX 密度:~6个/10s
|
||||
|
||||
cue:
|
||||
终端打字 → type × 4(间隔0.6s)
|
||||
回车 → enter
|
||||
卡片汇聚 → card × 4(错峰 0.2s)
|
||||
选中 → click
|
||||
Ripple → whoosh
|
||||
4次焦点 → focus × 4
|
||||
Logo → thud(1.5s)
|
||||
|
||||
音量:BGM 0.45 / SFX 1.0 · amix normalize=0
|
||||
```
|
||||
|
||||
### 配方 B · 工具功能演示(参考 Anthropic Code Desktop)
|
||||
```
|
||||
时长:30-45 秒
|
||||
BGM:bgm-tutorial.mp3 · 50%
|
||||
SFX 密度:0-2个/10s(极少)
|
||||
|
||||
策略:让 BGM + 讲解 voiceover 驱动,SFX 只在**决定性时刻**(文件保存/命令执行完成)
|
||||
```
|
||||
|
||||
### 配方 C · AI 生成演示
|
||||
```
|
||||
时长:15-20 秒
|
||||
BGM:bgm-tech.mp3 或无 BGM
|
||||
SFX 密度:~8个/10s(高密度)
|
||||
|
||||
cue:
|
||||
用户输入 → type + enter
|
||||
AI 开始处理 → magic/ai-process(1.2s 循环)
|
||||
生成完成 → feedback/complete-done
|
||||
结果呈现 → magic/sparkle
|
||||
|
||||
亮点:ai-process 可以循环 2-3 次贯穿整个生成过程
|
||||
```
|
||||
|
||||
### 配方 D · 纯氛围长镜头(参考 Artifacts)
|
||||
```
|
||||
时长:10-15 秒
|
||||
BGM:无
|
||||
SFX:单独使用 3-5 个精心设计的 cue
|
||||
|
||||
策略:每个 SFX 都是主角,没有BGM「糊在一起」的问题。
|
||||
适合:单产品慢镜头、特写展示
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ffmpeg 合成模板
|
||||
|
||||
### 模板 1 · 单 SFX 叠加到视频
|
||||
```bash
|
||||
ffmpeg -y -i video.mp4 -itsoffset 2.5 -i sfx.mp3 \
|
||||
-filter_complex "[0:a][1:a]amix=inputs=2:normalize=0[a]" \
|
||||
-map 0:v -map "[a]" output.mp4
|
||||
```
|
||||
|
||||
### 模板 2 · 多 SFX 时间轴合成(按cue时间对齐)
|
||||
```bash
|
||||
ffmpeg -y \
|
||||
-i sfx-type.mp3 -i sfx-enter.mp3 -i sfx-click.mp3 -i sfx-thud.mp3 \
|
||||
-filter_complex "\
|
||||
[0:a]adelay=1100|1100[a0];\
|
||||
[1:a]adelay=3200|3200[a1];\
|
||||
[2:a]adelay=7000|7000[a2];\
|
||||
[3:a]adelay=21800|21800[a3];\
|
||||
[a0][a1][a2][a3]amix=inputs=4:duration=longest:normalize=0[mixed]" \
|
||||
-map "[mixed]" -t 25 sfx-track.mp3
|
||||
```
|
||||
**关键参数**:
|
||||
- `adelay=N|N`:前面是左声道延迟(ms),后面是右声道,写两遍保证立体声对齐
|
||||
- `normalize=0`:保留动态范围,关键!
|
||||
- `-t 25`:截断到指定时长
|
||||
|
||||
### 模板 3 · 视频 + SFX track + BGM(带频段隔离)
|
||||
```bash
|
||||
ffmpeg -y -i video.mp4 -i sfx-track.mp3 -i bgm.mp3 \
|
||||
-filter_complex "\
|
||||
[2:a]atrim=0:25,afade=in:st=0:d=0.3,afade=out:st=23.5:d=1.5,\
|
||||
lowpass=f=4000,volume=0.45[bgm];\
|
||||
[1:a]highpass=f=800,volume=1.0[sfx];\
|
||||
[bgm][sfx]amix=inputs=2:duration=first:normalize=0[a]" \
|
||||
-map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k final.mp4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 失败模式速查
|
||||
|
||||
| 症状 | 根因 | 修复 |
|
||||
|---|---|---|
|
||||
| SFX 听不见 | BGM 高频部分遮盖 | 加 `lowpass=f=4000` 给BGM + `highpass=f=800` 给SFX |
|
||||
| 音效过响刺耳 | SFX 绝对音量太大 | SFX 音量降到 0.7,同时降低 BGM 到 0.3,保持差值 |
|
||||
| BGM 和 SFX 节奏冲突 | BGM 选错了(用了有强beat的music) | 换成 ambient / minimal synth 的 BGM |
|
||||
| 动画结束 BGM 突然断 | 没做 fade out | `afade=out:st=N-1.5:d=1.5` |
|
||||
| SFX 重叠成糊 | cue 太密 + 每个 SFX 时长太长 | SFX 时长控到 0.5s 以内,cue 间隔 ≥ 0.2s |
|
||||
| 公众号 mp4 没声音 | 公众号有时会 mute auto-play | 不用担心,用户点开会有声音;gif 本来就没声音 |
|
||||
|
||||
---
|
||||
|
||||
## 和视觉的联动(高级)
|
||||
|
||||
### SFX 音色要和视觉风格匹配
|
||||
- 暖米/纸张感视觉 → SFX 用**木质/柔和**音色(Morse, paper snap, soft click)
|
||||
- 冷黑科技视觉 → SFX 用**金属/数字**音色(beep, pulse, glitch)
|
||||
- 手绘/童趣视觉 → SFX 用**卡通/夸张**音色(boing, pop, zap)
|
||||
|
||||
我们当前 `apple-gallery-showcase.md` 的暖米底色 → 搭配 `keyboard/type.mp3`(mechanical)+ `container/card-snap.mp3`(soft)+ `impact/logo-reveal-v2.mp3`(cinematic bass)
|
||||
|
||||
### SFX 可以引导视觉节奏
|
||||
高级技巧:**先设计 SFX 时间轴,然后调整视觉动画去对齐 SFX**(不是反过来)。
|
||||
因为 SFX 每个 cue 都是一个「钟表 tick」,视觉动画适配 SFX 节奏会非常稳——反之 SFX 去追视觉,常常 ±1 帧对不上就有违和感。
|
||||
|
||||
---
|
||||
|
||||
## 质量检查清单(发布前自检)
|
||||
|
||||
- [ ] 响度差:SFX peak - BGM peak = -6 到 -8 dB?
|
||||
- [ ] 频段:BGM lowpass 4kHz + SFX highpass 800Hz?
|
||||
- [ ] amix normalize=0(保留动态范围)?
|
||||
- [ ] BGM fade-in 0.3s + fade-out 1.5s?
|
||||
- [ ] SFX 数量是否合适(按场景性格选密度)?
|
||||
- [ ] 每个 SFX 和视觉 beat 同帧对齐(±1 帧内)?
|
||||
- [ ] Logo reveal 音效时长够(建议 1.5s)?
|
||||
- [ ] 关闭 BGM 听一遍:SFX 单独是否足够有节奏感?
|
||||
- [ ] 关闭 SFX 听一遍:BGM 单独是否有情绪起伏?
|
||||
|
||||
两层任何一层单独听都应该自洽。如果只有两层叠加才好听,说明没做好。
|
||||
|
||||
---
|
||||
|
||||
## 参考
|
||||
|
||||
- SFX 资产清单:`sfx-library.md`
|
||||
- 视觉风格参考:`apple-gallery-showcase.md`
|
||||
- Anthropic 三支片子深度音频分析:`/Users/alchain/Documents/写作/01-公众号写作/项目/2026.04-huashu-design发布/参考动画/AUDIO-BEST-PRACTICES.md`
|
||||
- huashu-design v9 实战案例:`/Users/alchain/Documents/写作/01-公众号写作/项目/2026.04-huashu-design发布/配图/hero-animation-v9-final.mp4`
|
||||
264
.claude/skills/huashu-design/references/cinematic-patterns.md
Normal file
264
.claude/skills/huashu-design/references/cinematic-patterns.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Cinematic Patterns · Workflow Demo 的 Best Practice
|
||||
|
||||
> 从「PPT 动画」升级到「发布会级 cinematic」的 5 个关键 pattern。
|
||||
> 蒸馏自 2026-04 「聊聊 skill」 deck 的两个 cinematic demo(Nuwa workflow + Darwin workflow),实测可复现。
|
||||
|
||||
---
|
||||
|
||||
## 0 · 这份文档解决什么问题
|
||||
|
||||
当你需要做「演示一个工作流的 demo 动画」时(典型场景:skill 工作流、产品 onboarding、API 调用流程、agent 任务执行),有两种常见做法:
|
||||
|
||||
| 范式 | 长什么样 | 后果 |
|
||||
|---|---|---|
|
||||
| **PPT 动画**(差) | step 1 fade in → step 2 fade in → step 3 fade in,4 个 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(中高频 punch),sparkle 0.7(要醒目),logo-reveal 0.85(最强 hero moment)。
|
||||
|
||||
**用户控制**:
|
||||
- 必须有 ▶ 启动覆盖(浏览器 autoplay 限制)
|
||||
- 右上角小 mute 按钮(用户随时切静音)
|
||||
- 不要做成「翻到这页就强制响」
|
||||
|
||||
---
|
||||
|
||||
## 2 · 静态 Dashboard 设计要点
|
||||
|
||||
Dashboard 是双层结构的 Layer 1,PM 不点 ▶ 也能看懂这个 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 小时。
|
||||
260
.claude/skills/huashu-design/references/content-guidelines.md
Normal file
260
.claude/skills/huashu-design/references/content-guidelines.md
Normal 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按钮里的 →(箭头单独出现OK,emoji箭头不行)
|
||||
|
||||
没图标用真icon库(Lucide/Heroicons/Phosphor),或者用placeholder。
|
||||
|
||||
**❌ SVG 画 imagery**
|
||||
不要试图用SVG画:人物、场景、设备、物品、抽象艺术。AI画的SVG imagery一眼就是AI味,幼稚且廉价。**一个灰色矩形+"插画位 1200×800"的文字标签,比一个拙劣的SVG hero illustration强100倍**。
|
||||
|
||||
唯一可以用SVG的场景:
|
||||
- 真正的icon(16×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。
|
||||
|
||||
### 字体陷阱
|
||||
|
||||
**❌ 避免这些烂大街字体**:
|
||||
- Inter(AI生成的网页默认)
|
||||
- Roboto
|
||||
- Arial / Helvetica
|
||||
- 纯system font stack
|
||||
- Fraunces(AI发现了这个就用滥了)
|
||||
- Space Grotesk(最近AI的最爱)
|
||||
|
||||
**✅ 用有特点的display+body配对**。灵感方向:
|
||||
- 衬线display + 无衬线body(editorial feel)
|
||||
- Mono display + sans body(technical feel)
|
||||
- Heavy display + light body(contrast)
|
||||
- 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的征兆**。先做最简的版本,只在用户要求时加。
|
||||
199
.claude/skills/huashu-design/references/critique-guide.md
Normal file
199
.claude/skills/huashu-design/references/critique-guide.md
Normal 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
|
||||
213
.claude/skills/huashu-design/references/design-context.md
Normal file
213
.claude/skills/huashu-design/references/design-context.md
Normal 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 scaffold(App.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 system(fallback)
|
||||
如果以上都没有,用公认的设计系统作为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 3:Read所有能找到的context
|
||||
|
||||
如果用户给了codebase路径,你读:
|
||||
1. **先list文件结构**:找style/theme/component相关的文件
|
||||
2. **读theme/token文件**:lift具体的hex/px values
|
||||
3. **读2-3个代表性组件**:看视觉vocabulary(hover state、shadow、border、padding node pattern)
|
||||
4. **读global stylesheet**:基础重置、font loading
|
||||
5. **如果有Figma链接/截图**:看图,但**更相信代码**
|
||||
|
||||
**重要**:**不要**看了一眼就凭印象做。读下来有30+个具体values才真的lift到了。
|
||||
|
||||
### Step 4:Vocalize你要用的系统
|
||||
|
||||
看完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**
|
||||
- Button:filled primary,outlined secondary,ghost 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做组件vocabulary(https://ui.shadcn.com)
|
||||
- 用Tailwind spacing scale(4的倍数)
|
||||
|
||||
### 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 Sans(technical 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 code(Figma支持)
|
||||
|
||||
## 最后的提醒
|
||||
|
||||
**一个项目的设计质量上限,由你拿到的context质量决定**。
|
||||
|
||||
花10分钟收集context,比花1小时凭空画hi-fi更有价值。
|
||||
|
||||
**遇到没context的情况,优先问用户要,而不是硬上**。
|
||||
591
.claude/skills/huashu-design/references/design-styles.md
Normal file
591
.claude/skills/huashu-design/references/design-styles.md
Normal file
@@ -0,0 +1,591 @@
|
||||
# 设计哲学风格库:20种体系
|
||||
|
||||
> 用于视觉设计(网页/PPT/PDF/信息图/配图/App等)的设计风格库。
|
||||
> 每种风格提供:哲学内核 + 核心特征 + 提示词DNA(与场景模板组合使用)。
|
||||
|
||||
## 风格×场景×执行路径 速查表
|
||||
|
||||
| 风格 | 网页 | PPT | PDF | 信息图 | 封面 | AI生成 | 最佳路径 |
|
||||
|------|:---:|:---:|:---:|:-----:|:---:|:-----:|---------|
|
||||
| 01 Pentagram | ★★★ | ★★★ | ★★☆ | ★★☆ | ★★★ | ★☆☆ | HTML |
|
||||
| 02 Stamen Design | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★☆ | ★★☆ | 混合 |
|
||||
| 03 Information Architects | ★★★ | ★☆☆ | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | HTML |
|
||||
| 04 Fathom | ★★☆ | ★★★ | ★★★ | ★★★ | ★★☆ | ★☆☆ | HTML |
|
||||
| 05 Locomotive | ★★★ | ★★☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★☆ | 混合 |
|
||||
| 06 Active Theory | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★★ | AI生成 |
|
||||
| 07 Field.io | ★★☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
|
||||
| 08 Resn | ★★★ | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★☆ | AI生成 |
|
||||
| 09 Experimental Jetset | ★★☆ | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★☆ | 混合 |
|
||||
| 10 Müller-Brockmann | ★★☆ | ★★★ | ★★★ | ★★★ | ★★☆ | ★☆☆ | HTML |
|
||||
| 11 Build | ★★★ | ★★★ | ★★☆ | ★☆☆ | ★★★ | ★☆☆ | HTML |
|
||||
| 12 Sagmeister & Walsh | ★★☆ | ★★★ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
|
||||
| 13 Zach Lieberman | ★☆☆ | ★☆☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
|
||||
| 14 Raven Kwok | ★☆☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
|
||||
| 15 Ash Thorp | ★★☆ | ★★☆ | ★☆☆ | ★☆☆ | ★★★ | ★★★ | AI生成 |
|
||||
| 16 Territory Studio | ★★☆ | ★★☆ | ★☆☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
|
||||
| 17 Takram | ★★★ | ★★★ | ★★★ | ★★☆ | ★★☆ | ★☆☆ | HTML |
|
||||
| 18 Kenya Hara | ★★☆ | ★★★ | ★★★ | ★☆☆ | ★★★ | ★☆☆ | HTML |
|
||||
| 19 Irma Boom | ★☆☆ | ★★☆ | ★★★ | ★★☆ | ★★★ | ★★☆ | 混合 |
|
||||
| 20 Neo Shen | ★★☆ | ★★☆ | ★★☆ | ★★☆ | ★★★ | ★★★ | AI生成 |
|
||||
|
||||
> 场景适配:★★★ = 强烈推荐 / ★★☆ = 适合 / ★☆☆ = 需改造
|
||||
> AI生成:★★★ = 直出效果好 / ★★☆ = 需调整 / ★☆☆ = 建议HTML执行
|
||||
> 最佳路径:AI生成(图片直出)/ HTML(代码渲染,数据精确)/ 混合(HTML布局+AI配图)
|
||||
|
||||
**核心规律**:有明确视觉元素的风格(插画/粒子/生成艺术)AI直出效果好;依赖精确排版和数据的风格(网格/信息架构/留白)HTML渲染更可控。
|
||||
|
||||
---
|
||||
|
||||
## 一、信息建筑派(01-04)
|
||||
> 哲学:「数据不是装饰,是建筑材料」
|
||||
|
||||
### 01. Pentagram - Michael Bierut风格
|
||||
**哲学**:字体即语言,网格即思想
|
||||
**核心特征**:
|
||||
- 极度克制的颜色(黑白+1个品牌色)
|
||||
- 瑞士网格系统的现代演绎
|
||||
- 字体排印作为主要视觉语言
|
||||
- 负空间的战略性使用(60%+留白)
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Pentagram/Michael Bierut style:
|
||||
- Extreme typographic hierarchy, Helvetica/Univers family
|
||||
- Swiss grid with precise mathematical spacing
|
||||
- Black/white + one accent color (#HEX)
|
||||
- Information architecture as visual structure
|
||||
- 60%+ whitespace ratio
|
||||
- Data visualization as primary decoration
|
||||
```
|
||||
|
||||
**代表作**:Hillary Clinton 2016 campaign identity
|
||||
**搜索关键词**:pentagram hillary logo system
|
||||
|
||||
---
|
||||
|
||||
### 02. Stamen Design - 数据诗学
|
||||
**哲学**:让数据成为可触摸的风景
|
||||
**核心特征**:
|
||||
- 地图学思维应用于信息设计
|
||||
- 算法生成的有机图形
|
||||
- 温暖的数据可视化色调(赭石、鼠尾草绿、深蓝)
|
||||
- 可交互的层级系统
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Stamen Design aesthetic:
|
||||
- Cartographic approach to data visualization
|
||||
- Organic, algorithm-generated patterns
|
||||
- Warm palette (terracotta, sage green, deep blues)
|
||||
- Layered information like topographic maps
|
||||
- Hand-crafted feel despite digital precision
|
||||
- Soft shadows and depth
|
||||
```
|
||||
|
||||
**代表作**:COVID-19 surge map
|
||||
**搜索关键词**:stamen covid map visualization
|
||||
|
||||
---
|
||||
|
||||
### 03. Information Architects - 内容优先原则
|
||||
**哲学**:设计不是装饰,是内容的建筑
|
||||
**核心特征**:
|
||||
- 极端的内容层级清晰度
|
||||
- 只使用系统字体(优化阅读)
|
||||
- 蓝色超链接传统的坚守
|
||||
- 性能即美学
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Information Architects philosophy:
|
||||
- Content-first hierarchy, zero decorative elements
|
||||
- System fonts only (SF Pro/Roboto/Inter)
|
||||
- Classic blue hyperlinks (#0000EE)
|
||||
- Reading-optimized line length (66 characters)
|
||||
- Progressive disclosure of depth
|
||||
- Text-heavy, fast-loading design
|
||||
```
|
||||
|
||||
**代表作**:iA Writer app
|
||||
**搜索关键词**:information architects ia writer
|
||||
|
||||
---
|
||||
|
||||
### 04. Fathom Information Design - 科学叙事
|
||||
**哲学**:每一个像素都必须承载信息
|
||||
**核心特征**:
|
||||
- 科学期刊的严谨+设计的优雅
|
||||
- 定量数据的精确可视化
|
||||
- 冷静的专业色调(灰、海军蓝)
|
||||
- 注释与引用系统的设计化
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Fathom Information Design style:
|
||||
- Scientific journal aesthetic meets modern design
|
||||
- Precise data visualization (charts, timelines, scatter plots)
|
||||
- Neutral scheme (grays, navy, one highlight color)
|
||||
- Footnote/citation design integrated into layout
|
||||
- Clean sans-serif (GT America/Graphik)
|
||||
- Information density without clutter
|
||||
```
|
||||
|
||||
**代表作**:Bill & Melinda Gates Foundation年度报告
|
||||
**搜索关键词**:fathom information design gates foundation
|
||||
|
||||
---
|
||||
|
||||
## 二、运动诗学派(05-08)
|
||||
> 哲学:「技术本身就是一种流动的诗」
|
||||
|
||||
### 05. Locomotive - 滚动叙事大师
|
||||
**哲学**:滚动不是浏览,是旅程
|
||||
**核心特征**:
|
||||
- 丝滑的视差滚动
|
||||
- 电影化的分镜叙事
|
||||
- 大胆的空间留白
|
||||
- 动态元素的精确编排
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Locomotive scroll narrative style:
|
||||
- Film-like scene composition with parallax depth
|
||||
- Generous vertical spacing between sections
|
||||
- Bold typography emerging from darkness
|
||||
- Smooth motion blur effects
|
||||
- Dark mode (near-black backgrounds)
|
||||
- Strategic glowing accents
|
||||
- Hero sections 100vh tall
|
||||
```
|
||||
|
||||
**代表作**:Lusion.co website
|
||||
**搜索关键词**:locomotive scroll lusion
|
||||
|
||||
---
|
||||
|
||||
### 06. Active Theory - WebGL诗人
|
||||
**哲学**:让技术可见化即让技术可理解
|
||||
**核心特征**:
|
||||
- 3D粒子系统作为核心元素
|
||||
- 实时渲染的数据可视化
|
||||
- 鼠标交互驱动的世界构建
|
||||
- 霓虹与深空的配色
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Active Theory WebGL aesthetic:
|
||||
- Particle systems representing data flow
|
||||
- 3D visualization in depth space
|
||||
- Neon gradients (cyan/magenta/electric blue) on dark
|
||||
- Mouse-reactive environment
|
||||
- Depth of field and bokeh effects
|
||||
- Floating UI with glassmorphism
|
||||
```
|
||||
|
||||
**代表作**:NASA Prospect
|
||||
**搜索关键词**:active theory nasa webgl
|
||||
|
||||
---
|
||||
|
||||
### 07. Field.io - 算法美学
|
||||
**哲学**:代码即设计师
|
||||
**核心特征**:
|
||||
- 生成艺术系统
|
||||
- 每次访问都不同的动态图形
|
||||
- 抽象几何的智能编排
|
||||
- 技术感与艺术性的平衡
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Field.io generative design style:
|
||||
- Abstract geometric patterns, algorithmically generated
|
||||
- Dynamic composition that feels computational
|
||||
- Monochromatic base with vibrant accent
|
||||
- Mathematical precision in spacing
|
||||
- Voronoi diagrams or Delaunay triangulation
|
||||
- Clean code aesthetic
|
||||
```
|
||||
|
||||
**代表作**:British Council digital installations
|
||||
**搜索关键词**:field.io generative design
|
||||
|
||||
---
|
||||
|
||||
### 08. Resn - 叙事驱动的交互
|
||||
**哲学**:每个点击都推进故事
|
||||
**核心特征**:
|
||||
- 游戏化的用户旅程
|
||||
- 强烈的情感化设计
|
||||
- 插画与代码的深度结合
|
||||
- 非线性的探索体验
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Resn interactive storytelling approach:
|
||||
- Illustrative style mixed with UI elements
|
||||
- Gamified exploration (progress indicators)
|
||||
- Warm color palette despite tech subject
|
||||
- Character-driven design
|
||||
- Scroll-triggered animations
|
||||
- Editorial illustration meets product design
|
||||
```
|
||||
|
||||
**代表作**:Resn.co.nz portfolio
|
||||
**搜索关键词**:resn interactive storytelling
|
||||
|
||||
---
|
||||
|
||||
## 三、极简主义派(09-12)
|
||||
> 哲学:「删减到无法再删」
|
||||
|
||||
### 09. Experimental Jetset - 概念极简
|
||||
**哲学**:一个想法=一个形式
|
||||
**核心特征**:
|
||||
- 单一视觉隐喻贯穿整个设计
|
||||
- 蓝/红/黄+黑白的蒙德里安色系
|
||||
- 字体即图形
|
||||
- 反商业的诚实设计
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Experimental Jetset conceptual minimalism:
|
||||
- Single visual metaphor for entire design
|
||||
- Primary colors only (red/blue/yellow) + black/white
|
||||
- Typography as main graphic element
|
||||
- Grid-based with deliberate rule-breaking
|
||||
- No photography, only type and geometry
|
||||
- Anti-commercial, honest aesthetic
|
||||
```
|
||||
|
||||
**代表作**:Whitney Museum identity
|
||||
**搜索关键词**:experimental jetset whitney responsive w
|
||||
|
||||
---
|
||||
|
||||
### 10. Müller-Brockmann传承 - 瑞士网格纯粹主义
|
||||
**哲学**:客观性即美
|
||||
**核心特征**:
|
||||
- 数学精确的网格系统(8pt基线)
|
||||
- 绝对的左对齐或居中
|
||||
- 单色或双色方案
|
||||
- 功能主义至上
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Josef Müller-Brockmann Swiss modernism:
|
||||
- Mathematical grid system (8pt baseline)
|
||||
- Strict alignment (flush left or centered)
|
||||
- Two-color maximum (black + one accent)
|
||||
- Akzidenz-Grotesk or similar rationalist typeface
|
||||
- No decorative elements
|
||||
- Timeless, objective aesthetic
|
||||
```
|
||||
|
||||
**代表作**:《Grid Systems in Graphic Design》
|
||||
**搜索关键词**:muller brockmann grid systems poster
|
||||
|
||||
---
|
||||
|
||||
### 11. Build - 当代极简品牌
|
||||
**哲学**:精致的简单比复杂更难
|
||||
**核心特征**:
|
||||
- 奢侈品级的留白(70%+)
|
||||
- 微妙的字重对比(200-600)
|
||||
- 单一强调色的战略使用
|
||||
- 呼吸感的节奏
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Build studio luxury minimalism:
|
||||
- Generous whitespace (70%+ of area)
|
||||
- Subtle typography weight shifts (200 to 600)
|
||||
- Single accent color used sparingly
|
||||
- High-end product photography aesthetic
|
||||
- Soft shadows and subtle gradients
|
||||
- Golden ratio proportions
|
||||
```
|
||||
|
||||
**代表作**:Build studio portfolio
|
||||
**搜索关键词**:build studio london branding
|
||||
|
||||
---
|
||||
|
||||
### 12. Sagmeister & Walsh - 快乐极简
|
||||
**哲学**:美即功能的情感维度
|
||||
**核心特征**:
|
||||
- 意外的色彩爆发
|
||||
- 手工感与数字的融合
|
||||
- 正能量的视觉语言
|
||||
- 实验性但可读
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Sagmeister & Walsh joyful philosophy:
|
||||
- Unexpected color bursts on minimal base
|
||||
- Handmade elements (physical objects in digital)
|
||||
- Optimistic visual language
|
||||
- Experimental typography that remains legible
|
||||
- Human warmth through imperfection
|
||||
- Mix of analog and digital aesthetics
|
||||
```
|
||||
|
||||
**代表作**:The Happy Show
|
||||
**搜索关键词**:sagmeister walsh happy show
|
||||
|
||||
---
|
||||
|
||||
## 四、实验先锋派(13-16)
|
||||
> 哲学:「打破规则即创造规则」
|
||||
|
||||
### 13. Zach Lieberman - 代码诗学
|
||||
**哲学**:编程即绘画
|
||||
**核心特征**:
|
||||
- 手绘感的算法图形
|
||||
- 实时生成艺术
|
||||
- 黑白的纯粹表达
|
||||
- 工具本身的可见性
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Zach Lieberman code-as-art style:
|
||||
- Hand-drawn aesthetic generated by code
|
||||
- Black and white only, no color
|
||||
- Real-time generative patterns
|
||||
- Sketch-like line quality
|
||||
- Visible process/grid/construction lines
|
||||
- Poetic interpretation of algorithms
|
||||
```
|
||||
|
||||
**代表作**:openFrameworks creative coding
|
||||
**搜索关键词**:zach lieberman openframeworks generative
|
||||
|
||||
---
|
||||
|
||||
### 14. Raven Kwok - 参数化美学
|
||||
**哲学**:系统的美胜过个体的美
|
||||
**核心特征**:
|
||||
- 分形与递归图形
|
||||
- 黑白高对比
|
||||
- 建筑化的信息结构
|
||||
- 东方园林的算法演绎
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Raven Kwok parametric aesthetic:
|
||||
- Fractal patterns and recursive structures
|
||||
- High-contrast black and white
|
||||
- Architectural visualization of data
|
||||
- Chinese garden principles in algorithm form
|
||||
- Intricate detail that rewards zooming
|
||||
- Processing/Creative coding aesthetic
|
||||
```
|
||||
|
||||
**代表作**:Raven Kwok generative art exhibitions
|
||||
**搜索关键词**:raven kwok processing generative art
|
||||
|
||||
---
|
||||
|
||||
### 15. Ash Thorp - 赛博诗意
|
||||
**哲学**:未来不是冰冷的,是孤独的诗
|
||||
**核心特征**:
|
||||
- 电影级的光影
|
||||
- 赛博朋克的温暖版本(橙/青,非冷蓝)
|
||||
- 故事性的概念设计
|
||||
- 工业美学的精致化
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Ash Thorp cinematic concept art:
|
||||
- Film-grade lighting and atmospheric effects
|
||||
- Warm cyberpunk (orange/teal, NOT cold blue)
|
||||
- Industrial design meets luxury
|
||||
- Narrative concept art feel
|
||||
- Volumetric lighting and god rays
|
||||
- Blade Runner warmth over Tron coldness
|
||||
```
|
||||
|
||||
**代表作**:Ghost in the Shell concept art
|
||||
**搜索关键词**:ash thorp ghost shell concept art
|
||||
|
||||
---
|
||||
|
||||
### 16. Territory Studio - 屏幕界面虚构
|
||||
**哲学**:未来UI的今日想象
|
||||
**核心特征**:
|
||||
- 科幻电影中的屏幕设计(FUI)
|
||||
- 全息投影感
|
||||
- 多层叠加的数据可视化
|
||||
- 可信的未来感
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Territory Studio FUI (Fantasy User Interface):
|
||||
- Fantasy User Interface design
|
||||
- Holographic projection aesthetics
|
||||
- Orange/amber monochrome or cyan accents
|
||||
- Multiple overlapping data layers
|
||||
- Believable future technology
|
||||
- Technical readouts and data streams
|
||||
```
|
||||
|
||||
**代表作**:Blade Runner 2049 screen graphics
|
||||
**搜索关键词**:territory studio blade runner interface
|
||||
|
||||
---
|
||||
|
||||
## 五、东方哲学派(17-20)
|
||||
> 哲学:「留白即内容」
|
||||
|
||||
### 17. Takram - 日式思辨设计
|
||||
**哲学**:技术是思考的媒介
|
||||
**核心特征**:
|
||||
- 概念原型的优雅
|
||||
- 柔和的科技感(圆角、柔和阴影)
|
||||
- 图表即艺术
|
||||
- 谦逊的精致
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Takram Japanese speculative design:
|
||||
- Elegant concept prototypes and diagrams
|
||||
- Soft tech aesthetic (rounded corners, gentle shadows)
|
||||
- Charts and diagrams as art pieces
|
||||
- Modest sophistication
|
||||
- Neutral natural colors (beige, soft gray, muted green)
|
||||
- Design as philosophical inquiry
|
||||
```
|
||||
|
||||
**代表作**:NHK Fabricated City
|
||||
**搜索关键词**:takram nhk data visualization
|
||||
|
||||
---
|
||||
|
||||
### 18. Kenya Hara - 空的设计
|
||||
**哲学**:设计不是填充,是清空
|
||||
**核心特征**:
|
||||
- 极致的留白(80%+)
|
||||
- 纸张质感的数字化
|
||||
- 白色的层次(暖白、冷白、米白)
|
||||
- 触觉的视觉化
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Kenya Hara "emptiness" design:
|
||||
- Extreme whitespace (80%+)
|
||||
- Paper texture and tactility in digital form
|
||||
- Layers of white (warm white, cool white, off-white)
|
||||
- Minimal color (if any, very desaturated)
|
||||
- Design by subtraction not addition
|
||||
- Zen simplicity
|
||||
```
|
||||
|
||||
**代表作**:Muji art direction, 《Designing Design》
|
||||
**搜索关键词**:kenya hara designing design muji
|
||||
|
||||
---
|
||||
|
||||
### 19. Irma Boom - 书籍建筑师
|
||||
**哲学**:信息的物理诗学
|
||||
**核心特征**:
|
||||
- 非线性的信息架构
|
||||
- 边缘与边界的游戏
|
||||
- 意外的颜色组合(粉+红、橙+棕)
|
||||
- 手工艺的数字转译
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Irma Boom book architecture style:
|
||||
- Non-linear information structure
|
||||
- Play with edges, margins, boundaries
|
||||
- Unexpected color combos (pink+red, orange+brown)
|
||||
- Handcraft translated to digital
|
||||
- Dense information inviting exploration
|
||||
- Editorial design, unconventional grid
|
||||
```
|
||||
|
||||
**代表作**:SHV Think Book (2136 pages)
|
||||
**搜索关键词**:irma boom shv think book
|
||||
|
||||
---
|
||||
|
||||
### 20. Neo Shen - 东方光影诗
|
||||
**哲学**:技术需要人的温度
|
||||
**核心特征**:
|
||||
- 水墨晕染的数字化
|
||||
- 柔和的光晕效果
|
||||
- 诗意的留白
|
||||
- 情感化的色彩(深蓝、暖灰、柔金)
|
||||
|
||||
**提示词DNA**:
|
||||
```
|
||||
Neo Shen poetic Chinese aesthetic:
|
||||
- Digital interpretation of ink wash painting
|
||||
- Soft glow and light diffusion effects
|
||||
- Poetic negative space
|
||||
- Emotional palette (deep blues, warm grays, soft gold)
|
||||
- Calligraphic influences in typography
|
||||
- Atmospheric depth
|
||||
```
|
||||
|
||||
**代表作**:Neo Shen digital art series
|
||||
**搜索关键词**:neo shen digital ink wash art
|
||||
|
||||
---
|
||||
|
||||
## 提示词使用说明
|
||||
|
||||
**组合公式**:`[风格提示词DNA] + [场景模板(见scene-templates.md)] + [具体内容]`
|
||||
|
||||
### 核心原则:描述情绪而非布局(Mood, Not Layout)
|
||||
|
||||
AI图像生成的关键:短提示词 > 长提示词。描述3句情绪和内容,比30行布局细节效果更好。
|
||||
|
||||
| 杀死多样性的写法 | 激发创造力的写法 |
|
||||
|----------------|----------------|
|
||||
| 指定颜色比例(60%/25%/15%) | 描述情绪("warm like Sunday morning") |
|
||||
| 规定布局位置("标题居中,图片右侧") | 引用具体美学("Pentagram editorial feel") |
|
||||
| 限制角色姿势和表情 | 让AI自然诠释风格 |
|
||||
| 列出所有要包含的视觉元素 | 描述观众应该感受到什么 |
|
||||
|
||||
### Good / Bad 示例
|
||||
|
||||
**Bad — 过度约束(AI生成出来空且平):**
|
||||
```
|
||||
Professional presentation slide. Dark background, light text.
|
||||
Title centered at top. Two columns below. Left column: bullet points.
|
||||
Right column: bar chart. Colors: navy 60%, white 30%, gold 10%.
|
||||
Font size: title 36pt, body 18pt. Margins: 40px all sides.
|
||||
```
|
||||
|
||||
**Good — 情绪驱动(生成多样且有质感):**
|
||||
```
|
||||
A data visualization that feels like a Bloomberg Businessweek
|
||||
editorial spread. The key number "28.5%" should dominate the
|
||||
composition like a headline. Warm cream tones with sharp black
|
||||
typography. The data tells a story of dramatic channel shift.
|
||||
```
|
||||
|
||||
### 执行路径选择
|
||||
|
||||
根据速查表的「最佳路径」列选择:
|
||||
- **AI生成**:有明确视觉元素的风格(06/07/12/13/14/15/16/20),用 Gemini/Midjourney 直出
|
||||
- **HTML渲染**:依赖精确排版的风格(01/03/04/10/11/17/18),代码控制数据和布局
|
||||
- **混合**:HTML做骨架布局 + AI生成配图/背景(02/05/08/09/19)
|
||||
|
||||
### 质量控制
|
||||
|
||||
1. ❌ 不要直接写 "in the style of Pentagram" → ✅ 用具体设计特征描述
|
||||
2. 文字在AI生成中常出错 → 生成后替换文字
|
||||
3. 比例易失真 → 明确指定 aspect ratio
|
||||
4. 先生成3-5个变体,选择最佳后细化
|
||||
|
||||
**默认审美禁区**(用户可按自己品牌 override):
|
||||
- ❌ 赛博霓虹/深蓝色底(#0D1117)
|
||||
- ❌ 封面图加个人署名/水印
|
||||
|
||||
---
|
||||
|
||||
**版本**:v2.1
|
||||
**更新日期**:2026-02-13
|
||||
**适用场景**:网页/PPT/PDF/信息图/封面/配图/App等所有视觉设计
|
||||
**与 image-to-slides 联动**:PPT场景可直接引用本文件风格,通过 image-to-slides skill 执行生成
|
||||
334
.claude/skills/huashu-design/references/editable-pptx.md
Normal file
334
.claude/skills/huashu-design/references/editable-pptx.md
Normal 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×540pt(LAYOUT_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 尺寸决定的是**物理尺寸**不是清晰度。超大 body(20″×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 条规则。
|
||||
|
||||
### 规则 1:DIV 里不能直接写文字 — 必须用 `<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** 上写。
|
||||
|
||||
### 规则 4:DIV 不能用 `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 · 如果用户选 B:AI 主动改写,不要求用户自己写
|
||||
|
||||
这里的 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 格式,不是反过来。
|
||||
@@ -0,0 +1,250 @@
|
||||
# Gallery Ripple + Multi-Focus · 场景编排哲学
|
||||
|
||||
> 从 huashu-design hero 动画 v9(25 秒,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(~8s,4 次循环)**:镜头在慢速 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.94(zoom out to reveal)—— 配合出现的同步推远感。
|
||||
|
||||
### Multi-Focus(4 次节奏)
|
||||
|
||||
```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 喘息。总计 8s(11.0–19.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×540(hold 态)
|
||||
- 外围有 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. **纸感底色 + 赤陶橙 accent(Anthropic 血统)**
|
||||
|
||||
```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)
|
||||
- 源 HTML(v6 + 音频集成版):`www.huasheng.ai/huashu-design-hero/index.html`
|
||||
@@ -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.md(11500 字、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 graphic,logo 转一下」 | ❌ 不触发 | 用 animations.md 标准流程 |
|
||||
| 「做个 onboarding 动画 demo」 | ❌ 不触发 | 用 animations.md |
|
||||
| 「教程视频带配音」 | ❌ 不触发 | 走 voiceover-pipeline.md |
|
||||
| 「单个 hero animation」 | ⚠️ 看复杂度 | 如果是高规格 hero,触发;普通 hero 用 hero-animation-case-study.md |
|
||||
|
||||
---
|
||||
|
||||
## 9. 参考样本
|
||||
|
||||
完整 director's notes 参考样本(self-contained,本 skill 内):
|
||||
|
||||
`assets/director-notes-samples/launch-film-30s-sample.md`(约 78KB · 11500 字 · 13 镜 · 5 大部分齐全)
|
||||
|
||||
原始项目位置(含对应实施 HTML + 关键帧):
|
||||
|
||||
- `~/.claude/skills/huashu-md-html/demos/v5-director-notes.md`(director's notes)
|
||||
- `~/.claude/skills/huashu-md-html/demos/v5-six-forms.html`(HTML 实施)
|
||||
- `~/.claude/skills/huashu-md-html/demos/v5-keyframes/`(关键帧验证截图)
|
||||
|
||||
写新项目时强烈建议**先 Read 这份样本**,理解工作量和细节密度,再决定要不要全套走流程。
|
||||
|
||||
---
|
||||
|
||||
## 10. 反模式(不要这样做)
|
||||
|
||||
❌ **写 1000 字的精简版 director's notes 就动手**
|
||||
→ 精简版必然漏 Visual System 的某个子项,导致 HTML 实施时不停回头补 spec。要做就做万字级,要省就直接跳过。
|
||||
|
||||
❌ **storyboard 只写 5-8 镜**
|
||||
→ 30 秒片至少 12-15 镜(每镜 2-3 秒)。镜少 = 节奏匀速 = 没 climax。
|
||||
|
||||
❌ **director's notes 写完就交付,不做实施**
|
||||
→ 文档不是交付物,动画才是。文档 + 动画一起交付,文档作为「设计依据」附录。
|
||||
|
||||
❌ **多视角并行时让 subagent 看其他版本**
|
||||
→ 各 subagent 必须独立,否则趋同。审校阶段才对比。
|
||||
|
||||
❌ **跳过关键帧验证直接录 MP4**
|
||||
→ 必然返工。关键帧验证是最便宜的 quality gate。
|
||||
|
||||
❌ **把动画细节决策推迟到「等我录的时候再想」**
|
||||
→ 录制阶段是机械执行,不能做创意决策。所有决策必须在 director's notes 写死。
|
||||
|
||||
---
|
||||
|
||||
*最后修订:2026-05-11*
|
||||
*真实案例:huashu-md-html v2.0 launch film(v5-director-notes.md)*
|
||||
@@ -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 版本完成后系统审校
|
||||
|
||||
审校 framework(5 维度 + 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-notes:11500 字
|
||||
- 6 视角 director-notes 各 4000-12000 字
|
||||
- 总文档量:约 55000-70000 字
|
||||
- 5 大部分结构齐全:6/6 版本
|
||||
|
||||
### HTML 实施
|
||||
|
||||
- 每版独立 animation.html,30 秒,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: 不用 Inter(WKW 用衬线)
|
||||
|
||||
加了这些后,6 个版本风格纯度极高,无一相互趋同。
|
||||
|
||||
### 洞察 4 · 多视角的真正价值不是「选 winner」
|
||||
|
||||
最初设想是 A/B test 选最好的版本。实际审校时发现:**6 个版本各自有清晰 use case**:
|
||||
- v5 基线 → 产品页 / 微信读书(信息密度高)
|
||||
- Anderson → 公众号长文头图(翻杂志感强)
|
||||
- WKW → B 站 / 中文文化向(怀旧温度)
|
||||
- Vignelli → 设计圈 / Dribbble(每帧都是印刷海报)
|
||||
- Hara → 客户演示 / 静态截图(极简哲学)
|
||||
- Kusama → X 短视频 / 病毒传播(视觉冲击)
|
||||
|
||||
**结论**:marketing 不是 single-shot,是 platform-specific multiplex。6 视角并行的真正价值是**让一个项目有 6 个差异化武器**,不是让 5 个版本上不了台面。
|
||||
|
||||
### 洞察 5 · subagent 的失败率 ~16% 是可接受的
|
||||
|
||||
6 个里 1 个失败(Bass 首轮 socket error)。处理代价:重启 + 5 分钟简化版 brief,再等 12-15 分钟。**对比 vs. 等 1 个 agent 顺序跑 6 个版本(90+ 分钟)**——并行 + 重试明显更经济。
|
||||
|
||||
### 洞察 6 · 主线程在等待期间必须做 substantive idle work
|
||||
|
||||
subagent 完成需要 12-15 分钟。这段时间主线程绝不该空闲:
|
||||
|
||||
- **修主版本 bug**(用户已经反馈的)
|
||||
- **写 review framework**(等审校时填)
|
||||
- **沉淀方法论到 skill**(如这份 case study)
|
||||
- **准备 final summary**(用户回来一目了然)
|
||||
|
||||
这是 parallel multi-agent workflow 的「主线程职责」——不是 PM 等结果,是 orchestrator 同步推进。
|
||||
|
||||
---
|
||||
|
||||
## 何时启用「多视角并行」
|
||||
|
||||
| 场景 | 是否启用 | 原因 |
|
||||
|------|---------|------|
|
||||
| 用户明确说「想看不同方向」「再多做几个版本」 | ✅ 立刻启用 | 直接需求 |
|
||||
| 第一版做出来用户不满意但说不清要啥 | ✅ 启用 | A/B 选优于「我猜你要啥」 |
|
||||
| 项目准备多平台分发(X / 公众号 / B 站 / 朋友圈) | ✅ 启用 | 每平台一个版本 |
|
||||
| 客户没拍板风格但有预算(time + token) | ✅ 启用 | 反复改 = 5 倍代价 |
|
||||
| 用户已经给了明确风格参考且只要 1 个版本 | ❌ 不启用 | 浪费 |
|
||||
| 任务是简单 motion graphic / icon 动画 | ❌ 不启用 | 过度工程化 |
|
||||
| 时间紧 < 30 分钟 | ❌ 不启用 | subagent 跑不完 |
|
||||
|
||||
---
|
||||
|
||||
## 完整方法论流程图
|
||||
|
||||
```
|
||||
用户 brief(含质量预期)
|
||||
↓
|
||||
[主线程] 写 v5 基线 director's notes(万字级 5 大部分)
|
||||
↓
|
||||
[主线程] 实施 v5 HTML + 截关键帧(marketing baseline)
|
||||
↓
|
||||
[决策点] 是否启用多视角?
|
||||
↓ YES
|
||||
[主线程] 选 6 个差异化视角 + 写 6 份独立 brief(每份 8 字段)
|
||||
↓
|
||||
[6 subagents 并行]
|
||||
├── v5a brief → director-notes + html + keyframes + README
|
||||
├── v5b brief → ...
|
||||
├── v5c brief → ...
|
||||
├── v5d brief → ...
|
||||
├── v5e brief → ...
|
||||
└── v5f brief → ...
|
||||
↓
|
||||
[主线程同步做] 修 v5 bug · 写 review framework · 沉淀方法论
|
||||
↓
|
||||
[全 6 通知到达]
|
||||
↓
|
||||
[主线程] 失败检测 + 重试 / 补截图
|
||||
↓
|
||||
[主线程] 5 维度评分 + 3 顶层问 + use case 分配
|
||||
↓
|
||||
[主线程] 写 final REVIEW.md
|
||||
↓
|
||||
[交付] 6 完整版本 + review + 平台分发推荐
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 完整方法论:`references/launch-film-director-notes.md`
|
||||
- 单视角样本:`assets/director-notes-samples/launch-film-30s-sample.md`(v5 基线)
|
||||
- 实战项目位置:`~/.claude/skills/huashu-md-html/demos/`(含 6 + 1 视角全套文件)
|
||||
- 审校 review:`~/.claude/skills/huashu-md-html/demos/REVIEW.md`
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-05-11*
|
||||
*Real case study: huashu-md-html v2.0 launch film 6-perspective parallel experiment*
|
||||
276
.claude/skills/huashu-design/references/react-setup.md
Normal file
276
.claude/skills/huashu-design/references/react-setup.md
Normal 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冲突。
|
||||
|
||||
## 三条不可违反的规矩
|
||||
|
||||
### 规矩1:styles 对象必须用唯一命名
|
||||
|
||||
**错误**(多组件时必炸):
|
||||
```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命名,否则多组件加载时全栈报错。
|
||||
|
||||
### 规矩2:Scope 不共享,需手动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 API(HTML内)
|
||||
|
||||
部分原生 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, {...})`导出要共享的东西。
|
||||
262
.claude/skills/huashu-design/references/scene-templates.md
Normal file
262
.claude/skills/huashu-design/references/scene-templates.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# 场景模板库:按输出类型组织
|
||||
|
||||
> 与 design-styles.md 的「提示词DNA」组合使用。
|
||||
> 公式:`[风格提示词DNA] + [场景模板] + [具体内容描述]`
|
||||
|
||||
---
|
||||
|
||||
## 1. 公众号封面 / 文章题图
|
||||
|
||||
**规格**:
|
||||
- 封面图:2.35:1(900×383px 或 1200×510px)
|
||||
- 正文插图:16:9(1200×675px)或 4:3(1200×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:9(1200×675px)最通用
|
||||
- 1:1(800×800px)适合强调
|
||||
- 4:3(1200×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:9(1920×1080px)
|
||||
- 宽屏:16:10(1920×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×844pt(iPhone 15)
|
||||
- Android: 360×800dp
|
||||
- 平板: 1024×1366pt(iPad 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:4(1080×1440px)最佳
|
||||
- 方形:1:1(1080×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
|
||||
226
.claude/skills/huashu-design/references/sfx-library.md
Normal file
226
.claude/skills/huashu-design/references/sfx-library.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# SFX Library · huashu-design
|
||||
|
||||
> 全部由 ElevenLabs Sound Generation API 生成,苹果发布会级音质。
|
||||
> 产品级 SFX 资产库,覆盖花叔动画/演示/产品 Demo 全场景。
|
||||
|
||||
**资产位置**:`assets/sfx/<category>/<name>.mp3`
|
||||
**总数**:37 个 SFX(30 批量生成 + 7 个 v7b 保留)
|
||||
**生成模型**:ElevenLabs Sound Generation API(prompt_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 | 移动端 tap(iOS 界面) | 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 | 标准 whoosh(v7b 保留) | 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 impact(v7b 保留) | 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 |
|
||||
|
||||
### ✨ Magic(AI 变换)
|
||||
|
||||
| 文件 | 时长 | 用途 | 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`(一次性批量生成器)
|
||||
726
.claude/skills/huashu-design/references/slide-decks.md
Normal file
726
.claude/skills/huashu-design/references/slide-decks.md
Normal file
@@ -0,0 +1,726 @@
|
||||
# Slide Decks:HTML幻灯片制作规范
|
||||
|
||||
做幻灯片是设计工作的高频场景。这份文档说明怎么做好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 直接上 html2pptx,pass 率 < 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. 截图让用户确认 grammar(masthead / 字体 / 色 / 间距 / 结构 / 中英双语比例)
|
||||
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-21px,line-height 1.75-1.85
|
||||
- **accent 高亮**:正文里用主色加粗标注关键词,每页不超过 3 处(过多就失去锚点作用)
|
||||
- **背景**:暖米底 #FAFAFA + 极淡 radial-gradient noise(`rgba(33,33,33,0.015)`)增加纸感
|
||||
|
||||
### 视觉主角必须差异化
|
||||
|
||||
13 页如果全是「文字 + 一张截图」就太单调。**每页的视觉主角类型轮换**:
|
||||
|
||||
| 视觉类型 | 适合的 section |
|
||||
|---------|---------------|
|
||||
| 封面排版(大字 + masthead + pillar) | 首页 / 篇章封 |
|
||||
| 单角色 portrait(超大单只 momo 等) | 介绍单个概念/角色 |
|
||||
| 多角色合影 / 头像卡并排 | 团队 / 用户案例 |
|
||||
| 时间轴卡片递进 | 展示「长期关系」「演进」 |
|
||||
| 知识图谱 / 连接节点图 | 展示「协作」「流动」 |
|
||||
| Before/After 对比卡 + 中间箭头 | 展示「改变」「差异」 |
|
||||
| 产品 UI 截图 + 描边设备框 | 具体功能展示 |
|
||||
| 大引号 big-quote(半页大字) | 情绪页 / 问题页 / 引文页 |
|
||||
| 真人头像 + 引言卡(2×2 或 1×4) | 用户见证 / 使用场景 |
|
||||
| 大字封底 + URL 椭圆按钮 | CTA / 结尾 |
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 常见踩坑(moxt 实战总结)
|
||||
|
||||
### 1. Emoji 在 Chromium / Playwright 导出时不渲染
|
||||
|
||||
Chromium 默认不带彩色 emoji 字体,`page.pdf()` 或 `page.screenshot()` 时 emoji 显示为空方框。
|
||||
|
||||
**对策**:用 Unicode 文字符号(`✦` `✓` `✕` `→` `·` `—`)替代,或直接改纯文字(「Email · 23」而不是「📧 23 emails」)。
|
||||
|
||||
### 2. `export_deck_pdf.mjs` 报错 `Cannot find package 'playwright'`
|
||||
|
||||
原因:ESM 模块解析从脚本所在位置向上找 `node_modules`。脚本在 `~/.claude/skills/huashu-design/scripts/`,那里没依赖。
|
||||
|
||||
**对策**:把脚本复制到 deck 项目目录(例如 `brochure/build-pdf.mjs`),在项目根跑 `npm install playwright pdf-lib`,然后 `node build-pdf.mjs --slides slides --out output/deck.pdf`。
|
||||
|
||||
### 3. Google Fonts 没加载完就截图 → 中文显示为系统默认黑体
|
||||
|
||||
Playwright 截图/PDF 前至少 `wait-for-timeout=3500` 让 webfont 下载并 paint。或者把字体 self-host 到 `shared/fonts/` 减少网络依赖。
|
||||
|
||||
### 4. 信息密度失衡:内容页塞太多
|
||||
|
||||
moxt philosophy 页第一版用 2×2 = 4 段 + 底部 3 信条 = 7 块内容,挤压且重复。改成 1×3 = 3 段后呼吸感立刻回来。
|
||||
|
||||
**对策**:每页控制在「1 个核心信息 + 3-4 个辅助点 + 1 个视觉主角」,超过就拆到新页。**少即是多**——观众一页看 10 秒,给他 1 个记忆点比 4 个记忆点更容易记住。
|
||||
|
||||
---
|
||||
|
||||
## 🛑 先定架构:单文件 还是 多文件?
|
||||
|
||||
**这个选择是做幻灯片的第一步,错了会反复踩坑。先读完这一节再动手。**
|
||||
|
||||
### 两种架构对比
|
||||
|
||||
| 维度 | 单文件 + `deck_stage.js` | **多文件 + `deck_index.html` 拼接器** |
|
||||
|------|--------------------------|--------------------------------------|
|
||||
| 代码结构 | 一个 HTML,所有 slide 是 `<section>` | 每页独立 HTML,`index.html` 用 iframe 拼接 |
|
||||
| CSS 作用域 | ❌ 全局,一页的样式可能影响所有页 | ✅ 天然隔离,iframe 各自一片天 |
|
||||
| 验证粒度 | ❌ 要 JS goTo 才能切到某页 | ✅ 单页文件双击就能在浏览器看 |
|
||||
| 并行开发 | ❌ 一个文件,多 agent 改会冲突 | ✅ 多 agent 可并行做不同页,零冲突 merge |
|
||||
| 调试难度 | ❌ 一处 CSS 出错,全 deck 翻车 | ✅ 一页出错只影响自己 |
|
||||
| 内嵌交互 | ✅ 跨页共享状态很简单 | 🟡 iframe 间需 postMessage |
|
||||
| 打印 PDF | ✅ 内置 | ✅ 拼接器 beforeprint 遍历 iframe |
|
||||
| 键盘导航 | ✅ 内置 | ✅ 拼接器内置 |
|
||||
|
||||
### 选哪个?(决策树)
|
||||
|
||||
```
|
||||
│ 问:deck 预计有多少页?
|
||||
├── ≤10 页、需要 in-deck 动画或跨页交互、pitch deck → 单文件
|
||||
└── ≥10 页、学术讲座、课件、长 deck、多 agent 并行 → 多文件(推荐)
|
||||
```
|
||||
|
||||
**默认走多文件路径**。它不是「备选」,是**长 deck 和团队协作的主路径**。原因:单文件架构的每一个优势(键盘导航、打印、scale)多文件都有,而多文件的作用域隔离和可验证性是单文件补不回来的。
|
||||
|
||||
### 为什么这条规则这么硬?(真实事故记录)
|
||||
|
||||
单文件架构曾经在 AI心理学讲座 deck 制作中连踩四坑:
|
||||
|
||||
1. **CSS 特异性覆盖**:`.emotion-slide { display: grid }` (特异性 10) 干翻 `deck-stage > section { display: none }` (特异性 2),导致所有页同时渲染叠加。
|
||||
2. **Shadow DOM slot 规则被外层 CSS 压制**:`::slotted(section) { display: none }` 挡不住 outer rule 的覆盖,sections 不肯隐藏。
|
||||
3. **localStorage + hash 导航竞态**:刷新后不是跳到 hash 位置,而是停在 localStorage 记录的旧位置。
|
||||
4. **验证成本高**:必须 `page.evaluate(d => d.goTo(n))` 才能截某页,比直接 `goto(file://.../slides/05-X.html)` 慢一倍,还常报错。
|
||||
|
||||
全部根因是**单一全局命名空间**——多文件架构从物理层面把这些问题消除了。
|
||||
|
||||
---
|
||||
|
||||
## 路径 A(默认):多文件架构
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
我的Deck/
|
||||
├── index.html # 从 assets/deck_index.html 复制来,改 MANIFEST
|
||||
├── shared/
|
||||
│ ├── tokens.css # 共享设计 token(色板/字号/常用 chrome)
|
||||
│ └── fonts.html # <link> 引入 Google Fonts(每页 include)
|
||||
└── slides/
|
||||
├── 01-cover.html # 每个文件都是完整 1920×1080 HTML
|
||||
├── 02-agenda.html
|
||||
├── 03-problem.html
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 每张 slide 的模板骨架
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>P05 · Chapter Title</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=..." rel="stylesheet">
|
||||
<link rel="stylesheet" href="../shared/tokens.css">
|
||||
<style>
|
||||
/* 这一页独有的样式。用任何 class 名都不会污染别的页。*/
|
||||
body { padding: 120px; }
|
||||
.my-thing { ... }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 1920×1080 的内容(由 body 的 width/height 在 tokens.css 里锁定)-->
|
||||
<div class="page-header">...</div>
|
||||
<div>...</div>
|
||||
<div class="page-footer">...</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
**关键约束**:
|
||||
- `<body>` 就是画布,直接在上面布局。不要包 `<section>` 或其他 wrapper。
|
||||
- `width: 1920px; height: 1080px` 由 `shared/tokens.css` 里的 `body` 规则锁定。
|
||||
- 引 `shared/tokens.css` 共享设计 token(色板、字号、page-header/footer 等)。
|
||||
- 字体 `<link>` 每页自己写(fonts 单独 import 不贵,且保证每页独立可打开)。
|
||||
|
||||
### 拼接器:`deck_index.html`
|
||||
|
||||
**直接从 `assets/deck_index.html` 复制**。你只需要改一处——`window.DECK_MANIFEST` 数组,按顺序列出所有 slide 文件名和人类可读标签:
|
||||
|
||||
```js
|
||||
window.DECK_MANIFEST = [
|
||||
{ file: "slides/01-cover.html", label: "封面" },
|
||||
{ file: "slides/02-agenda.html", label: "目录" },
|
||||
{ file: "slides/03-problem.html", label: "问题陈述" },
|
||||
// ...
|
||||
];
|
||||
```
|
||||
|
||||
拼接器已内置:键盘导航(←/→/Home/End/数字键/P 打印)、scale + letterbox、右下计数器、localStorage 记忆、hash 跳页、打印模式(遍历 iframe 按页输出 PDF)。
|
||||
|
||||
### 单页验证(这是多文件架构的杀手级优势)
|
||||
|
||||
每张 slide 都是独立 HTML。**做完一张就在浏览器双击打开看**:
|
||||
|
||||
```bash
|
||||
open slides/05-personas.html
|
||||
```
|
||||
|
||||
Playwright 截图也是直接 `goto(file://.../slides/05-personas.html)`,不需要 JS 跳页,也不会被别的页的 CSS 干扰。这让「改一点验一点」的工作流成本接近零。
|
||||
|
||||
### 并行开发
|
||||
|
||||
把每张 slide 的任务拆给不同 agent,同时跑——HTML 文件彼此独立,merge 时没有冲突。长 deck 用这种并行方式能把制作时间压到 1/N。
|
||||
|
||||
### `shared/tokens.css` 该放什么
|
||||
|
||||
只放**真正跨页共用**的东西:
|
||||
|
||||
- CSS 变量(色板、字号阶、间距阶)
|
||||
- `body { width: 1920px; height: 1080px; }` 这样的 canvas 锁定
|
||||
- `.page-header` / `.page-footer` 这种每页都用一模一样的 chrome
|
||||
|
||||
**不要**把单页的布局 class 塞进来——那会退化回单文件架构的全局污染问题。
|
||||
|
||||
---
|
||||
|
||||
## 路径 B(小 deck):单文件 + `deck_stage.js`
|
||||
|
||||
适用于 ≤10 页、需要跨页共享状态(比如一个 React tweaks 面板要操控所有页)、或者做 pitch deck demo 这种要求极度紧凑的场景。
|
||||
|
||||
### 基本用法
|
||||
|
||||
1. 从 `assets/deck_stage.js` 读取内容,嵌入 HTML 的 `<script>`(或 `<script src="deck_stage.js">`)
|
||||
2. 在 body 里用 `<deck-stage>` 包 slide
|
||||
3. 🛑 **script 标签必须放在 `</deck-stage>` 之后**(见下方硬约束)
|
||||
|
||||
```html
|
||||
<body>
|
||||
|
||||
<deck-stage>
|
||||
<section>
|
||||
<h1>Slide 1</h1>
|
||||
</section>
|
||||
<section>
|
||||
<h1>Slide 2</h1>
|
||||
</section>
|
||||
</deck-stage>
|
||||
|
||||
<!-- ✅ 正确:script 在 deck-stage 之后 -->
|
||||
<script src="deck_stage.js"></script>
|
||||
|
||||
</body>
|
||||
```
|
||||
|
||||
### 🛑 Script 位置硬约束(2026-04-20 真实踩坑)
|
||||
|
||||
**不能把 `<script src="deck_stage.js">` 放在 `<head>` 里。** 即使它在 `<head>` 里能定义 `customElements`,parser 在解析到 `<deck-stage>` 开始标签时就会触发 `connectedCallback`——此时子 `<section>` 还没被 parse,`_collectSlides()` 拿到空数组,counter 显示 `1 / 0`,所有页同时叠加渲染。
|
||||
|
||||
**三条合规写法**(任选其一):
|
||||
|
||||
```html
|
||||
<!-- ✅ 最推荐:script 在 </deck-stage> 之后 -->
|
||||
</deck-stage>
|
||||
<script src="deck_stage.js"></script>
|
||||
|
||||
<!-- ✅ 也可:script 在 head 但加 defer -->
|
||||
<head><script src="deck_stage.js" defer></script></head>
|
||||
|
||||
<!-- ✅ 也可:module 脚本天然 defer -->
|
||||
<head><script src="deck_stage.js" type="module"></script></head>
|
||||
```
|
||||
|
||||
`deck_stage.js` 本身已内置 `DOMContentLoaded` 延迟收集防御,即使 script 放 head 也不会彻底炸掉——但 `defer` 或放 body 底部仍然是更干净的做法,避免依赖防御分支。
|
||||
|
||||
### ⚠️ 单文件架构的 CSS 陷阱(务必阅读)
|
||||
|
||||
单文件架构最常见的坑——**`display` 属性被单页样式偷走**。
|
||||
|
||||
常见错误姿势 1(直接写 display: flex 到 section):
|
||||
|
||||
```css
|
||||
/* ❌ 外部 CSS 特异性 2,覆盖了 shadow DOM 的 ::slotted(section){display:none}(也是 2)*/
|
||||
deck-stage > section {
|
||||
display: flex; /* 所有页会同时叠加渲染! */
|
||||
flex-direction: column;
|
||||
padding: 80px;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
常见错误姿势 2(section 有特异性更高的 class):
|
||||
|
||||
```css
|
||||
.emotion-slide { display: grid; } /* 特异性: 10,更糟 */
|
||||
```
|
||||
|
||||
两种都会让 **所有 slide 同时叠加渲染**——counter 可能显示 `1 / 10` 假装正常,但视觉上第一页盖着第二页盖着第三页。
|
||||
|
||||
### ✅ Starter CSS(开工直接 copy,不踩坑)
|
||||
|
||||
**section 自身**只管「可见/不可见」;**layout(flex/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 Serif,body 用 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` 残留,确认都清理了
|
||||
313
.claude/skills/huashu-design/references/tweaks-system.md
Normal file
313
.claude/skills/huashu-design/references/tweaks-system.md
Normal 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)
|
||||
- 字型(select:display 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)
|
||||
- Easing(linear/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(...);
|
||||
};
|
||||
```
|
||||
189
.claude/skills/huashu-design/references/verification.md
Normal file
189
.claude/skills/huashu-design/references/verification.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Verification:输出验证流程
|
||||
|
||||
一些 design-agent 原生环境(如 Claude.ai Artifacts)有内置的 `fork_verifier_agent` 起 subagent 用 iframe 截图检查。大部分 agent 环境(Claude Code / Codex / Cursor / Trae / 等)里没有这个内置能力——用 Playwright 手动做就能覆盖相同的验证场景。
|
||||
|
||||
## 验证清单
|
||||
|
||||
每次产出HTML后,按这个清单做一遍:
|
||||
|
||||
### 1. 浏览器渲染检查(必做)
|
||||
|
||||
最基础:**HTML能不能打开**?在macOS上:
|
||||
|
||||
```bash
|
||||
open -a "Google Chrome" "/path/to/your/design.html"
|
||||
```
|
||||
|
||||
或者用Playwright截图(下一节)。
|
||||
|
||||
### 2. 控制台错误检查
|
||||
|
||||
HTML文件里最常见的问题是JS报错导致白屏。用Playwright跑一遍:
|
||||
|
||||
```bash
|
||||
python ~/.claude/skills/claude-design/scripts/verify.py path/to/design.html
|
||||
```
|
||||
|
||||
这个脚本会:
|
||||
1. 用headless chromium打开HTML
|
||||
2. 截图保存到项目目录
|
||||
3. 抓取控制台错误
|
||||
4. 报告status
|
||||
|
||||
详见`scripts/verify.py`。
|
||||
|
||||
### 3. 多视口检查
|
||||
|
||||
如果是响应式设计,抓多个viewport:
|
||||
|
||||
```bash
|
||||
python verify.py design.html --viewports 1920x1080,1440x900,768x1024,375x667
|
||||
```
|
||||
|
||||
### 4. 交互检查
|
||||
|
||||
Tweaks、动画、按钮切换——默认的静态截图看不到。**建议让用户自己开浏览器点一遍**,或者用Playwright录屏:
|
||||
|
||||
```python
|
||||
page.video.record('interaction.mp4')
|
||||
```
|
||||
|
||||
### 5. 幻灯片逐页检查
|
||||
|
||||
Deck类HTML,一张张截:
|
||||
|
||||
```bash
|
||||
python verify.py deck.html --slides 10 # 截前10张
|
||||
```
|
||||
|
||||
生成 `deck-slide-01.png`、`deck-slide-02.png`... 方便快速浏览。
|
||||
|
||||
## Playwright Setup
|
||||
|
||||
首次使用需要:
|
||||
|
||||
```bash
|
||||
# 如果还没装
|
||||
npm install -g playwright
|
||||
npx playwright install chromium
|
||||
|
||||
# 或者Python版
|
||||
pip install playwright
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
如果用户已经全局安装 Playwright,直接用即可。
|
||||
|
||||
## 截图最佳实践
|
||||
|
||||
### 截完整页面
|
||||
|
||||
```python
|
||||
page.screenshot(path='full.png', full_page=True)
|
||||
```
|
||||
|
||||
### 截viewport
|
||||
|
||||
```python
|
||||
page.screenshot(path='viewport.png') # 默认只截可见区域
|
||||
```
|
||||
|
||||
### 截特定元素
|
||||
|
||||
```python
|
||||
element = page.query_selector('.hero-section')
|
||||
element.screenshot(path='hero.png')
|
||||
```
|
||||
|
||||
### 高清截图
|
||||
|
||||
```python
|
||||
page = browser.new_page(device_scale_factor=2) # retina
|
||||
```
|
||||
|
||||
### 等动画结束再截
|
||||
|
||||
```python
|
||||
page.wait_for_timeout(2000) # 等2秒让动画settle
|
||||
page.screenshot(...)
|
||||
```
|
||||
|
||||
## 把截图发给用户
|
||||
|
||||
### 本地截图直接打开
|
||||
|
||||
```bash
|
||||
open screenshot.png
|
||||
```
|
||||
|
||||
用户会在自己的 Preview/Figma/VSCode/浏览器 里看。
|
||||
|
||||
### 上传图床分享链接
|
||||
|
||||
如果需要给远程协作者看(比如 Slack/飞书/微信),让用户用自己的图床工具或 MCP 上传:
|
||||
|
||||
```bash
|
||||
python ~/Documents/写作/tools/upload_image.py screenshot.png
|
||||
```
|
||||
|
||||
返回ImgBB的永久链接,可以粘贴到任何地方。
|
||||
|
||||
## 验证出错时
|
||||
|
||||
### 页面白屏
|
||||
|
||||
控制台一定有错。先检查:
|
||||
|
||||
1. React+Babel script tag的integrity hash对不对(见`react-setup.md`)
|
||||
2. 是不是`const styles = {...}`命名冲突
|
||||
3. 跨文件的组件有没有export到`window`
|
||||
4. JSX语法错误(babel.min.js不报错,换babel.js非压缩版)
|
||||
|
||||
### 动画卡
|
||||
|
||||
- 用Chrome DevTools Performance tab录一段
|
||||
- 找layout thrashing(频繁的reflow)
|
||||
- 动效优先用`transform`和`opacity`(GPU加速)
|
||||
|
||||
### 字体不对
|
||||
|
||||
- 检查`@font-face`的url是否可访问
|
||||
- 检查fallback字体
|
||||
- 中文字体加载慢:先显示fallback,加载完再切换
|
||||
|
||||
### 布局错位
|
||||
|
||||
- 检查`box-sizing: border-box`是否全局应用
|
||||
- 检查`* margin: 0; padding: 0`reset
|
||||
- Chrome DevTools里打开gridlines看实际布局
|
||||
|
||||
## 验证=设计师的第二双眼
|
||||
|
||||
**永远要自己过一遍**。AI写代码时经常出现:
|
||||
|
||||
- 看起来对但interaction有bug
|
||||
- 静态截图好但scroll时错位
|
||||
- 宽屏好看但窄屏崩
|
||||
- Dark mode忘了测
|
||||
- Tweaks切换后某些组件没响应
|
||||
|
||||
**最后1分钟的验证可以省1小时的返工**。
|
||||
|
||||
## 常用验证脚本命令
|
||||
|
||||
```bash
|
||||
# 基础:打开+截图+抓错
|
||||
python verify.py design.html
|
||||
|
||||
# 多viewport
|
||||
python verify.py design.html --viewports 1920x1080,375x667
|
||||
|
||||
# 多slide
|
||||
python verify.py deck.html --slides 10
|
||||
|
||||
# 输出到指定目录
|
||||
python verify.py design.html --output ./screenshots/
|
||||
|
||||
# headless=false,打开真实浏览器给你看
|
||||
python verify.py design.html --show
|
||||
```
|
||||
209
.claude/skills/huashu-design/references/video-export.md
Normal file
209
.claude/skills/huashu-design/references/video-export.md
Normal file
@@ -0,0 +1,209 @@
|
||||
# Video Export:HTML 动画导出为 MP4/GIF
|
||||
|
||||
动画 HTML 完成后,用户常想「能导出视频吗」。这份指南给出完整流程。
|
||||
|
||||
## 何时导出
|
||||
|
||||
**导出时机**:
|
||||
- 动画完整跑通、视觉验证过(Playwright 截图确认各时间点状态正确)
|
||||
- 用户在浏览器里看过至少一次,表示效果 OK
|
||||
- **不要**在动画 bug 没修完的阶段导出——导出到视频后改起来更贵
|
||||
|
||||
**用户可能说的触发语**:
|
||||
- 「能导出成视频吗」
|
||||
- 「转成 MP4」
|
||||
- 「做成 GIF」
|
||||
- 「60fps」
|
||||
|
||||
## 产出规格
|
||||
|
||||
默认一次给三种格式,让用户选:
|
||||
|
||||
| 格式 | 规格 | 适合场景 | 典型大小(30s) |
|
||||
|---|---|---|---|
|
||||
| MP4 25fps | 1920×1080 · H.264 · CRF 18 | 公众号嵌入、视频号、YouTube | 1-2 MB |
|
||||
| MP4 60fps | 1920×1080 · minterpolate 插帧 · H.264 · CRF 18 | 高帧率展示、B站、作品集 | 1.5-3 MB |
|
||||
| GIF | 960×540 · 15fps · palette 优化 | Twitter/X、README、Slack 预览 | 2-4 MB |
|
||||
|
||||
## 工具链
|
||||
|
||||
两个脚本在 `scripts/`:
|
||||
|
||||
### 1. `render-video.js` — HTML → MP4
|
||||
|
||||
录一个 25fps 的 MP4 基础版本。依赖全局 playwright。
|
||||
|
||||
```bash
|
||||
NODE_PATH=$(npm root -g) node /path/to/claude-design/scripts/render-video.js <html文件>
|
||||
```
|
||||
|
||||
可选参数:
|
||||
- `--duration=30` 动画时长(秒)
|
||||
- `--width=1920 --height=1080` 分辨率
|
||||
- `--trim=2.2` 从视频开头裁掉的秒数(去掉 reload + 字体加载时间)
|
||||
- `--fontwait=1.5` 字体加载等待时间(秒),字体多时调高
|
||||
|
||||
输出:与 HTML 同目录,同名 `.mp4`。
|
||||
|
||||
### 2. `add-music.sh` — MP4 + BGM → MP4
|
||||
|
||||
给无声 MP4 混入背景音乐,按场景(mood)从内置 BGM 库里选,也可自带音频。自动匹配时长、加淡入淡出。
|
||||
|
||||
```bash
|
||||
bash add-music.sh <input.mp4> [--mood=<name>] [--music=<path>] [--out=<path>]
|
||||
```
|
||||
|
||||
**内置 BGM 库**(在 `assets/bgm-<mood>.mp3`):
|
||||
|
||||
| `--mood=` | 风格 | 适配场景 |
|
||||
|-----------|------|---------|
|
||||
| `tech`(默认) | Apple Silicon / 苹果发布会,极简合成器+钢琴 | 产品发布、AI工具、Skill 宣传 |
|
||||
| `ad` | upbeat 现代电子,有 build + drop | 社交媒体广告、产品预告、促销片 |
|
||||
| `educational` | 温暖明亮、轻吉他/电钢琴,inviting | 科普、教程介绍、课程预告 |
|
||||
| `educational-alt` | 同类备选,换一首试试 | 同上 |
|
||||
| `tutorial` | lo-fi 环境音,几乎无存在感 | 软件演示、编程教程、长演示 |
|
||||
| `tutorial-alt` | 同类备选 | 同上 |
|
||||
|
||||
**行为**:
|
||||
- 音乐按视频时长裁剪
|
||||
- 0.3s 淡入 + 1s 淡出(避免硬切)
|
||||
- 视频流 `-c:v copy` 不重编码,音频 AAC 192k
|
||||
- `--music=<path>` 优先级高于 `--mood`,可以直接指定任意外部音频
|
||||
- 传错 mood 名会列出所有可用选项,不会静默失败
|
||||
|
||||
**典型流水线**(动画导出三件套 + 配乐):
|
||||
```bash
|
||||
node render-video.js animation.html # 录屏
|
||||
bash convert-formats.sh animation.mp4 # 派生 60fps + GIF
|
||||
bash add-music.sh animation-60fps.mp4 # 加默认 tech BGM
|
||||
# 或针对不同场景:
|
||||
bash add-music.sh tutorial-demo.mp4 --mood=tutorial
|
||||
bash add-music.sh product-promo.mp4 --mood=ad --out=promo-final.mp4
|
||||
```
|
||||
|
||||
### 3. `convert-formats.sh` — MP4 → 60fps MP4 + GIF
|
||||
|
||||
从已有 MP4 生成 60fps 版本和 GIF。
|
||||
|
||||
```bash
|
||||
bash /path/to/claude-design/scripts/convert-formats.sh <input.mp4> [gif_width] [--minterpolate]
|
||||
```
|
||||
|
||||
输出(与输入同目录):
|
||||
- `<name>-60fps.mp4` — 默认用 `fps=60` 帧复制(兼容性广);加 `--minterpolate` 启用高质量插帧
|
||||
- `<name>.gif` — palette 优化的 GIF(默认 960 宽,可改)
|
||||
|
||||
**60fps 模式选择**:
|
||||
|
||||
| 模式 | 命令 | 兼容性 | 使用场景 |
|
||||
|---|---|---|---|
|
||||
| 帧复制(默认)| `convert-formats.sh in.mp4` | QuickTime/Safari/Chrome/VLC 全通 | 通用交付、上传平台、社交媒体 |
|
||||
| minterpolate 插帧 | `convert-formats.sh in.mp4 --minterpolate` | macOS QuickTime/Safari 可能拒打 | B站等需要真插帧的展示场景,**交付前必须本地测**目标播放器 |
|
||||
|
||||
为什么默认改成帧复制?minterpolate 输出的 H.264 elementary stream 有 known compat bug——之前默认 minterpolate 时多次踩到「macOS QuickTime 打不开」的问题。详见 `animation-pitfalls.md` §14。
|
||||
|
||||
`gif_width` 参数:
|
||||
- 960(默认)—— 社交平台通用
|
||||
- 1280 —— 更清晰但文件更大
|
||||
- 600 —— Twitter/X 优先加载
|
||||
|
||||
## 完整流程(标准推荐)
|
||||
|
||||
用户说「导出视频」后:
|
||||
|
||||
```bash
|
||||
cd <项目目录>
|
||||
|
||||
# 假设 $SKILL 指向本 skill 的根目录(自行按安装位置替换)
|
||||
|
||||
# 1. 录 25fps 基础 MP4
|
||||
NODE_PATH=$(npm root -g) node "$SKILL/scripts/render-video.js" my-animation.html
|
||||
|
||||
# 2. 派生 60fps MP4 和 GIF
|
||||
bash "$SKILL/scripts/convert-formats.sh" my-animation.mp4
|
||||
|
||||
# 产出清单:
|
||||
# my-animation.mp4 (25fps · 1-2 MB)
|
||||
# my-animation-60fps.mp4 (60fps · 1.5-3 MB)
|
||||
# my-animation.gif (15fps · 2-4 MB)
|
||||
```
|
||||
|
||||
## 技术细节(排错用)
|
||||
|
||||
### Playwright recordVideo 的坑
|
||||
|
||||
- 帧率固定 25fps,无法直接录 60fps(Chromium 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-28;GIF:降分辨率到 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 倍) |
|
||||
397
.claude/skills/huashu-design/references/voiceover-pipeline.md
Normal file
397
.claude/skills/huashu-design/references/voiceover-pipeline.md
Normal 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>` 各自独立 layout,scene 切换 = 整页 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-side:md 变大变粗占据画面,html 退到角落小字;数据围绕 md 涌入
|
||||
- html-side:html 反转为主角;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` + 白光晕**;深底用白字 + 黑光晕 | 浅底白字+黑描边 = 字糊 |
|
||||
| 字号 | 32px(1080p 视频)| <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` 是否正确 |
|
||||
| 某段音频明显比脚本长/短 | 该段文本里有奇怪标点或 emoji,TTS 解析异常 → 改稿 |
|
||||
| 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**。
|
||||
216
.claude/skills/huashu-design/references/workflow.md
Normal file
216
.claude/skills/huashu-design/references/workflow.md
Normal 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 1:Assumptions + Placeholders(5-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,结束。
|
||||
Reference in New Issue
Block a user