Initial: pi-skill — 68 skills, 43 extensions, 11 themes for Pi

This commit is contained in:
Kunthawat Greethong
2026-05-25 16:38:02 +07:00
commit 69f7d8bdda
1689 changed files with 342427 additions and 0 deletions

View File

@@ -0,0 +1,259 @@
# Accessibility
iOS accessibility guide covering Dynamic Type, semantic colors, VoiceOver, and motion adaptation.
## Dynamic Type
### Using System Fonts
```swift
private func setupLabels() {
let titleLabel = UILabel()
titleLabel.font = .preferredFont(forTextStyle: .headline)
titleLabel.adjustsFontForContentSizeCategory = true
let bodyLabel = UILabel()
bodyLabel.font = .preferredFont(forTextStyle: .body)
bodyLabel.adjustsFontForContentSizeCategory = true
bodyLabel.numberOfLines = 0
}
```
### Custom Font Scaling
```swift
extension UIFont {
static func scaled(_ name: String, size: CGFloat, for style: TextStyle) -> UIFont {
guard let font = UIFont(name: name, size: size) else {
return .preferredFont(forTextStyle: style)
}
return UIFontMetrics(forTextStyle: style).scaledFont(for: font)
}
}
let customFont = UIFont.scaled("Avenir-Medium", size: 16, for: .body)
```
### Text Style Reference
| Style | Default Size | Usage |
|-------|--------------|-------|
| `.largeTitle` | 34pt | Screen titles |
| `.title1` | 28pt | Primary headings |
| `.title2` | 22pt | Secondary headings |
| `.title3` | 20pt | Tertiary headings |
| `.headline` | 17pt (semibold) | Important information |
| `.body` | 17pt | Body text |
| `.callout` | 16pt | Explanatory text |
| `.subheadline` | 15pt | Subtitles |
| `.footnote` | 13pt | Footnotes |
| `.caption1` | 12pt | Labels |
| `.caption2` | 11pt | Small labels |
### Adapting Layout for Large Text
```swift
override func traitCollectionDidChange(_ previous: UITraitCollection?) {
super.traitCollectionDidChange(previous)
let isLargeText = traitCollection.preferredContentSizeCategory.isAccessibilityCategory
contentStack.axis = isLargeText ? .vertical : .horizontal
if isLargeText {
iconImageView.snp.remakeConstraints { make in
make.size.equalTo(64)
}
} else {
iconImageView.snp.remakeConstraints { make in
make.size.equalTo(44)
}
}
}
```
## Semantic Colors
Use system semantic colors for automatic Dark Mode adaptation:
```swift
view.backgroundColor = .systemBackground
containerView.backgroundColor = .secondarySystemBackground
cardView.backgroundColor = .tertiarySystemBackground
titleLabel.textColor = .label
subtitleLabel.textColor = .secondaryLabel
hintLabel.textColor = .tertiaryLabel
placeholderLabel.textColor = .placeholderText
separatorView.backgroundColor = .separator
borderView.layer.borderColor = UIColor.separator.cgColor
```
### System Color Reference
| Color | Light Mode | Dark Mode | Usage |
|-------|------------|-----------|-------|
| `.systemBackground` | White | Black | Main background |
| `.secondarySystemBackground` | Light gray | Dark gray | Card/grouped background |
| `.tertiarySystemBackground` | Lighter gray | Medium gray | Nested content background |
| `.label` | Black | White | Primary text |
| `.secondaryLabel` | Gray | Light gray | Secondary text |
| `.tertiaryLabel` | Light gray | Dark gray | Auxiliary text |
### Custom Color Adaptation
```swift
extension UIColor {
static let customAccent = UIColor { traitCollection in
switch traitCollection.userInterfaceStyle {
case .dark:
return UIColor(red: 0.4, green: 0.8, blue: 1.0, alpha: 1.0)
default:
return UIColor(red: 0.0, green: 0.5, blue: 0.8, alpha: 1.0)
}
}
}
```
## VoiceOver
### Basic Labels
```swift
let cartButton = UIButton(type: .system)
cartButton.setImage(UIImage(systemName: "cart.badge.plus"), for: .normal)
cartButton.accessibilityLabel = "Add to cart"
let ratingView = UIView()
ratingView.accessibilityLabel = "Rating: 4 out of 5 stars"
let closeButton = UIButton()
closeButton.accessibilityLabel = "Close"
closeButton.accessibilityHint = "Dismisses this dialog"
```
### Custom Accessibility
```swift
class ProductCell: UICollectionViewCell {
override var accessibilityLabel: String? {
get {
return "\(product.name), \(product.price), \(product.isAvailable ? "In stock" : "Out of stock")"
}
set {}
}
override var accessibilityTraits: UIAccessibilityTraits {
get {
var traits: UIAccessibilityTraits = .button
if product.isSelected {
traits.insert(.selected)
}
return traits
}
set {}
}
}
```
### Accessibility Container
```swift
class CustomContainerView: UIView {
override var isAccessibilityElement: Bool {
get { false }
set {}
}
override var accessibilityElements: [Any]? {
get {
return [titleLabel, actionButton, detailLabel]
}
set {}
}
}
```
### VoiceOver Notifications
```swift
func didLoadContent() {
UIAccessibility.post(notification: .screenChanged, argument: headerLabel)
}
func didUpdateStatus() {
UIAccessibility.post(notification: .announcement, argument: "Download complete")
}
```
## Reduce Motion
```swift
func animateTransition() {
let duration: TimeInterval = UIAccessibility.isReduceMotionEnabled ? 0 : 0.3
UIView.animate(withDuration: duration) {
self.cardView.alpha = 1
}
}
func showPopup() {
if UIAccessibility.isReduceMotionEnabled {
popupView.alpha = 1
} else {
popupView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
popupView.alpha = 0
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0) {
self.popupView.transform = .identity
self.popupView.alpha = 1
}
}
}
```
### Observing Setting Changes
```swift
NotificationCenter.default.addObserver(
self,
selector: #selector(reduceMotionChanged),
name: UIAccessibility.reduceMotionStatusDidChangeNotification,
object: nil
)
@objc func reduceMotionChanged() {
updateAnimationSettings()
}
```
## Accessibility Checklist
### Basic Requirements
- [ ] All icon buttons have `accessibilityLabel`
- [ ] Custom controls have correct `accessibilityTraits`
- [ ] Images have `accessibilityLabel` or marked as decorative
- [ ] Forms have clear error messages
### Dynamic Type
- [ ] Using `preferredFont(forTextStyle:)`
- [ ] Set `adjustsFontForContentSizeCategory = true`
- [ ] Layout adapts at accessibility sizes
- [ ] Text is not truncated
### Color Contrast
- [ ] Body text contrast >= 4.5:1
- [ ] Large text contrast >= 3:1
- [ ] Information not conveyed by color alone
### Motion
- [ ] Respect Reduce Motion setting
- [ ] No flashing or rapid animation
- [ ] Auto-playing animations can be paused
### Interaction
- [ ] Touch targets >= 44x44pt
- [ ] Gestures have alternative actions
- [ ] Timeouts can be extended
---
*UIKit, VoiceOver, Dynamic Type, and Apple are trademarks of Apple Inc.*

View File

@@ -0,0 +1,231 @@
# Adaptive Screens Guidelines
Requirements for large screens, tablets, foldables, and multi-window support.
## Adaptive Quality Tiers
Google defines three progressive quality tiers for adaptive apps:
### Tier 3: Adaptive Ready (Basic)
Minimum requirements for all apps:
| Requirement | Description |
|-------------|-------------|
| Full screen | App fills display, no letterboxing |
| Configuration changes | Handles rotation, folding, resizing |
| Multi-window | Supports split-screen mode |
| Basic input | Keyboard, mouse, trackpad support |
### Tier 2: Adaptive Optimized (Better)
Enhanced experience:
| Requirement | Description |
|-------------|-------------|
| Layout optimization | Responsive layouts for all sizes |
| Enhanced input | Full keyboard shortcuts, mouse hover states |
| Continuity | Seamless state preservation |
### Tier 1: Adaptive Differentiated (Best)
Device-specific excellence:
| Requirement | Description |
|-------------|-------------|
| Multitasking | Drag and drop, activity embedding |
| Foldable postures | Table-top mode, book mode support |
| Stylus | Full stylus input support |
| Desktop | Windowed mode optimization |
## Screen Size Classes
### Width-Based Classes
| Class | Width | Typical Devices |
|-------|-------|-----------------|
| Compact | < 600dp | Phone portrait |
| Medium | 600-840dp | Tablet portrait, phone landscape |
| Expanded | > 840dp | Tablet landscape, desktop |
### Layout Strategies
| Screen Class | Navigation | Content Layout |
|--------------|------------|----------------|
| Compact | Bottom nav | Single pane |
| Medium | Nav rail | List-detail (optional) |
| Expanded | Nav drawer/rail | List-detail, multi-pane |
## Configuration Changes
### Must Handle
| Change | Trigger |
|--------|---------|
| Rotation | Device rotated |
| Fold/Unfold | Foldable state change |
| Window resize | Multi-window adjustment |
| Split screen | Enter/exit split mode |
| Keyboard | External keyboard attach/detach |
### Configuration Handling
| Approach | Description |
|----------|-------------|
| Let system handle | Default, activity recreated |
| Handle manually | Declare configChanges, implement onConfigurationChanged |
### State Preservation
- Use ViewModel for UI state
- Use SavedStateHandle for process death
- Test with "Don't keep activities" enabled
## Multi-Window Support
### Requirements
| Feature | Status |
|---------|--------|
| resizeableActivity | true (default API 24+) |
| Minimum size | Support 220dp width |
| State handling | Preserve across resize |
### Best Practices
- Don't assume full-screen ownership
- Handle onConfigurationChanged gracefully
- Test at minimum supported size
- Support free-form windows (desktop mode)
## Foldable Devices
### Postures
| Posture | Description | Use Case |
|---------|-------------|----------|
| Flat | Fully open | Normal tablet use |
| Half-opened (tabletop) | Hinged at ~90° horizontal | Video calls, media |
| Half-opened (book) | Hinged at ~90° vertical | Reading, productivity |
| Folded | Closed | Compact phone mode |
### Design Considerations
- Avoid placing interactive elements on the fold
- Consider separate content for each screen segment
- Support continuity when fold state changes
- Use WindowInfoTracker to detect fold state
## External Input Devices
### Keyboard Support
| Requirement | Implementation |
|-------------|----------------|
| Tab navigation | Focusable elements in order |
| Enter/Space | Activates focused element |
| Arrow keys | Navigate lists, grids |
| Shortcuts | Common actions (Ctrl+S, etc.) |
| Focus indicators | Visible focus states |
### Mouse/Trackpad Support
| Requirement | Implementation |
|-------------|----------------|
| Hover states | Visual feedback on hover |
| Right-click | Context menu support |
| Scroll | Smooth scrolling |
| Pointer cursor | Appropriate cursor types |
### Stylus Support
| Feature | Implementation |
|---------|----------------|
| Pressure sensitivity | Variable stroke width |
| Palm rejection | Ignore palm touches |
| Tilt detection | Shading effects |
| Hover preview | Show cursor before touch |
## Navigation Patterns
### By Screen Width
| Width | Primary Nav | Secondary Nav |
|-------|-------------|---------------|
| < 600dp | Bottom nav (3-5 items) | Hamburger menu |
| 600-840dp | Navigation rail | Drawer on demand |
| > 840dp | Permanent drawer or rail | Drawer or none |
### Navigation Rail Specs
| Property | Value |
|----------|-------|
| Width | 80dp |
| Icon size | 24dp |
| Touch target | 56dp |
| Items | 3-7 destinations |
| FAB | Optional, at top |
### Permanent Navigation Drawer
| Property | Value |
|----------|-------|
| Width | 256-360dp |
| Position | Left edge (LTR) |
| Behavior | Always visible |
| Content | Full labels, icons |
## Responsive Layouts
### Breakpoints
| Class | Width Range |
|-------|-------------|
| COMPACT | < 600dp |
| MEDIUM | 600-840dp |
| EXPANDED | > 840dp |
Use WindowSizeClass to determine current breakpoint and adapt layout accordingly.
## Content Considerations
### Text Readability
- Line length: 45-75 characters max
- Use multiple columns on wide screens
- Maintain hierarchy with consistent spacing
### Media
- Support multiple aspect ratios
- Provide high-resolution assets
- Consider picture-in-picture for video
### Touch vs. Precise Input
- Large screens often use mouse/keyboard
- Don't assume touch-only interaction
- Provide hover states and tooltips
## Testing
### Device Matrix
| Device Type | Test Priority |
|-------------|---------------|
| Phone (portrait) | Required |
| Phone (landscape) | Required |
| Tablet (both orientations) | Required |
| Foldable (all postures) | High |
| Desktop/Chromebook | Medium |
### Test Cases
- [ ] App fills screen in all configurations
- [ ] No letterboxing or black bars
- [ ] State preserved across configuration changes
- [ ] Multi-window works at minimum size
- [ ] Keyboard navigation functional
- [ ] Mouse hover states present
- [ ] Foldable postures handled (if applicable)
- [ ] Navigation adapts to screen width

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,444 @@
---
name: fullstack-dev-api-design
description: "API design patterns and best practices. Use when creating endpoints, choosing methods/status codes, implementing pagination, or writing OpenAPI specs. Prevents common REST/GraphQL/gRPC mistakes."
license: MIT
metadata:
version: "2.0.0"
sources:
- Microsoft REST API Guidelines
- Google API Design Guide
- Zalando RESTful API Guidelines
- JSON:API Specification
- RFC 9457 (Problem Details for HTTP APIs)
- RFC 9110 (HTTP Semantics)
---
# API Design Guidelines
Framework-agnostic API design guide for backend and full-stack engineers. 50+ rules across 10 categories, prioritized by impact. Covers REST, GraphQL, and gRPC.
## Scope
**USE this skill when:**
- Designing a new API or adding endpoints
- Reviewing API pull requests
- Choosing between REST / GraphQL / gRPC
- Writing OpenAPI specifications
- Migrating or versioning an existing API
**NOT for:**
- Framework-specific implementation details (use your framework's own skill/docs)
- Frontend data fetching patterns (use React Query / SWR docs)
- Authentication implementation details (use your auth library's docs)
- Database schema design (→ `database-schema-design`)
## Context Required
Before applying this skill, gather:
| Required | Optional |
|----------|----------|
| Target consumers (browser, mobile, service) | Existing API conventions in the project |
| Expected request volume (RPS estimate) | Current OpenAPI / Swagger spec |
| Authentication method (JWT, API key, OAuth) | Rate limiting requirements |
| Data model / domain entities | Caching strategy |
---
## Quick Start Checklist
New API endpoint? Run through this before writing code:
- [ ] Resource named as **plural noun** (`/orders`, not `/getOrders`)
- [ ] URL in **kebab-case**, body fields in **camelCase**
- [ ] Correct **HTTP method** (GET=read, POST=create, PUT=replace, PATCH=partial, DELETE=remove)
- [ ] Correct **status code** (201 Created, 422 Validation, 404 Not Found…)
- [ ] Error response follows **RFC 9457** envelope
- [ ] **Pagination** on all list endpoints (default 20, max 100)
- [ ] **Authentication** required (Bearer token, not query param)
- [ ] **Request ID** in response header (`X-Request-Id`)
- [ ] **Rate limit** headers included
- [ ] Endpoint documented in **OpenAPI spec**
---
## Quick Navigation
| Need to… | Jump to |
|----------|---------|
| Name a resource URL | [1. Resource Modeling](#1-resource-modeling-critical) |
| Pick HTTP method + status code | [3. HTTP Methods & Status Codes](#3-http-methods--status-codes-critical) |
| Format error responses | [4. Error Handling](#4-error-handling-high) |
| Add pagination or filtering | [6. Pagination & Filtering](#6-pagination--filtering-high) |
| Choose API style (REST vs GraphQL vs gRPC) | [10. API Style Decision](#10-api-style-decision-tree) |
| Version an existing API | [7. Versioning](#7-versioning-medium-high) |
| Avoid common mistakes | [Anti-Patterns](#anti-patterns-checklist) |
---
## 1. Resource Modeling (CRITICAL)
### Core Rules
```
✅ /users — plural noun
✅ /users/{id}/orders — 1 level nesting
✅ /reviews?orderId={oid} — flatten deep nesting with query params
❌ /getUsers — verb in URL
❌ /user — singular
❌ /users/{uid}/orders/{oid}/items/{iid}/reviews — 3+ levels deep
```
**Max nesting: 2 levels.** Beyond that, promote to top-level resource with filters.
### Domain Alignment
Resources map to **domain concepts**, not database tables:
```
✅ /checkout-sessions (domain aggregate)
✅ /shipping-labels (domain concept)
❌ /tbl_order_header (database table leak)
❌ /join_user_role (internal schema leak)
```
---
## 2. URL & Naming (CRITICAL)
| Context | Convention | Example |
|---------|-----------|---------|
| URL path | kebab-case | `/order-items` |
| JSON body fields | camelCase | `{ "firstName": "Jane" }` |
| Query params | camelCase or snake_case (be consistent) | `?sortBy=createdAt` |
| Headers | Train-Case | `X-Request-Id` |
**Python exception:** If your entire stack is Python/snake_case, you MAY use `snake_case` in JSON — but be **consistent across all endpoints**.
```
✅ GET /users ❌ GET /users/
✅ GET /reports/annual ❌ GET /reports/annual.json
✅ POST /users ❌ POST /users/create
```
---
## 3. HTTP Methods & Status Codes (CRITICAL)
### Method Semantics
| Method | Semantics | Idempotent | Safe | Request Body |
|--------|-----------|-----------|------|-------------|
| GET | Read | ✅ | ✅ | ❌ Never |
| POST | Create / Action | ❌ | ❌ | ✅ Always |
| PUT | Full replace | ✅ | ❌ | ✅ Always |
| PATCH | Partial update | ❌* | ❌ | ✅ Always |
| DELETE | Remove | ✅ | ❌ | ❌ Rarely |
### Status Code Quick Reference
**Success:**
| Code | When | Response Body |
|------|------|--------------|
| 200 OK | GET, PUT, PATCH success | Resource / result |
| 201 Created | POST created resource | Created resource + `Location` header |
| 202 Accepted | Async operation started | Job ID / status URL |
| 204 No Content | DELETE success, PUT with no body | None |
**Client Errors:**
| Code | When | Key Distinction |
|------|------|-----------------|
| 400 Bad Request | Malformed syntax | Can't even parse |
| 401 Unauthorized | Missing / invalid auth | "Who are you?" |
| 403 Forbidden | Authenticated, no permission | "I know you, but no" |
| 404 Not Found | Resource doesn't exist | Also use to hide 403 |
| 409 Conflict | Duplicate, version mismatch | State conflict |
| 422 Unprocessable | Valid syntax, failed validation | Semantic errors |
| 429 Too Many Requests | Rate limit hit | Include `Retry-After` |
**Server Errors:** 500 (unexpected), 502 (upstream fail), 503 (overloaded), 504 (upstream timeout)
---
## 4. Error Handling (HIGH)
### Standard Error Envelope (RFC 9457)
Every error response uses this format:
```json
{
"type": "https://api.example.com/errors/insufficient-funds",
"title": "Insufficient Funds",
"status": 422,
"detail": "Account balance $10.00 is less than withdrawal $50.00.",
"instance": "/transactions/txn_abc123",
"request_id": "req_7f3a8b2c",
"errors": [
{ "field": "amount", "message": "Exceeds balance", "code": "INSUFFICIENT_BALANCE" }
]
}
```
### Multi-Language Implementation
**TypeScript (Express):**
```typescript
class AppError extends Error {
constructor(
public readonly title: string,
public readonly status: number,
public readonly detail: string,
public readonly code: string,
) { super(detail); }
}
// Middleware
app.use((err, req, res, next) => {
if (err instanceof AppError) {
return res.status(err.status).json({
type: `https://api.example.com/errors/${err.code}`,
title: err.title, status: err.status,
detail: err.detail, request_id: req.id,
});
}
res.status(500).json({ title: 'Internal Error', status: 500, request_id: req.id });
});
```
**Python (FastAPI):**
```python
from fastapi import Request
from fastapi.responses import JSONResponse
class AppError(Exception):
def __init__(self, title: str, status: int, detail: str, code: str):
self.title, self.status, self.detail, self.code = title, status, detail, code
@app.exception_handler(AppError)
async def app_error_handler(request: Request, exc: AppError):
return JSONResponse(status_code=exc.status, content={
"type": f"https://api.example.com/errors/{exc.code}",
"title": exc.title, "status": exc.status,
"detail": exc.detail, "request_id": request.state.request_id,
})
```
### Iron Rules
```
✅ Return RFC 9457 error envelope for ALL errors
✅ Include request_id in every error response
✅ Return per-field validation errors in `errors` array
❌ Never expose stack traces in production
❌ Never return 200 for errors
❌ Never swallow errors silently
```
---
## 5. Authentication & Authorization (HIGH)
```
✅ Authorization: Bearer eyJhbGci... (header)
❌ GET /users?token=eyJhbGci... (URL — appears in logs)
✅ 401 → "Who are you?" (missing/invalid credentials)
✅ 403 → "You can't do this" (authenticated, no permission)
✅ 404 → Hide resource existence (use instead of 403 when needed)
```
**Rate Limit Headers (always include):**
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1625097600
Retry-After: 30
```
---
## 6. Pagination & Filtering (HIGH)
### Cursor vs Offset
| Strategy | When | Pros | Cons |
|----------|------|------|------|
| **Cursor** (preferred) | Large/dynamic datasets | Consistent, no skips | Can't jump to page N |
| **Offset** | Small/stable datasets, admin UIs | Simple, page jumps | Drift on insert/delete |
**Cursor pagination response:**
```json
{
"data": [...],
"pagination": { "next_cursor": "eyJpZCI6MTIwfQ", "has_more": true }
}
```
**Offset pagination response:**
```json
{
"data": [...],
"pagination": { "page": 3, "per_page": 20, "total": 256, "total_pages": 13 }
}
```
**Always enforce:** Default 20 items, max 100 items.
### Standard Filter Patterns
```
GET /orders?status=shipped&created_after=2025-01-01&sort=-created_at&fields=id,status
```
| Pattern | Convention |
|---------|-----------|
| Exact match | `?status=shipped` |
| Range | `?price_gte=10&price_lte=100` |
| Date range | `?created_after=2025-01-01&created_before=2025-12-31` |
| Sort | `?sort=field` (asc), `?sort=-field` (desc) |
| Sparse fields | `?fields=id,name,email` |
| Search | `?q=search+term` |
---
## 7. Versioning (MEDIUM-HIGH)
| Strategy | Format | Best For |
|----------|--------|----------|
| **URL path** (recommended) | `/v1/users` | Public APIs |
| **Header** | `Api-Version: 2` | Internal APIs |
| **Query param** | `?version=2` | Legacy (avoid) |
**Non-breaking changes (no version bump):** New optional response fields, new endpoints, new optional params.
**Breaking changes (new version required):** Removing/renaming fields, changing types, stricter validation, removing endpoints.
**Deprecation headers:**
```
Sunset: Sat, 01 Mar 2026 00:00:00 GMT
Deprecation: true
Link: <https://api.example.com/v2/users>; rel="successor-version"
```
---
## 8. Request / Response Design (MEDIUM)
### Consistent Envelope
```json
{
"data": { "id": "ord_123", "status": "pending", "total": 99.50 },
"meta": { "request_id": "req_abc123", "timestamp": "2025-06-15T10:30:00Z" }
}
```
### Key Rules
| Rule | Correct | Wrong |
|------|---------|-------|
| Timestamps | `"2025-06-15T10:30:00Z"` (ISO 8601) | `"06/15/2025"` or `1718447400` |
| Public IDs | UUID `"550e8400-..."` | Auto-increment `42` |
| Null vs absent (PATCH) | `{ "nickname": null }` = clear field | Absent field = don't change |
| HATEOAS (public APIs) | `"links": { "cancel": "/orders/123/cancel" }` | No discoverability |
---
## 9. Documentation — OpenAPI (MEDIUM)
**Design-first workflow:**
```
1. Write OpenAPI 3.1 spec
2. Review spec with stakeholders
3. Generate server stubs + client SDKs
4. Implement handlers
5. Validate responses against spec in CI
```
Every endpoint documents: summary, all parameters, request body + examples, all response codes + schemas, auth requirements.
---
## 10. API Style Decision Tree
```
What kind of API?
├─ Browser + mobile clients, flexible queries
│ └─ GraphQL
│ Rules: DataLoader (no N+1), depth limit ≤7, Relay pagination
├─ Standard CRUD, public consumers, caching important
│ └─ REST (this guide)
│ Rules: Resources, HTTP methods, status codes, OpenAPI
├─ Service-to-service, high throughput, strong typing
│ └─ gRPC
│ Rules: Protobuf schemas, streaming for large data, deadlines
├─ Full-stack TypeScript, same team owns client + server
│ └─ tRPC
│ Rules: Shared types, no code generation needed
└─ Real-time bidirectional
└─ WebSocket / SSE
Rules: Heartbeat, reconnection, message ordering
```
---
## Anti-Patterns Checklist
| # | ❌ Don't | ✅ Do Instead |
|---|---------|--------------|
| 1 | Verbs in URLs (`/getUser`) | HTTP methods + noun resources |
| 2 | Return 200 for errors | Correct 4xx/5xx status codes |
| 3 | Mix naming styles | One convention per context |
| 4 | Expose database IDs | UUIDs for public identifiers |
| 5 | No pagination on lists | Always paginate (default 20) |
| 6 | Swallow errors silently | Structured RFC 9457 errors |
| 7 | Token in URL query | Authorization header |
| 8 | Deep nesting (3+ levels) | Flatten with query params |
| 9 | Break changes without version | Maintain compatibility or version |
| 10 | No rate limiting | Implement + communicate via headers |
| 11 | No request ID | `X-Request-Id` on every response |
| 12 | Stack traces in production | Safe error message + internal log |
---
## Common Issues
### Issue 1: "Should this be a new resource or a sub-resource?"
**Symptom:** URL path keeps growing (`/users/{id}/orders/{id}/items/{id}/reviews`)
**Rule:** If the child entity makes sense on its own, promote it. If it only exists within the parent context, keep it nested (max 2 levels).
```
/reviews?orderId=123 ✅ (reviews exist independently)
/orders/{id}/items ✅ (items belong to orders, 1 level)
```
### Issue 2: "PUT or PATCH?"
**Symptom:** Team can't agree on update semantics.
**Rule:**
- PUT = client sends **complete** resource (missing fields → set to default/null)
- PATCH = client sends **only changed fields** (missing fields → unchanged)
- When unsure → **PATCH** (safer, less surprising)
### Issue 3: "400 or 422?"
**Symptom:** Inconsistent validation error codes.
**Rule:**
- 400 = can't parse request at all (malformed JSON, wrong content-type)
- 422 = parsed OK, but values fail validation (invalid email, negative quantity)

View File

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

View File

@@ -0,0 +1,169 @@
# Asset Approval Checklist
Comprehensive checklist for reviewing marketing assets before approval.
## Quick Review
Before detailed review, verify:
- [ ] Asset serves stated purpose
- [ ] Target audience appropriate
- [ ] No obvious errors or issues
- [ ] Aligns with campaign goals
## Visual Elements
### Logo Usage
- [ ] Correct logo variant for context
- [ ] Proper clear space maintained
- [ ] Minimum size requirements met
- [ ] Approved colors only
- [ ] No unauthorized modifications
- [ ] Appropriate for background
### Color Compliance
- [ ] Uses brand palette colors only
- [ ] Primary/secondary ratio appropriate (60/30/10)
- [ ] Semantic colors used correctly
- [ ] No off-brand colors introduced
- [ ] Consistent across all elements
### Typography
- [ ] Brand fonts used throughout
- [ ] Correct font weights applied
- [ ] Proper type hierarchy
- [ ] Appropriate sizes for medium
- [ ] Line heights adequate
- [ ] No orphans/widows in body text
### Imagery
- [ ] Matches brand photography style
- [ ] Appropriate subjects/content
- [ ] Quality meets requirements
- [ ] Properly licensed/credited
- [ ] Optimized for intended use
## Accessibility
### Visual Accessibility
- [ ] Text contrast ratio >= 4.5:1 (AA)
- [ ] Large text contrast >= 3:1
- [ ] Interactive elements have visible focus
- [ ] Color not sole indicator of meaning
- [ ] Alt text for all images
### Content Accessibility
- [ ] Clear and scannable layout
- [ ] Readable font sizes
- [ ] Logical reading order
- [ ] Meaningful headings structure
- [ ] Links describe destination
## Content Quality
### Copy Review
- [ ] Matches brand voice
- [ ] Appropriate tone for context
- [ ] No prohibited terms used
- [ ] Value proposition clear
- [ ] CTA compelling and clear
- [ ] Proofread for errors
### Messaging
- [ ] Aligns with key messages
- [ ] Differentiators highlighted
- [ ] Benefits over features
- [ ] Target audience addressed
- [ ] No conflicting claims
## Technical Requirements
### File Specifications
- [ ] Correct file format
- [ ] Appropriate resolution
- [ ] File size optimized
- [ ] Proper naming convention
- [ ] Metadata included
### Platform Requirements
| Platform | Verified |
|----------|----------|
| Instagram | [ ] Correct dimensions |
| Twitter/X | [ ] Meets requirements |
| LinkedIn | [ ] Professional standards |
| Facebook | [ ] Guidelines compliant |
| Email | [ ] Size under 1MB |
| Web | [ ] Optimized for web |
## Legal & Compliance
### Intellectual Property
- [ ] Stock images licensed
- [ ] Music/audio cleared
- [ ] No trademark violations
- [ ] User content authorized
- [ ] Credits included where needed
### Regulatory
- [ ] Required disclosures present
- [ ] No misleading claims
- [ ] Pricing accurate
- [ ] Terms linked where needed
- [ ] Privacy compliant
## Review Status
### Reviewer Sign-off
| Review Area | Reviewer | Date | Status |
|-------------|----------|------|--------|
| Visual Design | | | [ ] Pass / [ ] Revisions |
| Copy/Content | | | [ ] Pass / [ ] Revisions |
| Brand Compliance | | | [ ] Pass / [ ] Revisions |
| Technical | | | [ ] Pass / [ ] Revisions |
| Legal | | | [ ] Pass / [ ] Revisions |
### Final Approval
- [ ] All review areas passed
- [ ] Revisions completed (if any)
- [ ] Final version uploaded
- [ ] Metadata updated
- [ ] Ready for publish/use
**Approved By:** _______________
**Date:** _______________
**Version:** _______________
## Common Issues & Fixes
| Issue | Fix |
|-------|-----|
| Logo too small | Increase to minimum size |
| Wrong font | Replace with brand font |
| Low contrast | Adjust colors for accessibility |
| Off-brand color | Replace with palette color |
| Blurry image | Use higher resolution source |
| Missing alt text | Add descriptive alt text |
| Weak CTA | Strengthen action-oriented copy |
## Automation Support
The `validate-asset.cjs` script can auto-check:
- Color palette compliance
- Minimum dimensions
- File format/size
- Naming convention
- Basic metadata
Run: `node .claude/skills/brand/scripts/validate-asset.cjs <asset-path>`
## Archival
After approval:
1. Update asset status in manifest.json
2. Add approver and timestamp
3. Move previous versions to archive
4. Update campaign tracking
5. Notify relevant teams

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
# Asset Organization Guide
Guidelines for organizing marketing assets in a structured, searchable system.
## Directory Structure
```
project-root/
├── .assets/ # Git-tracked metadata
│ ├── manifest.json # Central asset registry
│ ├── tags.json # Tagging system
│ ├── versions/ # Version history
│ │ └── {asset-id}/
│ │ └── v{n}.json
│ └── metadata/ # Type-specific metadata
│ ├── designs.json
│ ├── banners.json
│ ├── logos.json
│ └── videos.json
├── assets/ # Raw files
│ ├── designs/
│ │ ├── campaigns/ # Campaign-specific designs
│ │ ├── web/ # Website graphics
│ │ └── print/ # Print materials
│ ├── banners/
│ │ ├── social-media/ # Platform banners
│ │ ├── email-headers/ # Email template headers
│ │ └── landing-pages/ # Hero/section images
│ ├── logos/
│ │ ├── full-horizontal/ # Full logo with wordmark
│ │ ├── icon-only/ # Symbol only
│ │ ├── monochrome/ # Single color versions
│ │ └── variations/ # Special versions
│ ├── videos/
│ │ ├── ads/ # Promotional videos
│ │ ├── tutorials/ # How-to content
│ │ └── testimonials/ # Customer videos
│ ├── infographics/ # Data visualizations
│ └── generated/ # AI-generated assets
│ └── {YYYYMMDD}/ # Date-organized
```
## Naming Convention
### Format
```
{type}_{campaign}_{description}_{timestamp}_{variant}.{ext}
```
### Components
| Component | Format | Required | Examples |
|-----------|--------|----------|----------|
| type | lowercase | Yes | banner, logo, design, video |
| campaign | kebab-case | Yes* | claude-launch, q1-promo, evergreen |
| description | kebab-case | Yes | hero-image, email-header |
| timestamp | YYYYMMDD | Yes | 20251209 |
| variant | kebab-case | No | dark-mode, 1x1, mobile |
*Use "evergreen" for non-campaign assets
### Examples
```
banner_claude-launch_hero-image_20251209_16-9.png
logo_brand-refresh_horizontal-full-color_20251209.svg
design_holiday-campaign_email-hero_20251209_dark-mode.psd
video_product-demo_feature-walkthrough_20251209.mp4
infographic_evergreen_pricing-comparison_20251209.png
```
## Metadata Schema
### Asset Entry (manifest.json)
```json
{
"id": "uuid-v4",
"name": "Campaign Hero Banner",
"type": "banner",
"path": "assets/banners/landing-pages/banner_claude-launch_hero-image_20251209.png",
"dimensions": { "width": 1920, "height": 1080 },
"fileSize": 245760,
"mimeType": "image/png",
"tags": ["campaign", "hero", "launch"],
"status": "approved",
"source": {
"model": "imagen-4",
"prompt": "...",
"createdAt": "2025-12-09T10:30:00Z"
},
"version": 2,
"createdBy": "agent:content-creator",
"approvedBy": "user:john",
"approvedAt": "2025-12-09T14:00:00Z"
}
```
### Version Entry (versions/{id}/v{n}.json)
```json
{
"version": 2,
"previousVersion": 1,
"path": "assets/banners/landing-pages/banner_claude-launch_hero-image_20251209_v2.png",
"changes": "Updated CTA button color to match brand refresh",
"createdAt": "2025-12-09T12:00:00Z",
"createdBy": "agent:ui-designer"
}
```
## Tagging System
### Standard Tags
| Category | Values |
|----------|--------|
| status | draft, review, approved, archived |
| platform | instagram, twitter, linkedin, facebook, youtube, email, web |
| content-type | promotional, educational, brand, product, testimonial |
| format | 1x1, 4x5, 9x16, 16x9, story, reel, banner |
| source | imagen-4, veo-3, user-upload, canva, figma |
### Tag Usage
- Each asset should have: status + platform + content-type
- Optional: format, source, campaign
## File Organization Best Practices
1. **One file per variant** - Don't combine dark/light in one file
2. **Source files separate** - Keep .psd/.fig in same structure
3. **AI assets timestamped** - Auto-organize by generation date
4. **Archive don't delete** - Move to `archived/` with date prefix
5. **Large files external** - Videos > 100MB use cloud storage links
## Search Patterns
### By Type
```bash
# Find all banners
ls assets/banners/**/*
```
### By Campaign
```bash
# Find all assets for specific campaign
grep -l "claude-launch" .assets/manifest.json
```
### By Status
```bash
# Find approved assets only
jq '.assets[] | select(.status == "approved")' .assets/manifest.json
```
## Cleanup Workflow
1. Run `extract-colors.cjs` on new assets
2. Validate against brand guidelines
3. Update manifest.json with new entries
4. Tag appropriately
5. Remove duplicates/outdated versions

View File

@@ -0,0 +1,43 @@
# Prompt Engineering Guide
## Image Prompts
- Be specific about composition: "left-aligned subject with negative space on the right for text overlay"
- Specify lighting: "soft studio lighting", "golden hour backlight", "flat diffused light"
- Include style modifiers: "editorial photography", "3D render", "flat vector illustration"
- Add technical specs: "4K resolution, sharp focus, shallow depth of field"
- For web assets: always mention "clean background", "web-optimized", "high contrast for readability"
- **NEVER** include text in image prompts unless explicitly requested — AI text rendering is unreliable
## Video Prompts
- Use MiniMax camera commands in brackets: `[Push in]`, `[Truck left]`, `[Tracking shot]`, etc.
- Describe scene, subject, lighting, and mood — the API auto-optimizes prompts by default
- For web backgrounds: keep 6s duration, add `[Static shot]` for stability
- Max 2,000 characters
## Audio / TTS
- Specify genre, tempo (BPM), mood, and instruments
- For background music: "no vocals, suitable for background, not distracting"
- For sound effects: be extremely specific about the sound event
- For TTS: choose voice matching content language and speaker gender
## Preset Shortcuts
| Shortcut | Spec |
|----------|------|
| `hero` | 16:9 (1280x720) image, cinematic, text-safe space |
| `thumb` | 1:1 (1024x1024) image, centered subject |
| `icon` | 1:1 (1024x1024), flat style, clean background |
| `avatar` | 1:1 (1024x1024), portrait, circular crop ready |
| `banner` | 21:9 (1344x576), OG/social banner |
| `portrait` | 2:3 (832x1248), vertical portrait |
| `mobile` | 9:16 (720x1280), mobile fullscreen |
| `bg-video` | 768P, 6s, `[Static shot]`, MiniMax Hailuo-2.3 |
| `video` | 768P, 6s, MiniMax Hailuo-2.3, prompt auto-optimized |
| `video-hd` | 1080P, 6s, MiniMax Hailuo-2.3 |
| `bgm` | 30s background music, no vocals, loopable |
| `sfx` | Short sound effect, < 3s |
| `tts` | Text-to-speech, MiniMax HD, MP3 |
| `narration` | Expressive narration voice, MiniMax |

View File

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

View File

@@ -0,0 +1,165 @@
# Authentication Flow Patterns
Complete auth flow across frontend and backend. Covers JWT bearer flow, automatic token refresh, Next.js server-side auth, RBAC, and backend middleware order.
---
## JWT Bearer Flow (Most Common)
```
1. Login
Client → POST /api/auth/login { email, password }
Server → { accessToken (15min), refreshToken (7d, httpOnly cookie) }
2. Authenticated Requests
Client → GET /api/orders Authorization: Bearer <accessToken>
Server → validates JWT → returns data
3. Token Refresh (transparent)
Client → 401 received → POST /api/auth/refresh (cookie auto-sent)
Server → new accessToken
Client → retry original request with new token
4. Logout
Client → POST /api/auth/logout
Server → invalidate refresh token → clear cookie
```
---
## Frontend: Automatic Token Refresh
```typescript
// lib/api-client.ts — add to existing fetch wrapper
async function apiWithRefresh<T>(path: string, options: RequestInit = {}): Promise<T> {
try {
return await api<T>(path, options);
} catch (err) {
if (err instanceof ApiError && err.status === 401) {
// Try refresh
const refreshed = await api<{ accessToken: string }>('/api/auth/refresh', {
method: 'POST',
credentials: 'include', // send httpOnly cookie
});
setAuthToken(refreshed.accessToken);
// Retry original request
return api<T>(path, options);
}
throw err;
}
}
```
---
## Next.js: Server-Side Auth (App Router)
```typescript
// middleware.ts — protect routes server-side
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value;
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// app/dashboard/page.tsx — server component with auth
import { cookies } from 'next/headers';
export default async function Dashboard() {
const token = (await cookies()).get('session')?.value;
const user = await fetch(`${process.env.API_URL}/api/me`, {
headers: { Authorization: `Bearer ${token}` },
}).then(r => r.json());
return <DashboardContent user={user} />;
}
```
---
## Backend: Standard Middleware Order
```
Request → 1.RequestID → 2.Logging → 3.CORS → 4.RateLimit → 5.BodyParse
→ 6.Auth → 7.Authz → 8.Validation → 9.Handler → 10.ErrorHandler → Response
```
---
## Backend: JWT Rules
```
✅ Short expiry access token (15min) + refresh token (server-stored)
✅ Minimal claims: userId, roles (not entire user object)
✅ Rotate signing keys periodically
❌ Never store tokens in localStorage (XSS risk)
❌ Never pass tokens in URL query params
```
---
## Backend: RBAC Pattern
```typescript
function authorize(...roles: Role[]) {
return (req, res, next) => {
if (!req.user) throw new UnauthorizedError();
if (!roles.some(r => req.user.roles.includes(r))) throw new ForbiddenError();
next();
};
}
router.delete('/users/:id', authenticate, authorize('admin'), deleteUser);
```
---
## Auth Decision Table
| Method | When | Frontend |
|--------|------|----------|
| Session | Same-domain, SSR, Django templates | Django templates / htmx |
| JWT | Different domain, SPA, mobile | React, Vue, mobile apps |
| OAuth2 | Third-party login, API consumers | Any |
---
## Iron Rules
```
✅ Access token: short-lived (15min), in memory
✅ Refresh token: httpOnly cookie (XSS-safe)
✅ Automatic transparent refresh on 401
✅ Redirect to login when refresh fails
❌ Never store tokens in localStorage (XSS risk)
❌ Never send tokens in URL query params (logged)
❌ Never trust client-side auth checks alone (server must validate)
```
---
## Common Issues
### Issue 1: "Auth works on page load but breaks on navigation"
**Cause:** Token stored in component state (lost on unmount).
**Fix:** Store access token in a persistent location:
- React Context (survives navigation, lost on refresh)
- Cookie (survives refresh)
- React Query cache with `staleTime: Infinity` for session
### Issue 2: "CORS error with auth requests"
**Cause:** Missing `credentials: 'include'` on frontend or `credentials: true` on backend CORS config.
**Fix:**
1. Frontend: `fetch(url, { credentials: 'include' })`
2. Backend: `cors({ origin: 'https://your-frontend.com', credentials: true })`
3. Backend: explicit origin (not `*`) when using credentials

View File

@@ -0,0 +1,141 @@
# Authoring guide
How to turn a user request ("make me a deck about X") into a finished
html-ppt deck. Follow these steps in order.
## 1. Understand the deck
Before touching files, clarify:
1. **Audience** — engineers? designers? executives? consumers?
2. **Length** — 5 min lightning? 20 min share? 45 min talk?
3. **Language** — Chinese, English, bilingual? (Noto Sans SC is preloaded.)
4. **Format** — on-screen live, PDF export, 小红书图文?
5. **Tone** — clinical / playful / editorial / cyber?
The audience + tone map to a theme; the length maps to slide count; the
format maps to runtime features (live → notes + T-cycle; PDF → page-break
CSS, already handled in `base.css`).
## 2. Pick a theme
Use `references/themes.md`. When in doubt:
- **Engineers** → `catppuccin-mocha` / `tokyo-night` / `dracula`.
- **Designers / product** → `editorial-serif` / `aurora` / `soft-pastel`.
- **Execs** → `minimal-white` / `arctic-cool` / `swiss-grid`.
- **Consumers** → `xiaohongshu-white` / `sunset-warm` / `soft-pastel`.
- **Cyber / CLI / infra** → `terminal-green` / `blueprint` / `gruvbox-dark`.
- **Pitch / bold** → `neo-brutalism` / `sharp-mono` / `bauhaus`.
- **Launch / product reveal** → `glassmorphism` / `aurora`.
Wire the theme as `<link id="theme-link" href="../assets/themes/NAME.css">`
and list 3-5 alternatives in `data-themes` so the user can press T to audition.
## 3. Outline the deck
A solid 20-minute deck is usually:
```
cover → toc → section-divider #1 → [2-4 body pages] →
section-divider #2 → [2-4 body pages] → section-divider #3 →
[2-4 body pages] → cta → thanks
```
Pick 1 layout per page from `references/layouts.md`. Don't repeat the same
layout twice in a row.
## 4. Scaffold the deck
```bash
./scripts/new-deck.sh my-talk
```
This copies `templates/deck.html` into `examples/my-talk/index.html` with
paths rewritten. Add/remove `<section class="slide">` blocks to match your
outline.
## 5. Author each slide
For each outline item:
1. Open the matching single-page layout, e.g. `templates/single-page/kpi-grid.html`.
2. Copy the `<section class="slide">…</section>` block.
3. Paste into your deck.
4. Replace demo data with real data. Keep the class structure intact.
5. Set `data-title="..."` (used by the Overview grid).
6. Add `<div class="notes">…</div>` with speaker notes.
## 6. Add animations sparingly
Rules of thumb:
- Cover/title: `rise-in` or `blur-in`.
- Body content: `fade-up` for the hero element, `stagger-list` for grids/lists.
- Stat pages: `counter-up`.
- Section dividers: `perspective-zoom` or `cube-rotate-3d`.
- Closer: `confetti-burst` on the "Thanks" text.
Pick **one** accent animation per slide. Everything else should be calm.
## 7. Chinese + English decks
- Fonts are already imported in `fonts.css` (Noto Sans SC + Noto Serif SC).
- Use `lang="zh-CN"` on `<html>`.
- For bilingual titles, stack lines: `<h1 class="h1">主标题<br><span class="dim">English subtitle</span></h1>`.
- Keep English subtitles in a lighter weight (300) and dim color to avoid
visual competition.
## 8. Review in-browser
```bash
open examples/my-talk/index.html
```
Walk through every slide with ← →. Press:
- **O** — overview grid; catch any layout clipping.
- **T** — cycle themes; make sure nothing looks broken in any theme.
- **S** — open speaker notes; verify every slide has notes.
## 9. Export to PNG
```bash
# single slide
./scripts/render.sh examples/my-talk/index.html
# all slides (autodetect count by looking for .slide sections)
./scripts/render.sh examples/my-talk/index.html all
# explicit slide count + output dir
./scripts/render.sh examples/my-talk/index.html 12 out/my-talk-png
```
Output is 1920×1080 by default. Change in `render.sh` if the user wants 3:4
for 小红书图文 (1242×1660).
## 10. What to NOT do
- Don't hand-author from a blank file.
- Don't use raw hex colors in slide markup. Use tokens.
- Don't load heavy animation frameworks. Everything should stay within the
CSS/JS that already ships.
- Don't add more than one new template file unless a genuinely new layout
type is needed. Prefer composition.
- Don't delete slides from the showcase decks.
- **Don't put presenter-only text on the slide.** Any descriptive text,
narration cues, or explanations meant for the speaker (e.g. "这一页的重点是…",
"Note: mention X here", small grey captions explaining the slide's purpose)
MUST go inside `<div class="notes">`, not as visible elements. The `.notes`
div is hidden (`display:none`) and only shown via the S overlay. Slides
should contain ONLY audience-facing content.
## Troubleshooting
- **Theme doesn't switch with T**: check `data-themes` on `<body>` and
`data-theme-base` pointing to the themes directory relative to the HTML
file.
- **Fonts fall back**: make sure `fonts.css` is linked before the theme.
- **Chart.js colors wrong**: charts read CSS vars in JS; make sure they run
after the DOM is ready (`addEventListener('DOMContentLoaded', …)`).
- **PNG too small**: bump `--window-size` in `scripts/render.sh`.

View File

@@ -0,0 +1,237 @@
# Backtesting & Optimization
## Table of Contents
- [Strategy Tester Modes](#strategy-tester-modes)
- [Minimum Requirements for Valid Backtest](#minimum-requirements-for-valid-backtest)
- [OnTester() - Custom Optimization Criterion](#ontester---custom-optimization-criterion-mql5)
- [TesterStatistics() Constants](#testerstatistics-constants)
- [Key Performance Metrics](#key-performance-metrics)
- [Walk-Forward Analysis](#walk-forward-analysis)
- [Avoiding Overfitting](#avoiding-overfitting)
- [Monte Carlo Simulation](#monte-carlo-simulation)
- [Multi-Currency Testing (MQL5)](#multi-currency-testing-mql5)
- [Optimization Modes (MT5)](#optimization-modes-mt5)
- [Frame Functions (Inter-Pass Communication)](#frame-functions-inter-pass-communication)
- [Practical Backtesting Workflow](#practical-backtesting-workflow)
---
## Strategy Tester Modes
| Mode | Speed | Accuracy | When to Use |
|------|-------|----------|-------------|
| Every tick based on real ticks | Slowest | Most realistic | Final validation with broker-specific ticks |
| Every tick | Slow | High | Final validation, scalpers, intra-bar logic |
| 1-minute OHLC | Medium | Good | Most strategies (4 ticks per M1 bar) |
| Open prices only | Fast | Low | Bar-open strategies, rapid optimization |
| Math calculations | Instant | N/A | Pure computation without ticks |
Best practice: Optimize with Open Prices first, validate winners with Every Tick.
## Minimum Requirements for Valid Backtest
- 1,000+ trades minimum (ideally 2,000+)
- 2+ full market cycles (bull + bear + range)
- Multiple years of data
- Consistent spread settings (or variable spread)
## OnTester() - Custom Optimization Criterion (MQL5)
```mql5
double OnTester() {
double profit = TesterStatistics(STAT_PROFIT);
double profitFactor = TesterStatistics(STAT_PROFIT_FACTOR);
double sharpeRatio = TesterStatistics(STAT_SHARPE_RATIO);
double recoveryFactor = TesterStatistics(STAT_RECOVERY_FACTOR);
double maxDrawdown = TesterStatistics(STAT_EQUITY_DD_RELATIVE);
double totalTrades = TesterStatistics(STAT_TRADES);
if(totalTrades < 100) return 0; // Reject small samples
if(maxDrawdown > 25) return 0; // Reject excessive DD
if(profitFactor < 1.3) return 0; // Reject low PF
return sharpeRatio * recoveryFactor * MathSqrt(totalTrades);
}
```
## TesterStatistics() Constants
Complete table:
- STAT_PROFIT - Net profit
- STAT_GROSS_PROFIT - Gross profit
- STAT_GROSS_LOSS - Gross loss
- STAT_TRADES - Total trades
- STAT_PROFIT_TRADES - Winning trades
- STAT_LOSS_TRADES - Losing trades
- STAT_PROFIT_FACTOR - Gross profit / gross loss
- STAT_EXPECTED_PAYOFF - Expected payoff per trade
- STAT_SHARPE_RATIO - Sharpe ratio
- STAT_RECOVERY_FACTOR - Net profit / max drawdown
- STAT_EQUITY_DD - Max equity drawdown in money
- STAT_EQUITY_DD_PERCENT - Max equity drawdown %
- STAT_EQUITY_DD_RELATIVE - Relative equity drawdown %
- STAT_BALANCE_DD - Max balance drawdown
- STAT_BALANCE_DD_PERCENT - Max balance drawdown %
- STAT_MAX_PROFITTRADE - Largest profitable trade
- STAT_MAX_LOSSTRADE - Largest losing trade
- STAT_CONPROFITMAX - Max consecutive profit
- STAT_CONLOSSMAX - Max consecutive loss
- STAT_SHORT_TRADES - Short trades
- STAT_LONG_TRADES - Long trades
- STAT_WIN_SHORT_TRADES - Winning short trades
- STAT_WIN_LONG_TRADES - Winning long trades
## Key Performance Metrics
| Metric | Formula | Good Value | Excellent |
|--------|---------|------------|-----------|
| Sharpe Ratio | (Mean Return - Rf) / StdDev | > 1.0 | > 2.0 |
| Profit Factor | Gross Profit / Gross Loss | > 1.5 | > 2.0 |
| Recovery Factor | Net Profit / Max DD | > 3.0 | > 5.0 |
| Max Drawdown | Peak-to-trough % | < 20% | < 10% |
| Expected Payoff | Net Profit / Total Trades | > 0 | Context |
| Win Rate | Winning / Total | Context | Context |
## Walk-Forward Analysis
### Methodology
```
Window 1: [===== IN-SAMPLE =====][= OOS =]
Window 2: [===== IN-SAMPLE =====][= OOS =]
Window 3: [===== IN-SAMPLE =====][= OOS =]
Window 4: [===== IN-SAMPLE =====][= OOS =]
```
### Steps
1. Split data into overlapping windows (e.g., 12-month IS, 3-month OOS)
2. Optimize on in-sample period
3. Test optimized params on out-of-sample period
4. Slide window forward, repeat
5. Accept if OOS performance >= 50-80% of IS
6. MT5 supports natively: Strategy Tester > Settings > Forward period
### Walk-Forward Efficiency
WFE = (OOS annualized return) / (IS annualized return)
- WFE > 0.5 = acceptable
- WFE > 0.7 = good
- WFE < 0.3 = likely overfitted
## Avoiding Overfitting
### Principles
1. **Parameter stability**: Good params have good neighbors. If X=14 works but X=13 and X=15 don't, it's curve-fitted
2. **Fewer parameters**: Each extra parameter increases overfitting risk exponentially
3. **Out-of-sample testing**: Reserve 25-30% of data untouched
4. **Cross-market validation**: Test EURUSD strategy on GBPUSD
5. **Regime awareness**: Test across trending AND ranging markets
### Signs of Overfitting
- Sharp performance drop in forward test
- Many parameters (>5 optimized)
- Strategy only works on specific date range
- Unrealistically high backtest metrics
- Parameter sensitivity (small changes cause large performance swings)
## Monte Carlo Simulation
### Purpose
Test if results are due to skill or luck by randomizing trade sequence.
### Implementation
```mql5
double OnTester() {
// Collect all trade P&Ls
double trades[];
// ... populate from history ...
int simulations = 1000;
double worstDD95;
for(int sim = 0; sim < simulations; sim++) {
// Fisher-Yates shuffle
// Calculate max drawdown for shuffled sequence
}
// Sort DDs, find 95th percentile
// Reject if 95th percentile DD > 30%
return profit / (1 + worstDD95);
}
```
### What Monte Carlo Tells You
- Expected range of drawdowns (not just the one historical path)
- Probability of ruin at different risk levels
- Confidence interval for returns
- If strategy is fragile (high variance across simulations)
## Multi-Currency Testing (MQL5)
MT5 Strategy Tester supports multi-symbol natively:
```mql5
int OnInit() {
// Reference other symbols to include them in test
int handle_eur = iMA("EURUSD", PERIOD_H1, 14, 0, MODE_SMA, PRICE_CLOSE);
int handle_gbp = iMA("GBPUSD", PERIOD_H1, 14, 0, MODE_SMA, PRICE_CLOSE);
// Tester auto-synchronizes all referenced symbols
return INIT_SUCCEEDED;
}
```
## Optimization Modes (MT5)
| Mode | Description |
|------|-------------|
| Slow (Complete) | Tests every combination (exhaustive) |
| Fast (Genetic) | Genetic algorithm, finds near-optimal efficiently |
| Custom max/min | Optimizes by OnTester() return value |
### Cloud Computing
MQL5 Cloud Network distributes optimization across thousands of agents worldwide.
## Frame Functions (Inter-Pass Communication)
```mql5
// In EA (agent): send data at end of each pass
double OnTester() {
uchar data[];
// serialize results into data
FrameAdd("Results", 1, profit, data);
return profit;
}
// In terminal: receive during optimization
void OnTesterPass() {
ulong pass; string name; long id; double value; uchar data[];
while(FrameNext(pass, name, id, value, data)) {
PrintFormat("Pass #%d: profit=%.2f", pass, value);
}
}
// Control functions
void OnTesterInit() { /* before optimization starts */ }
void OnTesterDeinit() { /* after optimization finishes, aggregate results */ }
```
## Practical Backtesting Workflow
1. **Quick scan**: Open Prices Only, wide parameter ranges, genetic optimization
2. **Narrow down**: Reduce ranges around promising areas, complete optimization
3. **Validate**: Every Tick mode with best parameters
4. **Walk-forward**: Set forward period, verify OOS performance
5. **Monte Carlo**: Randomize trade sequence, check robustness
6. **Multi-market**: Test on correlated instruments
7. **Demo forward test**: Run on demo account for 1-3 months minimum

View File

@@ -0,0 +1,118 @@
# Banner Sizes & Art Direction Styles Reference
## Complete Banner Sizes
### Social Media
| Platform | Type | Size (px) | Aspect Ratio |
|----------|------|-----------|--------------|
| Facebook | Cover (desktop) | 820 × 312 | ~2.6:1 |
| Facebook | Cover (mobile) | 640 × 360 | ~16:9 |
| Facebook | Event cover | 1920 × 1080 | 16:9 |
| Twitter/X | Header | 1500 × 500 | 3:1 |
| Twitter/X | Ad banner | 800 × 418 | ~2:1 |
| LinkedIn | Company cover | 1128 × 191 | ~6:1 |
| LinkedIn | Personal banner | 1584 × 396 | 4:1 |
| YouTube | Channel art | 2560 × 1440 | 16:9 |
| YouTube | Safe area | 1546 × 423 | ~3.7:1 |
| Instagram | Stories | 1080 × 1920 | 9:16 |
| Instagram | Post | 1080 × 1080 | 1:1 |
| Pinterest | Pin | 1000 × 1500 | 2:3 |
### Web / Display Ads (Google Display Network)
| Name | Size (px) | Notes |
|------|-----------|-------|
| Medium Rectangle | 300 × 250 | Highest CTR |
| Leaderboard | 728 × 90 | Top of page |
| Wide Skyscraper | 160 × 600 | Sidebar |
| Half Page | 300 × 600 | Premium |
| Large Rectangle | 336 × 280 | High performer |
| Mobile Banner | 320 × 50 | Mobile default |
| Large Mobile | 320 × 100 | Mobile hero |
| Billboard | 970 × 250 | Desktop hero |
### Website
| Type | Size (px) |
|------|-----------|
| Full-width hero | 1920 × 6001080 |
| Section banner | 1200 × 400 |
| Blog header | 1200 × 628 |
| Email header | 600 × 200 |
### Print
| Type | Size |
|------|------|
| Roll-up | 850mm × 2000mm |
| Step-and-repeat | 8ft × 8ft |
| Vinyl outdoor | 6ft × 3ft |
| Trade show | 33in × 78in |
## 22 Art Direction Styles
1. **Minimalist** — White space dominant, single focal element, 1-2 colors, clean sans-serif
2. **Bold Typography** — Type IS the design; oversized, expressive letterforms fill canvas
3. **Gradient / Color Wash** — Smooth transitions, mesh gradients, chromatic blends
4. **Photo-Based** — Full-bleed photography with text overlay; hero lifestyle imagery
5. **Illustrated / Hand-Drawn** — Custom illustrations, bespoke icons, artisan feel
6. **Geometric / Abstract** — Shapes, lines, grids as primary visual elements
7. **Retro / Vintage** — Distressed textures, muted palettes, serif type, halftone dots
8. **Glassmorphism** — Frosted glass panels, blur backdrop, subtle border glow
9. **3D / Sculptural** — Rendered objects, depth, shadows; product-centric
10. **Neon / Cyberpunk** — Dark backgrounds, glowing neon accents, high contrast
11. **Duotone** — Two-color photo treatment; bold brand color overlay on image
12. **Editorial / Magazine** — Grid-heavy layouts, pull quotes, journalistic composition
13. **Collage / Mixed Media** — Cut-paper textures, photo cutouts, layered elements
14. **Retro Futurism** — Space-age nostalgia, chrome, gradients, optimism
15. **Expressive / Anti-Design** — Chaotic layouts, mixed fonts, deliberate "wrong" composition
16. **Digi-Cute / Kawaii** — Rounded shapes, pastel gradients, pixel art, playful characters
17. **Tactile / Sensory** — Puffy/squishy textures, hyper-real materials, embossed feel
18. **Data / Infographic** — Stats front-and-center, charts, numbers as heroes
19. **Dark Mode / Moody** — Near-black backgrounds, rich jewel tones, high contrast
20. **Flat / Solid Color** — Single background color, clean icons, no gradients
21. **Nature / Organic** — Earthy tones, botanical motifs, sustainable brand feel
22. **Motion-Ready / Kinetic** — Designed for animation; layered elements, loopable
## Design Principles
### Visual Hierarchy (3-Zone Rule)
- **Top**: Logo or main value prop
- **Middle**: Supporting message + visuals
- **Bottom**: CTA (button/QR/URL)
### Safe Zones
- Critical content in central 70-80% of canvas
- Avoid text/CTA within 50-100px of edges
- YouTube: 1546 × 423px safe area inside 2560 × 1440
- Meta/Instagram: central 80% to avoid UI chrome
### CTA Rules
- One CTA per banner
- High contrast vs background
- Bottom-right placement (terminal area)
- Min 44px height for mobile tap targets
- Action verbs: "Get", "Start", "Download", "Claim"
### Typography
- Max 2 typefaces per banner
- Min 16px body, ≥32px headline (digital)
- Min 4.5:1 contrast ratio
- Max 7 words/line, 3 lines for ads
### Text-to-Image Ratio
- Ads: under 20% text (Meta penalizes)
- Social covers: 60/40 image-to-text
- Print: 70pt+ headlines for 3-5m viewing distance
### Print Specs
- 300 DPI minimum (150 DPI for large format)
- 3-5mm bleed all sides
- CMYK color mode
- 1pt per foot viewing distance rule
## Pinterest Research Queries
Use these search queries on Pinterest for art direction references:
- `[purpose] banner design [style]` (e.g., "social media banner minimalist")
- `[platform] cover design inspiration` (e.g., "youtube channel art design")
- `creative banner layout [industry]` (e.g., "creative banner layout tech startup")
- `[style] graphic design 2026` (e.g., "gradient graphic design 2026")
- `banner ad design [product type]` (e.g., "banner ad design saas")

View File

@@ -0,0 +1,140 @@
# Brand Guidelines Template
Use this template to create comprehensive brand guidelines for any project.
## Document Structure
```markdown
# Brand Guidelines v{X.Y}
## Quick Reference
- **Primary Color:** #XXXXXX
- **Secondary Color:** #XXXXXX
- **Primary Font:** {font-family}
- **Voice:** {3 key traits}
## 1. Color Palette
### Primary Colors
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| {Name} | #{hex} | rgb({r},{g},{b}) | Primary brand color, CTAs, headers |
| {Name} | #{hex} | rgb({r},{g},{b}) | Supporting accent |
### Secondary Colors
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| {Name} | #{hex} | rgb({r},{g},{b}) | Secondary elements |
| {Name} | #{hex} | rgb({r},{g},{b}) | Highlights |
### Neutral Palette
| Name | Hex | RGB | Usage |
|------|-----|-----|-------|
| Background | #{hex} | rgb({r},{g},{b}) | Page backgrounds |
| Text Primary | #{hex} | rgb({r},{g},{b}) | Body text |
| Text Secondary | #{hex} | rgb({r},{g},{b}) | Captions, muted text |
| Border | #{hex} | rgb({r},{g},{b}) | Dividers, borders |
### Accessibility
- Text/Background Contrast: {ratio}:1 (WCAG {level})
- CTA Contrast: {ratio}:1
- All interactive elements meet WCAG 2.1 AA
## 2. Typography
### Font Stack
```css
--font-heading: '{Font}', sans-serif;
--font-body: '{Font}', sans-serif;
--font-mono: '{Font}', monospace;
```
### Type Scale
| Element | Font | Weight | Size (Desktop/Mobile) | Line Height |
|---------|------|--------|----------------------|-------------|
| H1 | {font} | 700 | 48px / 32px | 1.2 |
| H2 | {font} | 600 | 36px / 28px | 1.25 |
| H3 | {font} | 600 | 28px / 24px | 1.3 |
| H4 | {font} | 600 | 24px / 20px | 1.35 |
| Body | {font} | 400 | 16px / 16px | 1.5 |
| Small | {font} | 400 | 14px / 14px | 1.5 |
| Caption | {font} | 400 | 12px / 12px | 1.4 |
## 3. Logo Usage
### Variants
- **Primary:** Full horizontal logo with wordmark
- **Stacked:** Vertical arrangement for square spaces
- **Icon:** Symbol only for favicons, app icons
- **Monochrome:** Single color for limited palettes
### Clear Space
Minimum clear space = height of logo mark
### Minimum Size
- Digital: 80px width minimum
- Print: 25mm width minimum
### Don'ts
- Don't rotate or skew
- Don't change colors outside approved palette
- Don't add effects (shadows, gradients)
- Don't crop or modify proportions
- Don't place on busy backgrounds
## 4. Voice & Tone
### Brand Personality
{Trait 1}: {Description}
{Trait 2}: {Description}
{Trait 3}: {Description}
### Voice Chart
| Trait | We Are | We Are Not |
|-------|--------|------------|
| {Trait} | {Description} | {Anti-description} |
### Tone by Context
| Context | Tone | Example |
|---------|------|---------|
| Marketing | {tone} | "{example}" |
| Support | {tone} | "{example}" |
| Error Messages | {tone} | "{example}" |
| Success | {tone} | "{example}" |
### Prohibited Terms
- {term 1} (reason)
- {term 2} (reason)
## 5. Imagery Guidelines
### Photography Style
- {Lighting preference}
- {Subject guidelines}
- {Color treatment}
### Illustrations
- Style: {description}
- Colors: Brand palette only
- Stroke: {weight}px
### Icons
- Style: {outlined/filled/duotone}
- Size: 24px base grid
- Corner radius: {value}px
```
## Usage
1. Copy template above
2. Fill in brand-specific values
3. Save as `docs/brand-guidelines.md`
4. Reference in content workflows
## Extractable Fields
Scripts can extract:
- `colors.primary`, `colors.secondary`, `colors.neutral`
- `typography.heading`, `typography.body`
- `voice.traits`, `voice.prohibited`
- `logo.variants`, `logo.minSize`

View File

@@ -0,0 +1,320 @@
# Canvas Design System
Visual design philosophy, systematic composition, and sophisticated visual communication.
## Design Philosophy Approach
Canvas design operates through two-phase process:
### Phase 1: Design Philosophy Creation
Create visual philosophy - aesthetic movement expressed through form, space, color, composition. Not layouts or templates, but pure visual philosophy.
**What is created:** Design manifesto emphasizing:
- Visual expression over text
- Spatial communication
- Artistic interpretation
- Minimal words as visual accent
**Philosophy structure (4-6 paragraphs):**
- Space and form principles
- Color and material approach
- Scale and rhythm guidance
- Composition and balance rules
- Visual hierarchy system
### Phase 2: Visual Expression
Express philosophy through canvas artifacts:
- 90% visual design
- 10% essential text
- Museum-quality execution
- Systematic patterns
- Sophisticated composition
## Core Principles
### 1. Visual Communication First
Information lives in design, not paragraphs. Express ideas through:
- Color zones and fields
- Geometric precision
- Spatial relationships
- Visual weight and tension
- Form and structure
### 2. Minimal Text Integration
Text as rare, powerful gesture:
- Never paragraphs
- Only essential words
- Integrated into visual architecture
- Small labels, huge impact
- Typography as visual element
### 3. Expert Craftsmanship
Work must appear:
- Meticulously crafted
- Labored over with care
- Product of countless hours
- From absolute top of field
- Master-level execution
### 4. Systematic Patterns
Use scientific visual language:
- Repeating patterns
- Perfect shapes
- Dense accumulation of marks
- Layered elements
- Patient repetition rewards sustained viewing
## Design Movement Examples
### Concrete Poetry
**Philosophy:** Communication through monumental form and bold geometry.
**Expression:**
- Massive color blocks
- Sculptural typography (huge words, tiny labels)
- Brutalist spatial divisions
- Polish poster energy meets Le Corbusier
- Ideas through visual weight and spatial tension
- Text as rare, powerful gesture
### Chromatic Language
**Philosophy:** Color as primary information system.
**Expression:**
- Geometric precision
- Color zones create meaning
- Typography minimal - small sans-serif labels
- Josef Albers' interaction meets data visualization
- Information encoded spatially and chromatically
- Words only anchor what color shows
### Analog Meditation
**Philosophy:** Quiet visual contemplation through texture and breathing room.
**Expression:**
- Paper grain, ink bleeds
- Vast negative space
- Photography and illustration dominate
- Typography whispered (small, restrained)
- Japanese photobook aesthetic
- Images breathe across pages
- Text appears sparingly - short phrases only
### Organic Systems
**Philosophy:** Natural clustering and modular growth patterns.
**Expression:**
- Rounded forms
- Organic arrangements
- Color from nature through architecture
- Information through visual diagrams
- Spatial relationships and iconography
- Text only for key labels floating in space
- Composition tells story through spatial orchestration
### Geometric Silence
**Philosophy:** Pure order and restraint.
**Expression:**
- Grid-based precision
- Bold photography or stark graphics
- Dramatic negative space
- Typography precise but minimal
- Small essential text, large quiet zones
- Swiss formalism meets Brutalist material honesty
- Structure communicates, not words
- Every alignment from countless refinements
## Implementation Guidelines
### Subtle Reference Integration
Embed conceptual DNA without announcing:
- Niche reference woven invisibly
- Those who know feel it intuitively
- Others experience masterful abstract composition
- Like jazz musician quoting another song
- Sophisticated, never literal
- Reference enhances depth quietly
### Color Approach
**Intentional palette:**
- Limited colors (2-5)
- Cohesive system
- Purposeful relationships
- oklch color space for precision
- Each shade carries meaning
**Example palette:**
```
--color-primary: oklch(0.55 0.22 264)
--color-accent: oklch(0.75 0.18 45)
--color-neutral: oklch(0.90 0.02 264)
--color-dark: oklch(0.25 0.15 264)
```
### Typography System
**Thin fonts preferred:**
- Light weights (200-300)
- Clean sans-serifs
- Geometric precision
- Small sizes for labels
- Large sizes for impact moments
**Font integration:**
- Search `./canvas-fonts` directory
- Download needed fonts
- Bring typography onto canvas
- Part of art, not typeset digitally
### Composition Rules
**Systematic approach:**
- Repeating patterns establish rhythm
- Perfect geometric shapes
- Clinical typography
- Reference markers suggest imaginary discipline
- Dense accumulation builds meaning
- Layered patterns reward attention
**Spacing discipline:**
- Nothing falls off page
- Nothing overlaps
- Every element within canvas boundaries
- Proper margins non-negotiable
- Breathing room and clear separation
- Professional execution mandatory
### Canvas Boundaries
**Technical specs:**
- Single page default (multi-page when requested)
- PDF or PNG output
- High resolution
- Clean margins
- Contained composition
- Flawless formatting
## Multi-Page Design Systems
When creating multiple pages:
### Approach
- Treat first page as single page in coffee table book
- Create more pages along same philosophy
- Distinctly different but cohesive
- Pages tell story tastefully
- Full creative freedom
### Consistency Elements
- Shared color palette
- Consistent typography system
- Related compositional approach
- Visual language continuity
- Philosophical thread throughout
### Variation Strategy
- Unique twist per page
- Different focal points
- Varied spatial arrangements
- Complementary patterns
- Progressive visual narrative
## Execution Checklist
Before finalizing:
- [ ] Philosophy guides every decision
- [ ] 90% visual, 10% text maximum
- [ ] Text minimal and integrated
- [ ] Nothing overlaps or falls off page
- [ ] Margins and spacing pristine
- [ ] Composition cohesive with art
- [ ] Appears meticulously crafted
- [ ] Master-level execution evident
- [ ] Sophisticated, never amateur
- [ ] Could be displayed in museum
- [ ] Proves undeniable expertise
- [ ] Formatting flawless
- [ ] Every detail perfect
## Quality Standards
### What to Avoid
- Cartoony aesthetics
- Amateur execution
- Text-heavy composition
- Random placement
- Overlapping elements
- Inconsistent spacing
- Obvious AI generation
- Lack of refinement
### What to Achieve
- Museum quality
- Magazine worthy
- Art object status
- Countless hours appearance
- Top-of-field craftsmanship
- Philosophical coherence
- Visual sophistication
- Systematic precision
## Refinement Process
### Initial Pass
Create based on philosophy and principles.
### Second Pass (Critical)
- Don't add more graphics
- Refine what exists
- Make extremely crisp
- Respect minimalism philosophy
- Increase cohesion with art
- Make existing elements more artistic
- Polish rather than expand
### Final Verification
User already said: "It isn't perfect enough. Must be pristine, masterpiece of craftsmanship, as if about to be displayed in museum."
Apply this standard before delivery.
## Output Format
**Required files:**
1. Design philosophy (.md file)
2. Visual expression (.pdf or .png)
**Philosophy file contains:**
- Movement name
- 4-6 paragraph philosophy
- Visual principles
- Execution guidance
**Canvas file contains:**
- Visual interpretation
- Minimal text
- Systematic composition
- Expert-level execution
## Use Cases
Apply canvas design for:
- Brand identity systems
- Poster designs
- Visual manifestos
- Design system documentation
- Art pieces and compositions
- Conceptual visual frameworks
- Editorial design
- Exhibition materials
- Coffee table books
- Design philosophy demonstrations

View File

@@ -0,0 +1,25 @@
# Default Captions by Language
Select captions based on user's conversation language.
| Action | English | Spanish | French | German | Chinese | Japanese | Korean |
|--------|---------|---------|--------|--------|---------|----------|--------|
| Waving | Hi~ | ¡Hola! | Salut~ | Hallo~ | 嗨~ | やあ~ | 안녕~ |
| Laughing | LOL | Jajaja | MDR | Haha | 哈哈哈 | 笑 | ㅋㅋㅋ |
| Crying | Boo-hoo | Buaaa | Snif | Heul | 呜呜呜 | えーん | 흑흑 |
| Heart | Love ya | Te quiero | Je t'aime | Liebe | 爱你哦 | 大好き | 사랑해 |
## Filename Convention
| Action | Filename ID |
|--------|-------------|
| Happy waving | hi |
| Laughing hard | laugh |
| Crying tears | cry |
| Heart gesture | love |
## Custom Caption Guidelines
- Keep captions short: 1-3 words work best
- Actions auto-match caption meaning (e.g., "Sleepy" → yawning action)
- Users can provide captions in any language

View File

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

View File

@@ -0,0 +1,95 @@
# CIP Deliverable Guide
## Core Identity
### Primary Logo
- Vector format (SVG, AI, EPS)
- Clear space rules defined
- Scalable from favicon to billboard
### Logo Variations
- Horizontal, vertical, stacked
- Icon/symbol only
- Monochrome versions (black, white, reversed)
## Stationery Set
### Business Card
- Standard: 3.5x2 inches / 85x55mm
- Premium paper stock (300-400gsm)
- Finishes: matte, spot UV, foil, emboss
### Letterhead
- A4 or Letter size
- Header area for logo/contact
- Digital and print versions
### Envelope
- DL, C4, C5 sizes
- Logo on flap or front
- Return address styling
## Office Environment
### Reception Signage
- 3D dimensional letters
- Backlit LED options
- Materials: acrylic, metal, wood
### Wayfinding System
- Consistent icon system
- Clear hierarchy
- ADA compliance
### Wall Graphics
- Mission/values displays
- Large-scale murals
- Window frosting
## Apparel
### Polo Shirt
- Embroidery preferred
- Left chest placement
- Quality fabric (pique cotton)
### Uniforms
- Department color coding
- Name badge integration
- Safety requirements if applicable
## Vehicle Branding
### Car/Sedan
- Door panel branding
- Partial or full wrap
- Contact information visible
### Fleet Vehicles
- Consistent design across fleet
- High visibility contact details
- Professional installation
## Digital Assets
### Social Media
- Profile pictures (icon version)
- Cover images (platform-specific)
- Post templates
### Email Signature
- HTML responsive
- Max 600px width
- Essential contact only
## Events & Promotional
### Trade Show Booth
- Modular design
- Easy assembly
- Key messaging visible
### Promotional Items
- Quality over quantity
- Useful items preferred
- Brand colors prominent

View File

@@ -0,0 +1,121 @@
# CIP Design Reference
Corporate Identity Program design with 50+ deliverables, 20 styles, 20 industries. Generate mockups with Gemini Nano Banana (Flash/Pro).
## Scripts
| Script | Purpose |
|--------|---------|
| `scripts/cip/search.py` | Search deliverables, styles, industries; generate CIP briefs |
| `scripts/cip/generate.py` | Generate CIP mockups with Gemini (Flash/Pro) |
| `scripts/cip/render-html.py` | Render HTML presentation from CIP mockups |
| `scripts/cip/core.py` | BM25 search engine for CIP data |
## Commands
### CIP Brief (Start Here)
```bash
python3 ~/.claude/skills/design/scripts/cip/search.py "tech startup" --cip-brief -b "BrandName"
```
### Search Domains
```bash
# Deliverables
python3 ~/.claude/skills/design/scripts/cip/search.py "business card letterhead" --domain deliverable
# Design styles
python3 ~/.claude/skills/design/scripts/cip/search.py "luxury premium elegant" --domain style
# Industry guidelines
python3 ~/.claude/skills/design/scripts/cip/search.py "hospitality hotel" --domain industry
# Mockup contexts
python3 ~/.claude/skills/design/scripts/cip/search.py "office reception" --domain mockup
```
### Generate Mockups
```bash
# With logo (RECOMMENDED - uses image editing)
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --deliverable "business card" --industry "consulting"
# Full CIP set with logo
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo /path/to/logo.png --industry "consulting" --set
# Pro model for 4K text rendering
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TopGroup" --logo logo.png --deliverable "business card" --model pro
# Custom deliverables with aspect ratio
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "GreenLeaf" --logo logo.png --industry "organic food" --deliverables "letterhead,packaging,vehicle" --ratio 16:9
# Without logo (AI generates interpretation)
python3 ~/.claude/skills/design/scripts/cip/generate.py --brand "TechFlow" --deliverable "business card" --no-logo-prompt
```
### Render HTML Presentation
```bash
python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images /path/to/cip-output
python3 ~/.claude/skills/design/scripts/cip/render-html.py --brand "TopGroup" --industry "consulting" --images ./topgroup-cip --output presentation.html
```
## Models
- `flash` (default): `gemini-2.5-flash-image` - Fast, cost-effective
- `pro`: `gemini-3-pro-image-preview` - Quality, 4K text rendering
## Deliverable Categories
| Category | Items |
|----------|-------|
| Core Identity | Logo, Logo Variations |
| Stationery | Business Card, Letterhead, Envelope, Folder, Notebook, Pen |
| Security/Access | ID Badge, Lanyard, Access Card |
| Office Environment | Reception Signage, Wayfinding, Meeting Room Signs, Wall Graphics |
| Apparel | Polo Shirt, T-Shirt, Cap, Jacket, Apron |
| Promotional | Tote Bag, Gift Box, USB Drive, Water Bottle, Mug, Umbrella |
| Vehicle | Car Sedan, Van, Truck |
| Digital | Social Media, Email Signature, PowerPoint, Document Templates |
| Product | Packaging Box, Labels, Tags, Retail Display |
| Events | Trade Show Booth, Banner Stand, Table Cover, Backdrop |
## Design Styles
| Style | Colors | Best For |
|-------|--------|----------|
| Corporate Minimal | Navy, White, Blue | Finance, Legal, Consulting |
| Modern Tech | Purple, Cyan, Green | Tech, Startups, SaaS |
| Luxury Premium | Black, Gold, White | Fashion, Jewelry, Hotels |
| Warm Organic | Brown, Green, Cream | Food, Organic, Artisan |
| Bold Dynamic | Red, Orange, Black | Sports, Entertainment |
## HTML Presentation Features
- Hero section with brand name, industry, style, mood
- Deliverable cards with mockup images
- Descriptions: concept, purpose, specifications
- Responsive desktop/mobile, dark theme
- Images embedded as base64 (single-file portable)
## Workflow
1. Generate CIP brief → `scripts/cip/search.py --cip-brief`
2. Generate mockups with logo → `scripts/cip/generate.py --brand --logo --industry --set`
3. Render HTML presentation → `scripts/cip/render-html.py --brand --industry --images`
**Tip:** If no logo exists, use Logo Design (built-in) to generate one first.
## Detailed References
- `references/cip-deliverable-guide.md` - Deliverable specifications
- `references/cip-style-guide.md` - Design style descriptions
- `references/cip-prompt-engineering.md` - AI generation prompts
## Setup
```bash
export GEMINI_API_KEY="your-key"
pip install google-genai pillow
```

View File

@@ -0,0 +1,84 @@
# CIP Mockup Prompt Engineering
## Base Prompt Structure
```
Professional corporate identity mockup photograph showing [DELIVERABLE] for brand '[BRAND_NAME]', [STYLE] design style, using colors [COLORS], [TYPOGRAPHY] typography, logo placement: [PLACEMENT], [MATERIALS] materials with [FINISHES] finish, [CONTEXT] setting, [MOOD] mood, photorealistic product photography, soft natural lighting, high quality professional shot, 8k resolution detailed
```
## Deliverable-Specific Modifiers
### Business Card
```
business card on marble surface, stack of cards, premium paper texture, soft shadows, 45 degree angle
```
### Letterhead
```
letterhead flat lay with envelope and pen, velvet fabric background, brand stationery set, overhead view
```
### Office Signage
```
3D logo signage on office wall, modern lobby interior, backlit LED, brushed metal finish, architectural photography
```
### Vehicle Branding
```
branded vehicle on urban street, 3/4 front angle view, professional car wrap, motion blur background optional
```
### Apparel (Polo/T-Shirt)
```
folded polo shirt on clean background, embroidered logo on chest, premium fabric texture, product photography
```
## Style Modifiers
### Corporate Minimal
```
clean minimal aesthetic, white space, subtle shadows, matte finish, professional
```
### Luxury Premium
```
dark background, dramatic rim lighting, gold accents, premium materials, sophisticated
```
### Modern Tech
```
gradient colors, geometric elements, clean surfaces, futuristic, innovative
```
### Warm Organic
```
natural materials, kraft paper texture, warm lighting, authentic, artisan
```
## Lighting Modifiers
- **Studio:** `professional studio lighting, even illumination`
- **Natural:** `soft natural daylight, window light`
- **Dramatic:** `dramatic rim light, dark background, high contrast`
- **Warm:** `warm golden hour lighting, cozy atmosphere`
## Context Modifiers
- **Marble desk:** `white marble surface, soft shadows, luxury`
- **Wooden table:** `warm wood grain, natural, artisan`
- **Office interior:** `modern office environment, architectural`
- **Flat lay:** `overhead view, organized arrangement`
- **Lifestyle:** `in-use context, human element`
## Quality Modifiers
Always include:
```
photorealistic, professional photography, high quality, 8k resolution, detailed, sharp focus
```
## Negative Prompts (what to avoid)
```
blurry, low quality, distorted text, misspelled, amateur, clipart, cartoon, illustration, watermark
```

View File

@@ -0,0 +1,68 @@
# CIP Design Style Guide
## Corporate Minimal
**Industries:** Finance, Legal, Consulting, Tech
**Colors:** Navy (#0F172A), White (#FFFFFF), Blue accents
**Typography:** Clean sans-serif (Inter, Helvetica)
**Materials:** Premium matte paper, subtle textures
**Finishes:** Matte, spot UV on logo
## Modern Tech
**Industries:** Tech, SaaS, Startups, AI
**Colors:** Purple (#6366F1), Cyan (#0EA5E9), Green (#10B981)
**Typography:** Geometric sans (Outfit, Poppins)
**Materials:** Smooth surfaces, gradient prints
**Finishes:** Gloss, metallic accents
## Luxury Premium
**Industries:** Fashion, Jewelry, Hotels, Fine Dining
**Colors:** Black (#1C1917), Gold (#D4AF37), White
**Typography:** Elegant serif (Playfair), thin sans
**Materials:** Heavy cotton paper, leather, metal
**Finishes:** Gold foil, emboss, deboss, soft-touch
## Classic Traditional
**Industries:** Law Firms, Heritage Brands, Finance
**Colors:** Navy, Burgundy, Gold
**Typography:** Traditional serif (Times, Garamond)
**Materials:** Quality laid paper, wood
**Finishes:** Letterpress, gold emboss
## Warm Organic
**Industries:** Food, Organic, Wellness, Craft
**Colors:** Brown (#8B4513), Green (#228B22), Cream
**Typography:** Friendly serif, organic script
**Materials:** Kraft paper, recycled materials
**Finishes:** Uncoated, natural textures
## Bold Dynamic
**Industries:** Sports, Entertainment, Gaming
**Colors:** Red (#DC2626), Orange (#F97316), Black
**Typography:** Bold condensed sans
**Materials:** High-contrast, metallic
**Finishes:** Gloss, vibrant colors
## Fresh Modern
**Industries:** Healthcare, Wellness, Fintech
**Colors:** Mint (#10B981), Sky (#0EA5E9), White
**Typography:** Modern rounded sans
**Materials:** Light, clean surfaces
**Finishes:** Matte, clean minimal
## Soft Elegant
**Industries:** Beauty, Wedding, Spa, Fashion
**Colors:** Pink (#F472B6), Gold, White
**Typography:** Elegant script, thin sans
**Materials:** Soft-touch, quality paper
**Finishes:** Rose gold foil, emboss
## Color Psychology
| Color | Meaning | Best Use |
|-------|---------|----------|
| Blue | Trust, stability | Finance, Tech, Healthcare |
| Green | Growth, nature | Eco, Wellness, Organic |
| Gold | Luxury, prestige | Premium, Jewelry |
| Red | Energy, passion | Food, Sports |
| Black | Sophistication | Luxury, Fashion |
| White | Clean, minimal | Tech, Healthcare |

View File

@@ -0,0 +1,357 @@
# CJK Typography & Mixed-Script Guide
Rules for Chinese, Japanese, and Korean text in DOCX documents.
## Table of Contents
1. [Font Selection](#font-selection)
2. [Font Size Names (CJK)](#font-size-names)
3. [RunFonts Mapping](#runfonts-mapping)
4. [Punctuation & Line Breaking](#punctuation--line-breaking)
5. [Paragraph Indentation](#paragraph-indentation)
6. [Line Spacing for CJK](#line-spacing)
7. [Chinese Government Standard (GB/T 9704)](#gbt-9704)
8. [Mixed CJK + Latin Best Practices](#mixed-script)
9. [OpenXML Quick Reference](#openxml-quick-reference)
---
## Font Selection
### Recommended CJK Fonts
| Language | Serif (正文) | Sans (标题) | Notes |
|----------|-------------|-------------|-------|
| **Simplified Chinese** | 宋体 (SimSun) | 微软雅黑 (Microsoft YaHei) | YaHei for screen, SimSun for print |
| **Simplified Chinese** | 仿宋 (FangSong) | 黑体 (SimHei) | Government documents |
| **Traditional Chinese** | 新細明體 (PMingLiU) | 微軟正黑體 (Microsoft JhengHei) | Taiwan standard |
| **Japanese** | MS 明朝 (MS Mincho) | MS ゴシック (MS Gothic) | Classic pairing |
| **Japanese** | 游明朝 (Yu Mincho) | 游ゴシック (Yu Gothic) | Modern, Windows 10+ |
| **Korean** | 바탕 (Batang) | 맑은 고딕 (Malgun Gothic) | Standard pairing |
### Government Document Fonts (公文)
| Element | Font | Size |
|---------|------|------|
| 标题 (title) | 小标宋 (FZXiaoBiaoSong-B05S) | 二号 (22pt) |
| 一级标题 | 黑体 (SimHei) | 三号 (16pt) |
| 二级标题 | 楷体_GB2312 (KaiTi_GB2312) | 三号 (16pt) |
| 三级标题 | 仿宋_GB2312 加粗 | 三号 (16pt) |
| 正文 (body) | 仿宋_GB2312 (FangSong_GB2312) | 三号 (16pt) |
| 附注/页码 | 宋体 (SimSun) | 四号 (14pt) |
---
## Font Size Names
CJK uses named sizes. Map to points and `w:sz` half-point values:
| 字号 | Points | `w:sz` | Common Use |
|------|--------|--------|------------|
| 初号 | 42pt | 84 | Display title |
| 小初 | 36pt | 72 | Large title |
| 一号 | 26pt | 52 | Chapter heading |
| 小一 | 24pt | 48 | Major heading |
| 二号 | 22pt | 44 | Document title (公文) |
| 小二 | 18pt | 36 | Western H1 equivalent |
| 三号 | 16pt | 32 | CJK heading / 公文 body |
| 小三 | 15pt | 30 | Sub-heading |
| 四号 | 14pt | 28 | CJK subheading |
| 小四 | 12pt | 24 | Standard body (CJK) |
| 五号 | 10.5pt | 21 | Compact CJK body |
| 小五 | 9pt | 18 | Footnotes |
| 六号 | 7.5pt | 15 | Fine print |
---
## RunFonts Mapping
OpenXML uses four font slots to handle multilingual text:
```xml
<w:rFonts
w:ascii="Calibri" <!-- Latin characters (U+0000U+007F) -->
w:hAnsi="Calibri" <!-- Latin extended, Greek, Cyrillic -->
w:eastAsia="SimSun" <!-- CJK Unified Ideographs, Kana, Hangul -->
w:cs="Arial" <!-- Arabic, Hebrew, Thai, Devanagari -->
/>
```
**Word's character classification logic:**
1. Character is in CJK range → uses `w:eastAsia` font
2. Character is in complex script range → uses `w:cs` font
3. Character is basic Latin (ASCII) → uses `w:ascii` font
4. Everything else → uses `w:hAnsi` font
**Key**: `w:eastAsia` is the **only** way to set CJK fonts. Setting just `w:ascii` will NOT affect CJK characters. Mixed text within a single run auto-switches fonts at the character level — no need for separate runs.
### Document Defaults
```xml
<w:docDefaults>
<w:rPrDefault>
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="SimSun" w:cs="Arial" />
<w:sz w:val="22" />
<w:szCs w:val="22" />
<w:lang w:val="en-US" w:eastAsia="zh-CN" />
</w:rPr>
</w:rPrDefault>
</w:docDefaults>
```
`w:lang w:eastAsia` helps Word resolve ambiguous characters (e.g., punctuation shared between CJK and Latin).
---
## Punctuation & Line Breaking
### Full-Width vs Half-Width
CJK text uses full-width punctuation:
| Type | CJK | Latin |
|------|-----|-------|
| Period | 。(U+3002) | . |
| Comma | (U+FF0C) 、(U+3001) | , |
| Colon | (U+FF1A) | : |
| Semicolon | (U+FF1B) | ; |
| Quotes | 「」『』 or ""'' | "" '' |
| Parentheses | (U+FF08/09) | () |
In mixed text, use the punctuation style of the **surrounding language context**.
### OpenXML Controls
```xml
<w:pPr>
<w:adjustRightInd w:val="true" /> <!-- Adjust right indent for CJK punctuation -->
<w:snapToGrid w:val="true" /> <!-- Align to document grid -->
<w:kinsoku w:val="true" /> <!-- Enable CJK line breaking rules -->
<w:overflowPunct w:val="true" /> <!-- Allow punctuation to overflow margins -->
</w:pPr>
```
### Kinsoku Rules (禁則処理)
Prevents certain characters from appearing at the start or end of a line:
- **Cannot start a line**: `)」』】〉》。、,!?;:` and closing brackets
- **Cannot end a line**: `(「『【〈《` and opening brackets
Word applies these automatically when `w:kinsoku` is enabled.
### Line Breaking
- CJK characters can break between **any two characters** (no word boundaries needed)
- Latin words within CJK text still follow word-boundary breaking
- `w:wordWrap w:val="false"` enables CJK-style breaking (break anywhere)
---
## Paragraph Indentation
### Chinese Standard: 2-Character Indent
Chinese body text conventionally uses a 2-character first-line indent:
```xml
<w:ind w:firstLineChars="200" /> <!-- 200 = 2 characters × 100 -->
```
Preferred over `w:firstLine` with fixed DXA because `firstLineChars` scales with font size.
| Indent | Value |
|--------|-------|
| 1 character | `w:firstLineChars="100"` |
| 2 characters | `w:firstLineChars="200"` |
| 3 characters | `w:firstLineChars="300"` |
---
## Line Spacing
- CJK characters are taller than Latin characters at the same point size
- Default `1.0` line spacing may feel cramped with CJK text
- Recommended: `1.151.5` for mixed CJK+Latin, `1.0` with fixed 28pt for 公文
### Auto Spacing
```xml
<w:pPr>
<w:autoSpaceDE w:val="true"/> <!-- auto space between CJK and Latin -->
<w:autoSpaceDN w:val="true"/> <!-- auto space between CJK and numbers -->
</w:pPr>
```
Adds ~¼ em spacing between CJK and non-CJK characters automatically. **Recommended: always enable.**
---
## GB/T 9704
Chinese government document standard (党政机关公文格式). These are **strict requirements**, not suggestions.
### Page Setup
| Parameter | Value | OpenXML |
|-----------|-------|---------|
| Page size | A4 (210×297mm) | Width=11906, Height=16838 |
| Top margin | 37mm | 2098 DXA |
| Bottom margin | 35mm | 1984 DXA |
| Left margin | 28mm | 1588 DXA |
| Right margin | 26mm | 1474 DXA |
| Characters/line | 28 | |
| Lines/page | 22 | |
| Line spacing | Fixed 28pt | `line="560"` lineRule="exact" |
### Document Structure
```
┌─────────────────────────────────┐
│ 发文机关标志 (红头) │ ← 小标宋 or 红色大字
│ ══════════════════ (红线) │ ← Red #FF0000, 2pt
├─────────────────────────────────┤
│ 发文字号: X机发2025X号 │ ← 仿宋 三号, centered
│ │
│ 标题 (Title) │ ← 小标宋 二号, centered
│ │ 可分多行,回行居中
│ 主送机关: │ ← 仿宋 三号
│ │
│ 正文 (Body)... │ ← 仿宋_GB2312 三号
│ 一、一级标题 │ ← 黑体 三号
│ (一)二级标题 │ ← 楷体 三号
│ 1. 三级标题 │ ← 仿宋 三号 加粗
│ (1) 四级标题 │ ← 仿宋 三号
│ │
│ 附件: 1. xxx │ ← 仿宋 三号
│ │
│ 发文机关署名 │ ← 仿宋 三号
│ 成文日期 │ ← 仿宋 三号, 小写中文数字
├─────────────────────────────────┤
│ ══════════════════ (版记线) │
│ 抄送: xxx │ ← 仿宋 四号
│ 印发机关及日期 │ ← 仿宋 四号
└─────────────────────────────────┘
```
### Numbering System
```
一、 ← 黑体 (SimHei), no indentation
(一) ← 楷体 (KaiTi), indented 2 chars
1. ← 仿宋加粗 (FangSong Bold), indented 2 chars
(1) ← 仿宋 (FangSong), indented 2 chars
```
### Colors
| Element | Color | Requirement |
|---------|-------|-------------|
| All body text | Black #000000 | Mandatory |
| 红头 (agency name) | Red #FF0000 | Mandatory |
| 红线 (separator) | Red #FF0000 | Mandatory |
| 公章 (official seal) | Red | Mandatory |
### Page Numbers
- Position: bottom center
- Format: `-X-` (dash-number-dash)
- Font: 宋体 四号 (SimSun 14pt, `sz="28"`)
- No page number on cover page if present
---
## Mixed Script
### Font Size Harmony
CJK characters appear larger than Latin characters at the same point size. Compensation:
- If body is Calibri 11pt, pair with CJK at 11pt (same size — CJK looks slightly larger but acceptable)
- If precise visual match needed, CJK can be set 0.51pt smaller
- In practice, same point size is standard — don't over-optimize
### Bold and Italic
- **Chinese/Japanese have no true italic.** Word synthesizes a slant which looks poor
- Use **bold** for emphasis in CJK text
- Use 着重号 (emphasis dots) for traditional emphasis: `<w:em w:val="dot"/>` on RunProperties
---
## OpenXML Quick Reference
### Set EastAsia Font (C#)
```csharp
new Run(
new RunProperties(
new RunFonts { EastAsia = "SimSun", Ascii = "Calibri", HighAnsi = "Calibri" },
new FontSize { Val = "32" } // 三号 = 16pt = sz 32
),
new Text("这是正文内容")
);
```
### Document Defaults (C#)
```csharp
new DocDefaults(new RunPropertiesDefault(new RunPropertiesBaseStyle(
new RunFonts {
Ascii = "Calibri", HighAnsi = "Calibri",
EastAsia = "Microsoft YaHei"
},
new Languages { Val = "en-US", EastAsia = "zh-CN" }
)));
```
### 公文 Style Definitions (C#)
```csharp
// Title style — 小标宋 二号 centered
new Style(
new StyleName { Val = "GongWen Title" },
new BasedOn { Val = "Normal" },
new StyleRunProperties(
new RunFonts { EastAsia = "FZXiaoBiaoSong-B05S" },
new FontSize { Val = "44" }, // 二号 = 22pt
new Bold()
),
new StyleParagraphProperties(
new Justification { Val = JustificationValues.Center },
new SpacingBetweenLines { Line = "560", LineRule = LineSpacingRuleValues.Exact }
)
) { Type = StyleValues.Paragraph, StyleId = "GongWenTitle" };
// Body style — 仿宋_GB2312 三号
new Style(
new StyleName { Val = "GongWen Body" },
new StyleRunProperties(
new RunFonts { EastAsia = "FangSong_GB2312", Ascii = "FangSong_GB2312" },
new FontSize { Val = "32" } // 三号 = 16pt
),
new StyleParagraphProperties(
new SpacingBetweenLines { Line = "560", LineRule = LineSpacingRuleValues.Exact }
)
) { Type = StyleValues.Paragraph, StyleId = "GongWenBody" };
```
### Emphasis Dots (着重号)
```csharp
new RunProperties(new Emphasis { Val = EmphasisMarkValues.Dot });
```
### East Asian Text Layout
```xml
<!-- Snap to grid (align CJK chars to character grid) -->
<w:snapToGrid w:val="true"/>
<!-- Two-lines-in-one (双行合一) -->
<w:eastAsianLayout w:id="1" w:combine="true"/>
<!-- Vertical text in a cell -->
<w:textDirection w:val="tbRl"/>
```

View File

@@ -0,0 +1,184 @@
# Chinese University Thesis Template Guide (中国高校论文模板指南)
## Why This Guide Exists
Chinese university thesis templates (.docx) have structural patterns that differ significantly
from Western templates. Agents that assume Western conventions (Heading1/Heading2/Normal) will
fail repeatedly. This guide documents the ACTUAL patterns found in Chinese templates.
## Common StyleId Patterns
### Pattern A: Numeric IDs (most common in Chinese Word templates)
| Style Purpose | styleId | w:name | w:basedOn |
|--------------|---------|--------|-----------|
| Normal body | `a` | "Normal" | — |
| Default paragraph font | `a0` | "Default Paragraph Font" | — |
| Heading 1 (章标题) | `1` | "heading 1" | `a` |
| Heading 2 (节标题) | `2` | "heading 2" | `a` |
| Heading 3 (小节标题) | `3` | "heading 3" | `a` |
| TOC 1 | `11` | "toc 1" | `a` |
| TOC 2 | `21` | "toc 2" | `a` |
| TOC 3 | `31` | "toc 3" | `a` |
| Header | `a3` | "header" | `a` |
| Footer | `a4` | "footer" | `a` |
| Table of Contents heading | `10` | "TOC Heading" | `1` |
### Pattern B: English IDs (less common, usually from international templates)
Standard Heading1/Heading2/Heading3/Normal — these follow the Western pattern.
### Pattern C: Mixed (some Chinese, some English)
Some templates define custom styles with Chinese names:
| Style Purpose | styleId | w:name |
|--------------|---------|--------|
| 论文标题 | `lunwenbiaoti` | "论文标题" |
| 章标题 | `zhangbiaoti` | "章标题" |
| 正文 | `zhengwen` | "正文" |
### How to Identify Which Pattern
```bash
# Extract all styleIds from the template
$CLI analyze --input template.docx --styles-only
# Or manually:
# unzip template.docx word/styles.xml
# Search for w:styleId= in the extracted file
```
Look at the first few styleIds. If you see `1`, `2`, `3`, `a`, `a0` → Pattern A.
If you see `Heading1`, `Normal` → Pattern B.
## Standard Thesis Structure
Chinese university theses follow a highly standardized structure:
```
┌─────────────────────────────────────┐
│ 封面 (Cover Page) │ ← Usually 1-2 pages
│ - 校名、校徽 │
│ - 论文题目 (title) │
│ - 作者、导师、院系、日期 │
├─────────────────────────────────────┤
│ 学术诚信承诺书 / 独创性声明 │ ← 1 page
│ (Academic Integrity Declaration) │
├─────────────────────────────────────┤
│ 中文摘要 (Chinese Abstract) │ ← 1-2 pages
│ - "摘 要" heading │
│ - Abstract body │
│ - "关键词:" line │
├─────────────────────────────────────┤
│ 英文摘要 (English Abstract) │ ← 1-2 pages
│ - "ABSTRACT" heading │
│ - Abstract body │
│ - "Keywords:" line │
├─────────────────────────────────────┤
│ 目录 (Table of Contents) │ ← 1-3 pages
│ - Often inside SDT block │
│ - Static example entries │
│ - TOC field code │
├─────────────────────────────────────┤
│ 正文 (Body) │ ← Main content
│ 第1章 绪论 │
│ 1.1 研究背景 │
│ 1.2 研究目的和意义 │
│ 第2章 文献综述 │
│ ... │
│ 第N章 结论与展望 │
├─────────────────────────────────────┤
│ 参考文献 (References) │ ← Styled differently
├─────────────────────────────────────┤
│ 致谢 (Acknowledgments) │ ← Optional
├─────────────────────────────────────┤
│ 附录 (Appendices) │ ← Optional
└─────────────────────────────────────┘
```
## Identifying Zone Boundaries in Templates
Templates contain EXAMPLE content that must be replaced. Here's how to find the zones:
### Zone A (Front matter) — KEEP from template
- Starts at: paragraph 0
- Ends at: the paragraph BEFORE the first chapter heading
- Contains: cover, declaration, abstracts, TOC
- How to detect end: search for first paragraph with style `1` (or Heading1) containing "第1章" or "绪论"
### Zone B (Body content) — REPLACE with user content
- Starts at: first chapter heading ("第1章...")
- Ends at: "参考文献" heading (inclusive) or last body paragraph before acknowledgments
- How to detect:
```python
for i, el in enumerate(body_elements):
text = get_text(el)
style = get_style(el)
if style in ('1', 'Heading1') and ('第1章' in text or '绪论' in text):
zone_b_start = i
if '参考文献' in text:
zone_b_end = i
```
### Zone C (Back matter) — KEEP from template (or remove)
- Starts after: 参考文献
- Contains: 致谢, 附录, final sectPr
## Font Expectations in Chinese Thesis Templates
| Element | Font | Size (字号) | Size (pt) | w:sz |
|---------|------|------------|-----------|------|
| 论文标题 | 华文中宋 or 黑体 | 二号 or 小二 | 22pt or 18pt | 44 or 36 |
| 章标题 (H1) | 黑体 | 三号 | 16pt | 32 |
| 节标题 (H2) | 黑体 | 四号 | 14pt | 28 |
| 小节标题 (H3) | 黑体 | 小四 | 12pt | 24 |
| 正文 | 宋体 | 小四 | 12pt | 24 |
| 页眉 | 宋体 | 五号 | 10.5pt | 21 |
| 页脚/页码 | 宋体 | 五号 | 10.5pt | 21 |
| 表格内容 | 宋体 | 五号 | 10.5pt | 21 |
| 参考文献条目 | 宋体 | 五号 | 10.5pt | 21 |
## RunFonts for CJK Body Text
```xml
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"
w:eastAsia="宋体" w:cs="Times New Roman"/>
```
For headings:
```xml
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"
w:eastAsia="黑体" w:cs="Times New Roman"/>
```
IMPORTANT: When cleaning direct formatting, ALWAYS preserve w:eastAsia.
Removing it causes Chinese text to fall back to the wrong font.
## Common Mistakes with Chinese Templates
1. **Searching for `Heading1`** — Chinese templates use `1`, not `Heading1`
2. **Clearing all rFonts** — Must keep eastAsia font declarations
3. **Assuming "第1章" is the first paragraph** — It's typically paragraph 100+ after cover/abstract/TOC
4. **Ignoring SDT blocks in TOC** — The TOC is wrapped in an SDT, not just field codes
5. **Wrong line spacing** — Chinese theses typically use fixed 20pt (line="400") or 22pt (line="440"), not the 28pt used in government documents
6. **Missing section breaks** — Each zone (abstract, TOC, body) usually has its own sectPr for different headers/footers
## Style Mapping Quick Reference
When source document uses Western IDs and template uses Chinese numeric IDs:
```json
{
"Heading1": "1",
"Heading2": "2",
"Heading3": "3",
"Heading4": "3",
"Normal": "a",
"BodyText": "a",
"ListParagraph": "a",
"Caption": "a",
"TOC1": "11",
"TOC2": "21",
"TOC3": "31"
}
```
When source uses Chinese numeric IDs and template uses Western IDs — reverse the mapping.

View File

@@ -0,0 +1,186 @@
# Color Palette Management
Guidelines for defining, extracting, and enforcing brand colors.
## Color System Structure
### Hierarchy
```
Primary Colors (1-2)
├── Main brand color - Used for CTAs, headers, key elements
└── Supporting primary - Secondary emphasis
Secondary Colors (2-3)
├── Accent colors - Highlights, interactive states
└── Supporting visuals - Icons, illustrations
Neutral Palette (3-5)
├── Background colors - Page, card, modal backgrounds
├── Text colors - Headings, body, muted text
└── UI elements - Borders, dividers, shadows
Semantic Colors (4)
├── Success - #22C55E (green)
├── Warning - #F59E0B (amber)
├── Error - #EF4444 (red)
└── Info - #3B82F6 (blue)
```
## Color Documentation Format
### Markdown Table
```markdown
| Name | Hex | RGB | HSL | Usage |
|------|-----|-----|-----|-------|
| Primary Blue | #2563EB | rgb(37,99,235) | hsl(217,91%,53%) | CTAs, links |
```
### CSS Variables
```css
:root {
/* Primary */
--color-primary: #2563EB;
--color-primary-light: #3B82F6;
--color-primary-dark: #1D4ED8;
/* Secondary */
--color-secondary: #8B5CF6;
--color-accent: #F59E0B;
/* Neutral */
--color-background: #FFFFFF;
--color-surface: #F9FAFB;
--color-text-primary: #111827;
--color-text-secondary: #6B7280;
--color-border: #E5E7EB;
}
```
### Tailwind Config
```javascript
colors: {
primary: {
DEFAULT: '#2563EB',
50: '#EFF6FF',
100: '#DBEAFE',
500: '#3B82F6',
600: '#2563EB',
700: '#1D4ED8',
}
}
```
## Accessibility Requirements
### Contrast Ratios (WCAG 2.1)
| Level | Normal Text | Large Text | UI Components |
|-------|-------------|------------|---------------|
| AA | 4.5:1 | 3:1 | 3:1 |
| AAA | 7:1 | 4.5:1 | 4.5:1 |
### Checking Contrast
```javascript
// Formula for relative luminance
function luminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
}
function contrastRatio(l1, l2) {
const lighter = Math.max(l1, l2);
const darker = Math.min(l1, l2);
return (lighter + 0.05) / (darker + 0.05);
}
```
## Color Extraction
### From Images
Use `extract-colors.cjs` script to:
1. Load image file
2. Extract dominant colors using k-means clustering
3. Map to nearest brand colors
4. Report compliance percentage
### From Brand Guidelines
Parse markdown to extract:
- Hex values from tables
- CSS variable definitions
- Color names and usage descriptions
## Brand Compliance Validation
### Rules
1. **Primary color ratio**: 60-70% of design
2. **Secondary color ratio**: 20-30% of design
3. **Accent color ratio**: 5-10% of design
4. **Off-brand tolerance**: Max 20% non-palette colors
### Validation Output
```json
{
"compliance": 85,
"colors": {
"brand": ["#2563EB", "#8B5CF6", "#FFFFFF"],
"offBrand": ["#FF5500"],
"dominant": "#2563EB"
},
"issues": [
"Off-brand color #FF5500 detected (15% coverage)",
"Primary color underused (45% vs 60% target)"
]
}
```
## Color Usage Guidelines
### Do's
- Use primary for main CTAs and key elements
- Maintain consistent hover/active states
- Test all combinations for accessibility
- Document color decisions
### Don'ts
- Use more than 2-3 colors in single component
- Mix warm and cool tones without intent
- Use pure black (#000) for text (use #111 or similar)
- Rely solely on color for meaning (use icons/text too)
## Color Palette Examples
### Tech/SaaS
```
Primary: #2563EB (Blue)
Secondary: #8B5CF6 (Purple)
Accent: #10B981 (Emerald)
Background: #F9FAFB
Text: #111827
```
### Marketing/Creative
```
Primary: #F97316 (Orange)
Secondary: #EC4899 (Pink)
Accent: #14B8A6 (Teal)
Background: #FFFFFF
Text: #1F2937
```
### Professional/Corporate
```
Primary: #1E40AF (Navy)
Secondary: #475569 (Slate)
Accent: #0EA5E9 (Sky)
Background: #F8FAFC
Text: #0F172A
```
## Tools & Resources
- [Coolors](https://coolors.co) - Palette generation
- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/)
- [Tailwind Color Reference](https://tailwindcss.com/docs/customizing-colors)
- [Color Hunt](https://colorhunt.co) - Curated palettes

View File

@@ -0,0 +1,191 @@
# Comments System Guide (4-File Architecture)
## Overview
Word comments require coordination across **four XML files** plus references in `document.xml`, `[Content_Types].xml`, and `document.xml.rels`.
---
## The Four Comment Files
### 1. `word/comments.xml` — Main Comment Content
Contains the actual comment text:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<w:comment w:id="1" w:author="Alice" w:date="2026-03-21T09:00:00Z" w:initials="A">
<w:p>
<w:pPr><w:pStyle w:val="CommentText" /></w:pPr>
<w:r>
<w:rPr><w:rStyle w:val="CommentReference" /></w:rPr>
<w:annotationRef />
</w:r>
<w:r>
<w:t>This needs clarification.</w:t>
</w:r>
</w:p>
</w:comment>
</w:comments>
```
Key attributes: `w:id` (unique integer), `w:author`, `w:date` (ISO 8601), `w:initials`.
### 2. `word/commentsExtended.xml` — W15 Extensions
Links comments to paragraphs and tracks resolved status:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w15:commentsEx xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml">
<w15:commentEx w15:paraId="1A2B3C4D" w15:done="0" />
</w15:commentsEx>
```
- `w15:paraId` — matches the `w14:paraId` of the comment's paragraph in `comments.xml`
- `w15:done``"0"` = open, `"1"` = resolved
### 3. `word/commentsIds.xml` — Persistent ID Mapping
Provides durable IDs that survive copy/paste across documents:
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w16cid:commentsIds xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid">
<w16cid:commentId w16cid:paraId="1A2B3C4D" w16cid:durableId="12345678" />
</w16cid:commentsIds>
```
- `w16cid:paraId` — same as `w15:paraId`
- `w16cid:durableId` — globally unique identifier (8-digit hex)
### 4. `word/commentsExtensible.xml` — W16 Extensions
Modern comment extensions (used in newer Word versions):
```xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w16cex:commentsExtensible xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex">
<w16cex:commentExtensible w16cex:durableId="12345678" w16cex:dateUtc="2026-03-21T09:00:00Z" />
</w16cex:commentsExtensible>
```
---
## Document.xml References
Comments are anchored in document content using three elements:
```xml
<w:p>
<w:commentRangeStart w:id="1" />
<w:r><w:t>This text has a comment.</w:t></w:r>
<w:commentRangeEnd w:id="1" />
<w:r>
<w:rPr><w:rStyle w:val="CommentReference" /></w:rPr>
<w:commentReference w:id="1" />
</w:r>
</w:p>
```
- `w:commentRangeStart` — marks where the commented text begins
- `w:commentRangeEnd` — marks where the commented text ends
- `w:commentReference` — the visible comment marker (superscript number), placed in a run after the range end
The `w:id` on all three must match the `w:id` in `comments.xml`.
---
## Content Types Registration
Add to `[Content_Types].xml`:
```xml
<Override PartName="/word/comments.xml"
ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" />
<Override PartName="/word/commentsExtended.xml"
ContentType="application/vnd.ms-word.commentsExtended+xml" />
<Override PartName="/word/commentsIds.xml"
ContentType="application/vnd.ms-word.commentsIds+xml" />
<Override PartName="/word/commentsExtensible.xml"
ContentType="application/vnd.ms-word.commentsExtensible+xml" />
```
---
## Relationship Registration
Add to `word/_rels/document.xml.rels`:
```xml
<Relationship Id="rId20" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments"
Target="comments.xml" />
<Relationship Id="rId21" Type="http://schemas.microsoft.com/office/2011/relationships/commentsExtended"
Target="commentsExtended.xml" />
<Relationship Id="rId22" Type="http://schemas.microsoft.com/office/2016/09/relationships/commentsIds"
Target="commentsIds.xml" />
<Relationship Id="rId23" Type="http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible"
Target="commentsExtensible.xml" />
```
---
## Step-by-Step: Adding a New Comment
1. **Choose a unique comment ID** (scan existing `w:id` values, use max + 1)
2. **Generate a paraId** (8-character hex, e.g., `"1A2B3C4D"`) and durableId (8-digit hex)
3. **Add to `comments.xml`**: Create `w:comment` element with content
4. **Add to `commentsExtended.xml`**: Create `w15:commentEx` with `paraId`, `done="0"`
5. **Add to `commentsIds.xml`**: Create `w16cid:commentId` with `paraId` and `durableId`
6. **Add to `commentsExtensible.xml`**: Create `w16cex:commentExtensible` with `durableId` and `dateUtc`
7. **Add to `document.xml`**: Insert `w:commentRangeStart`, `w:commentRangeEnd`, and `w:commentReference` around target text
8. **Verify `[Content_Types].xml`** and `document.xml.rels` have entries for all 4 files
---
## Step-by-Step: Adding a Reply
Replies are comments whose paragraph's `w14:paraId` links to a parent comment:
1. Create a new `w:comment` in `comments.xml` with a new `w:id`
2. In `commentsExtended.xml`, add `w15:commentEx` with:
- `w15:paraId` = new paragraph ID
- `w15:paraIdParent` = the `paraId` of the comment being replied to
- `w15:done="0"`
3. Add entries in `commentsIds.xml` and `commentsExtensible.xml`
4. In `document.xml`, the reply does NOT need its own range markers — it shares the parent's range
```xml
<!-- In commentsExtended.xml -->
<w15:commentEx w15:paraId="5E6F7A8B" w15:paraIdParent="1A2B3C4D" w15:done="0" />
```
---
## Step-by-Step: Resolving a Comment
Set `w15:done="1"` on the comment's `w15:commentEx` entry:
```xml
<!-- Before -->
<w15:commentEx w15:paraId="1A2B3C4D" w15:done="0" />
<!-- After -->
<w15:commentEx w15:paraId="1A2B3C4D" w15:done="1" />
```
This marks the comment (and all its replies) as resolved. The comment remains visible but appears grayed out in Word.
---
## Minimum Viable Comment
At minimum, a working comment requires:
1. `comments.xml` with the `w:comment` element
2. `document.xml` with range markers and reference
3. Relationship in `document.xml.rels`
4. Content type in `[Content_Types].xml`
The extended files (`commentsExtended`, `commentsIds`, `commentsExtensible`) are optional but recommended for full compatibility with modern Word.

View File

@@ -0,0 +1,236 @@
# Component Specifications
Detailed specs for core components with states and variants.
## Button
### Variants
| Variant | Background | Text | Border | Use Case |
|---------|------------|------|--------|----------|
| default | primary | white | none | Primary actions |
| secondary | gray-100 | gray-900 | none | Secondary actions |
| outline | transparent | foreground | border | Tertiary actions |
| ghost | transparent | foreground | none | Subtle actions |
| link | transparent | primary | none | Navigation |
| destructive | red-600 | white | none | Dangerous actions |
### Sizes
| Size | Height | Padding X | Padding Y | Font Size | Icon Size |
|------|--------|-----------|-----------|-----------|-----------|
| sm | 32px | 12px | 6px | 14px | 16px |
| default | 40px | 16px | 8px | 14px | 18px |
| lg | 48px | 24px | 12px | 16px | 20px |
| icon | 40px | 0 | 0 | - | 18px |
### States
| State | Background | Text | Opacity | Cursor |
|-------|------------|------|---------|--------|
| default | token | token | 1 | pointer |
| hover | darker | token | 1 | pointer |
| active | darkest | token | 1 | pointer |
| focus | token | token | 1 | pointer |
| disabled | muted | muted-fg | 0.5 | not-allowed |
| loading | token | token | 0.7 | wait |
### Anatomy
```
┌─────────────────────────────────────┐
│ [icon] Label Text [icon] │
└─────────────────────────────────────┘
↑ ↑
leading icon trailing icon
```
---
## Input
### Variants
| Variant | Description |
|---------|-------------|
| default | Standard text input |
| textarea | Multi-line text |
| select | Dropdown selection |
| checkbox | Boolean toggle |
| radio | Single selection |
| switch | Toggle switch |
### Sizes
| Size | Height | Padding | Font Size |
|------|--------|---------|-----------|
| sm | 32px | 8px 12px | 14px |
| default | 40px | 8px 12px | 14px |
| lg | 48px | 12px 16px | 16px |
### States
| State | Border | Background | Ring |
|-------|--------|------------|------|
| default | gray-300 | white | none |
| hover | gray-400 | white | none |
| focus | primary | white | primary/20% |
| error | red-500 | white | red/20% |
| disabled | gray-200 | gray-100 | none |
### Anatomy
```
Label (optional)
┌─────────────────────────────────────┐
│ [icon] Placeholder/Value [action] │
└─────────────────────────────────────┘
Helper text or error message
```
---
## Card
### Variants
| Variant | Shadow | Border | Use Case |
|---------|--------|--------|----------|
| default | sm | 1px | Standard card |
| elevated | lg | none | Prominent content |
| outline | none | 1px | Subtle container |
| interactive | sm→md | 1px | Clickable card |
### Anatomy
```
┌─────────────────────────────────────┐
│ Card Header │
│ Title │
│ Description │
├─────────────────────────────────────┤
│ Card Content │
│ Main content area │
│ │
├─────────────────────────────────────┤
│ Card Footer │
│ Actions │
└─────────────────────────────────────┘
```
### Spacing
| Area | Padding |
|------|---------|
| header | 24px 24px 0 |
| content | 24px |
| footer | 0 24px 24px |
| gap | 16px |
---
## Badge
### Variants
| Variant | Background | Text |
|---------|------------|------|
| default | primary | white |
| secondary | gray-100 | gray-900 |
| outline | transparent | foreground |
| destructive | red-600 | white |
| success | green-600 | white |
| warning | yellow-500 | gray-900 |
### Sizes
| Size | Padding | Font Size | Height |
|------|---------|-----------|--------|
| sm | 4px 8px | 11px | 20px |
| default | 4px 10px | 12px | 24px |
| lg | 6px 12px | 14px | 28px |
---
## Alert
### Variants
| Variant | Icon | Background | Border |
|---------|------|------------|--------|
| default | info | gray-50 | gray-200 |
| destructive | alert | red-50 | red-200 |
| success | check | green-50 | green-200 |
| warning | warning | yellow-50 | yellow-200 |
### Anatomy
```
┌─────────────────────────────────────┐
│ [icon] Title [×]│
│ Description text │
└─────────────────────────────────────┘
```
---
## Dialog
### Sizes
| Size | Max Width | Use Case |
|------|-----------|----------|
| sm | 384px | Simple confirmations |
| default | 512px | Standard dialogs |
| lg | 640px | Complex forms |
| xl | 768px | Data-heavy dialogs |
| full | 100% - 32px | Full-screen on mobile |
### Anatomy
```
┌───────────────────────────────────────┐
│ Dialog Header [×]│
│ Title │
│ Description │
├───────────────────────────────────────┤
│ Dialog Content │
│ Scrollable if needed │
│ │
├───────────────────────────────────────┤
│ Dialog Footer │
│ [Cancel] [Confirm]│
└───────────────────────────────────────┘
```
---
## Table
### Row States
| State | Background | Use Case |
|-------|------------|----------|
| default | white | Normal row |
| hover | gray-50 | Mouse over |
| selected | primary/10% | Selected row |
| striped | gray-50/white | Alternating |
### Cell Alignment
| Content Type | Alignment |
|--------------|-----------|
| Text | Left |
| Numbers | Right |
| Status/Badge | Center |
| Actions | Right |
### Spacing
| Element | Value |
|---------|-------|
| cell padding | 12px 16px |
| header padding | 12px 16px |
| row height (compact) | 40px |
| row height (default) | 48px |
| row height (comfortable) | 56px |

View File

@@ -0,0 +1,214 @@
# Component Tokens
Component-specific tokens referencing semantic layer.
## Button Tokens
```css
:root {
/* Default (Primary) */
--button-bg: var(--color-primary);
--button-fg: var(--color-primary-foreground);
--button-hover-bg: var(--color-primary-hover);
--button-active-bg: var(--color-primary-active);
/* Secondary */
--button-secondary-bg: var(--color-secondary);
--button-secondary-fg: var(--color-secondary-foreground);
--button-secondary-hover-bg: var(--color-secondary-hover);
/* Outline */
--button-outline-border: var(--color-border);
--button-outline-fg: var(--color-foreground);
--button-outline-hover-bg: var(--color-accent);
/* Ghost */
--button-ghost-fg: var(--color-foreground);
--button-ghost-hover-bg: var(--color-accent);
/* Destructive */
--button-destructive-bg: var(--color-destructive);
--button-destructive-fg: var(--color-destructive-foreground);
--button-destructive-hover-bg: var(--color-destructive-hover);
/* Sizing */
--button-padding-x: var(--space-4);
--button-padding-y: var(--space-2);
--button-padding-x-sm: var(--space-3);
--button-padding-y-sm: var(--space-1-5);
--button-padding-x-lg: var(--space-6);
--button-padding-y-lg: var(--space-3);
/* Shape */
--button-radius: var(--radius-md);
--button-font-size: var(--font-size-sm);
--button-font-weight: var(--font-weight-medium);
}
```
## Input Tokens
```css
:root {
/* Background & Border */
--input-bg: var(--color-background);
--input-border: var(--color-input);
--input-fg: var(--color-foreground);
/* Placeholder */
--input-placeholder: var(--color-muted-foreground);
/* Focus */
--input-focus-border: var(--color-ring);
--input-focus-ring: var(--color-ring);
/* Error */
--input-error-border: var(--color-error);
--input-error-fg: var(--color-error);
/* Disabled */
--input-disabled-bg: var(--color-muted);
--input-disabled-fg: var(--color-muted-foreground);
/* Sizing */
--input-padding-x: var(--space-3);
--input-padding-y: var(--space-2);
--input-radius: var(--radius-md);
--input-font-size: var(--font-size-sm);
}
```
## Card Tokens
```css
:root {
/* Background & Border */
--card-bg: var(--color-card);
--card-fg: var(--color-card-foreground);
--card-border: var(--color-border);
/* Shadow */
--card-shadow: var(--shadow-default);
--card-shadow-hover: var(--shadow-md);
/* Spacing */
--card-padding: var(--space-6);
--card-padding-sm: var(--space-4);
--card-gap: var(--space-4);
/* Shape */
--card-radius: var(--radius-lg);
}
```
## Badge Tokens
```css
:root {
/* Default */
--badge-bg: var(--color-primary);
--badge-fg: var(--color-primary-foreground);
/* Secondary */
--badge-secondary-bg: var(--color-secondary);
--badge-secondary-fg: var(--color-secondary-foreground);
/* Outline */
--badge-outline-border: var(--color-border);
--badge-outline-fg: var(--color-foreground);
/* Destructive */
--badge-destructive-bg: var(--color-destructive);
--badge-destructive-fg: var(--color-destructive-foreground);
/* Sizing */
--badge-padding-x: var(--space-2-5);
--badge-padding-y: var(--space-0-5);
--badge-radius: var(--radius-full);
--badge-font-size: var(--font-size-xs);
}
```
## Alert Tokens
```css
:root {
/* Default */
--alert-bg: var(--color-background);
--alert-fg: var(--color-foreground);
--alert-border: var(--color-border);
/* Destructive */
--alert-destructive-bg: var(--color-destructive);
--alert-destructive-fg: var(--color-destructive-foreground);
/* Spacing */
--alert-padding: var(--space-4);
--alert-radius: var(--radius-lg);
}
```
## Dialog/Modal Tokens
```css
:root {
/* Overlay */
--dialog-overlay-bg: rgb(0 0 0 / 0.5);
/* Content */
--dialog-bg: var(--color-background);
--dialog-fg: var(--color-foreground);
--dialog-border: var(--color-border);
--dialog-shadow: var(--shadow-lg);
/* Spacing */
--dialog-padding: var(--space-6);
--dialog-radius: var(--radius-lg);
--dialog-max-width: 32rem;
}
```
## Table Tokens
```css
:root {
/* Header */
--table-header-bg: var(--color-muted);
--table-header-fg: var(--color-muted-foreground);
/* Body */
--table-row-bg: var(--color-background);
--table-row-hover-bg: var(--color-muted);
--table-row-fg: var(--color-foreground);
/* Border */
--table-border: var(--color-border);
/* Spacing */
--table-cell-padding-x: var(--space-4);
--table-cell-padding-y: var(--space-3);
}
```
## Usage Example
```css
.button {
background: var(--button-bg);
color: var(--button-fg);
padding: var(--button-padding-y) var(--button-padding-x);
border-radius: var(--button-radius);
font-size: var(--button-font-size);
font-weight: var(--button-font-weight);
transition: background var(--duration-fast);
}
.button:hover {
background: var(--button-hover-bg);
}
.button.secondary {
background: var(--button-secondary-bg);
color: var(--button-secondary-fg);
}
```

View File

@@ -0,0 +1,94 @@
# Brand Consistency Checklist
## Visual Consistency
### Logo
- [ ] Correct logo version used
- [ ] Proper clear space maintained
- [ ] Approved colors only
- [ ] Legible at all sizes
- [ ] No unauthorized modifications
### Colors
- [ ] Only brand palette colors
- [ ] Consistent color application
- [ ] Proper contrast for accessibility
- [ ] Color ratios maintained
### Typography
- [ ] Brand fonts used
- [ ] Correct weights/styles
- [ ] Proper hierarchy
- [ ] Consistent formatting
### Imagery
- [ ] Matches brand style
- [ ] Consistent editing/filters
- [ ] Appropriate subjects
- [ ] Quality standards met
## Voice Consistency
### Tone
- [ ] Matches brand personality
- [ ] Appropriate for context
- [ ] Consistent across channels
- [ ] No conflicting messages
### Language
- [ ] Brand terminology used
- [ ] Consistent capitalization
- [ ] Proper abbreviations
- [ ] Jargon level appropriate
### Messaging
- [ ] Aligns with key messages
- [ ] Value prop clear
- [ ] Differentiators highlighted
- [ ] CTAs consistent
## Channel Audit
### Website
- [ ] Homepage
- [ ] Product pages
- [ ] Blog/content
- [ ] Footer/navigation
### Social Media
- [ ] Profile images
- [ ] Cover images
- [ ] Bio/about sections
- [ ] Post templates
### Email
- [ ] Header/footer
- [ ] Templates
- [ ] Signatures
- [ ] Automated messages
### Collateral
- [ ] Presentations
- [ ] One-pagers
- [ ] Business cards
- [ ] Promotional materials
## Common Issues
| Issue | Fix |
|-------|-----|
| Outdated logo | Replace with current version |
| Off-brand colors | Update to palette |
| Wrong font | Replace with brand font |
| Inconsistent voice | Apply style guide |
| Mixed messaging | Align to framework |
## Audit Frequency
| Asset Type | Frequency |
|------------|-----------|
| Website | Monthly |
| Social profiles | Quarterly |
| Email templates | Quarterly |
| Sales materials | Quarterly |
| Full brand audit | Annually |

View File

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

View File

@@ -0,0 +1,84 @@
# Copywriting Formulas
25 formulas for persuasive slide copy.
## Core Formulas
### PAS (Problem-Agitate-Solution)
**Use:** Problem slides, pain points
**Components:** Problem → Agitate → Solution
**Template:** "[Pain point]? Every [time frame], [consequence]. [Solution] fixes this."
### AIDA (Attention-Interest-Desire-Action)
**Use:** CTAs, closing slides
**Components:** Attention → Interest → Desire → Action
**Template:** "[Bold statement]. [Benefit detail]. [Social proof]. [CTA]."
### FAB (Features-Advantages-Benefits)
**Use:** Feature slides, product showcases
**Components:** Feature → Advantage → Benefit
**Template:** "[Feature] lets you [advantage], so you can [benefit]."
### Cost of Inaction
**Use:** Agitation slides, urgency
**Components:** Status Quo → Loss → Time Decay
**Template:** "Without [solution], you're losing [amount] every [timeframe]."
### Before-After-Bridge
**Use:** Transformation slides, case studies
**Components:** Before → After → Bridge
**Template:** "[Pain point before]. [Desired state after]. [Your solution] is the bridge."
## Formula-to-Slide Mapping
| Slide Type | Primary Formula | Emotion |
|------------|-----------------|---------|
| Title/Hook | AIDA, Hook | curiosity |
| Problem | PAS, Agitate | frustration |
| Cost/Risk | Cost of Inaction | fear |
| Solution | FAB, BAB | hope |
| Features | FAB | confidence |
| Traction | Proof Stack | trust |
| Social Proof | Testimonial | trust |
| Pricing | Value Stack | confidence |
| CTA | AIDA, Urgency | urgency |
## Headline Patterns
### Power Words
- "Stop [bad thing]"
- "Get [desired result] in [timeframe]"
- "The [adjective] way to [action]"
- "Why [audience] choose [product]"
- "[Number] ways to [achieve goal]"
### Contrast Patterns
- "[Old way] is dead. Meet [new way]."
- "Don't [bad action]. Instead, [good action]."
- "From [pain point] to [benefit]."
### Social Proof Patterns
- "[Number]+ [users/companies] trust [product]"
- "Join [notable company] and [notable company]"
- "As seen in [publication]"
## Search Commands
```bash
# Find formula for slide type
python .claude/skills/design-system/scripts/search-slides.py "problem agitation" -d copy
# Get emotion-appropriate formula
python .claude/skills/design-system/scripts/search-slides.py "urgency cta" -d copy
```
## Quick Reference
| Need | Use Formula |
|------|------------|
| Create urgency | Cost of Inaction, Scarcity |
| Build trust | Social Proof, Testimonial |
| Show value | FAB, Value Stack |
| Drive action | AIDA, CTA |
| Tell story | BAB, Story Arc |
| Present data | Proof Stack |

View File

@@ -0,0 +1,4 @@
Invoke `slides` skill to create persuasive HTML slides using design tokens, Chart.js, and the slide knowledge database.
## Task
<task>$ARGUMENTS</task>

View File

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

View File

@@ -0,0 +1,706 @@
---
name: fullstack-dev-db-schema
description: "Database schema design and migrations. Use when creating tables, defining ORM models, adding indexes, or designing relationships. Covers zero-downtime migrations and multi-tenancy."
license: MIT
metadata:
version: "1.0.0"
sources:
- PostgreSQL official documentation
- Use The Index, Luke (use-the-index-luke.com)
- Designing Data-Intensive Applications (Martin Kleppmann)
- Database Reliability Engineering (Laine Campbell & Charity Majors)
---
# Database Schema Design
ORM-agnostic guide for relational database schema design. Covers data modeling, normalization, indexing, migrations, multi-tenancy, and common application patterns. Primarily PostgreSQL-focused but principles apply to MySQL/MariaDB.
## Scope
**USE this skill when:**
- Designing a schema for a new project or feature
- Deciding between normalization and denormalization
- Choosing which indexes to create
- Planning a zero-downtime migration on a live database
- Implementing multi-tenant data isolation
- Adding audit trails, soft delete, or versioning
- Diagnosing slow queries caused by schema problems
**NOT for:**
- Choosing which database technology to use (→ `technology-selection`)
- PostgreSQL-specific query tuning (use PostgreSQL performance docs)
- ORM-specific configuration (→ `django-best-practices` or your ORM's docs)
- Application-layer caching (→ `fullstack-dev-practices`)
## Context Required
| Required | Optional |
|----------|----------|
| Database engine (PostgreSQL / MySQL) | Expected data volume (rows, growth rate) |
| Domain entities and relationships | Read/write ratio |
| Key access patterns (queries) | Multi-tenant requirements |
---
## Quick Start Checklist
Designing a new schema:
- [ ] **Domain entities identified** — map 1 entity = 1 table (not 1 class = 1 table)
- [ ] **Primary keys**: UUID for public IDs, serial/bigserial for internal-only
- [ ] **Foreign keys** with explicit `ON DELETE` behavior
- [ ] **NOT NULL** by default — nullable only when business logic requires it
- [ ] **Timestamps**: `created_at` + `updated_at` on every table
- [ ] **Indexes** created for every WHERE, JOIN, ORDER BY column
- [ ] **No premature denormalization** — start normalized, denormalize when measured
- [ ] **Naming convention** consistent: `snake_case`, plural table names
---
## Quick Navigation
| Need to… | Jump to |
|----------|---------|
| Model entities and relationships | [1. Data Modeling](#1-data-modeling-critical) |
| Decide normalize vs denormalize | [2. Normalization](#2-normalization-vs-denormalization-critical) |
| Choose the right index | [3. Indexing](#3-indexing-strategy-critical) |
| Run migrations safely on live DB | [4. Migrations](#4-zero-downtime-migrations-high) |
| Design multi-tenant schema | [5. Multi-Tenancy](#5-multi-tenant-design-high) |
| Add soft delete / audit trails | [6. Common Patterns](#6-common-schema-patterns-medium) |
| Partition large tables | [7. Partitioning](#7-table-partitioning-medium) |
| See anti-patterns | [Anti-Patterns](#anti-patterns) |
---
## Core Principles (7 Rules)
```
1. ✅ Start normalized (3NF) — denormalize only when you have measured evidence
2. ✅ Every table has a primary key, created_at, updated_at
3. ✅ UUID for public-facing IDs, serial for internal join keys
4. ✅ NOT NULL by default — null is a business decision, not a lazy default
5. ✅ Index every column used in WHERE, JOIN, ORDER BY
6. ✅ Foreign keys enforced in database (not just application code)
7. ✅ Migrations are additive — never drop/rename in production without a multi-step plan
```
---
## 1. Data Modeling (CRITICAL)
### Table Naming
```sql
-- ✅ Plural, snake_case
CREATE TABLE orders (...);
CREATE TABLE order_items (...);
CREATE TABLE user_profiles (...);
-- ❌ Singular, mixed case
CREATE TABLE Order (...);
CREATE TABLE OrderItem (...);
CREATE TABLE tbl_usr_prof (...); -- cryptic abbreviation
```
### Primary Keys
| Strategy | When | Pros | Cons |
|----------|------|------|------|
| `bigserial` (auto-increment) | Internal tables, FK joins | Compact, fast joins | Enumerable, not safe for public IDs |
| `uuid` (v4 random) | Public-facing resources | Non-guessable, globally unique | Larger (16 bytes), random I/O on B-Tree |
| `uuid` v7 (time-sorted) | Public + needs ordering | Non-guessable + insert-friendly | Newer, less ecosystem support |
| `text` slug | URL-friendly resources | Human-readable | Must enforce uniqueness, updates expensive |
**Recommended default:**
```sql
CREATE TABLE orders (
id bigserial PRIMARY KEY, -- internal FK target
public_id uuid NOT NULL DEFAULT gen_random_uuid() UNIQUE, -- API-facing
-- ...
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
```
### Relationships
```sql
-- One-to-Many: user → orders
CREATE TABLE orders (
id bigserial PRIMARY KEY,
user_id bigint NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- ...
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
-- Many-to-Many: orders ↔ products (via junction table)
CREATE TABLE order_items (
id bigserial PRIMARY KEY,
order_id bigint NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
product_id bigint NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
quantity int NOT NULL CHECK (quantity > 0),
unit_price numeric(10,2) NOT NULL,
UNIQUE (order_id, product_id) -- prevent duplicate line items
);
-- One-to-One: user → profile
CREATE TABLE user_profiles (
user_id bigint PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
bio text,
avatar_url text,
-- ...
);
```
### ON DELETE Behavior
| Behavior | When | Example |
|----------|------|---------|
| `CASCADE` | Child meaningless without parent | order_items when order deleted |
| `RESTRICT` | Prevent accidental deletion | products referenced by order_items |
| `SET NULL` | Preserve child, clear reference | orders.assigned_to when employee leaves |
| `SET DEFAULT` | Fallback to default value | Rare, for status columns |
---
## 2. Normalization vs Denormalization (CRITICAL)
### Start Normalized (3NF)
**Normal forms in practice:**
| Form | Rule | Example Violation |
|------|------|-------------------|
| 1NF | No repeating groups, atomic values | `tags = "go,python,rust"` in one column |
| 2NF | No partial dependencies (composite keys) | `order_items.product_name` depends on `product_id` alone |
| 3NF | No transitive dependencies | `orders.customer_city` depends on `customer_id`, not `order_id` |
**1NF violation fix:**
```sql
-- ❌ Tags as comma-separated string
CREATE TABLE posts (id serial, tags text); -- tags = "go,python"
-- ✅ Separate table (or array/JSONB if simple)
CREATE TABLE post_tags (
post_id bigint REFERENCES posts(id) ON DELETE CASCADE,
tag_id bigint REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
-- ✅ Alternative: PostgreSQL array (if tags are just strings, no metadata)
CREATE TABLE posts (id serial, tags text[] NOT NULL DEFAULT '{}');
CREATE INDEX idx_posts_tags ON posts USING GIN(tags);
```
### When to Denormalize
**Denormalize ONLY when:**
1. You have **measured** a performance problem (EXPLAIN ANALYZE, not "I think it's slow")
2. The denormalized data is **read-heavy** (read:write ratio > 100:1)
3. You accept the **consistency maintenance cost** (triggers, application logic, or materialized views)
**Safe denormalization patterns:**
```sql
-- Pattern 1: Materialized view (computed, refreshable)
CREATE MATERIALIZED VIEW order_summary AS
SELECT o.id, o.user_id, o.total,
COUNT(oi.id) AS item_count,
u.email AS user_email
FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN users u ON u.id = o.user_id
GROUP BY o.id, u.email;
REFRESH MATERIALIZED VIEW CONCURRENTLY order_summary; -- non-blocking
-- Pattern 2: Cached aggregate column (application-maintained)
ALTER TABLE orders ADD COLUMN item_count int NOT NULL DEFAULT 0;
-- Update via trigger or application code on order_item insert/delete
-- Pattern 3: JSONB snapshot (freeze-at-write-time)
-- Store a copy of the product details at the time of purchase
CREATE TABLE order_items (
id bigserial PRIMARY KEY,
order_id bigint NOT NULL REFERENCES orders(id),
product_id bigint REFERENCES products(id),
quantity int NOT NULL,
unit_price numeric(10,2) NOT NULL, -- frozen price
product_snapshot jsonb NOT NULL -- frozen name, description, image
);
```
---
## 3. Indexing Strategy (CRITICAL)
### Index Types (PostgreSQL)
| Type | When | Example |
|------|------|---------|
| **B-Tree** (default) | Equality, range, ORDER BY | `WHERE status = 'active'`, `WHERE created_at > '2025-01-01'` |
| **Hash** | Equality only (rare, B-Tree usually better) | `WHERE id = 123` (large tables, Postgres 10+) |
| **GIN** | Arrays, JSONB, full-text search | `WHERE tags @> '{go}'`, `WHERE data->>'key' = 'val'` |
| **GiST** | Geometry, ranges, nearest-neighbor | PostGIS, tsrange, ltree |
| **BRIN** | Very large tables with natural ordering | Time-series data sorted by timestamp |
### Index Decision Rules
```
Rule 1: Index every column in WHERE clauses
Rule 2: Index every column used in JOIN ON conditions
Rule 3: Index every column in ORDER BY (if queried with LIMIT)
Rule 4: Composite index for multi-column WHERE (leftmost prefix rule)
Rule 5: Partial index when filtering a subset (e.g., only active records)
Rule 6: Covering index (INCLUDE) to avoid table lookup
Rule 7: DON'T index low-cardinality columns alone (e.g., boolean)
```
### Composite Index: Column Order Matters
```sql
-- Query: WHERE user_id = ? AND status = ? ORDER BY created_at DESC
-- ✅ Optimal: matches query pattern left-to-right
CREATE INDEX idx_orders_user_status_created
ON orders(user_id, status, created_at DESC);
-- ❌ Wrong order: can't use for this query efficiently
CREATE INDEX idx_orders_created_user_status
ON orders(created_at DESC, user_id, status);
```
**Leftmost prefix rule:** Index on `(A, B, C)` supports queries on `(A)`, `(A, B)`, `(A, B, C)` but NOT `(B)`, `(C)`, or `(B, C)`.
### Partial Index (Index Only What Matters)
```sql
-- Only 5% of orders are 'pending', but queried frequently
CREATE INDEX idx_orders_pending
ON orders(created_at DESC)
WHERE status = 'pending';
-- Only active users matter for login
CREATE INDEX idx_users_active_email
ON users(email)
WHERE is_active = true;
```
### Covering Index (Avoid Table Lookup)
```sql
-- Query only needs id and status, no need to read the table row
CREATE INDEX idx_orders_user_covering
ON orders(user_id) INCLUDE (status, total);
-- Now this query is index-only:
SELECT status, total FROM orders WHERE user_id = 123;
```
### When NOT to Index
```
❌ Columns rarely used in WHERE/JOIN/ORDER BY
❌ Tables with < 1,000 rows (sequential scan is faster)
❌ Columns with very low cardinality alone (e.g., boolean is_active)
❌ Write-heavy tables where index maintenance cost > read benefit
❌ Duplicate indexes (check pg_stat_user_indexes for unused indexes)
```
---
## 4. Zero-Downtime Migrations (HIGH)
### The Golden Rule
```
NEVER make destructive changes in one step.
Always: ADD → MIGRATE DATA → REMOVE OLD (in separate deploys).
```
### Safe Migration Patterns
**Rename a column (3 deploys):**
```
Deploy 1: Add new column
ALTER TABLE users ADD COLUMN full_name text;
UPDATE users SET full_name = name; -- backfill
-- App writes to BOTH name and full_name
Deploy 2: Switch reads to new column
-- App reads from full_name, still writes to both
Deploy 3: Drop old column
ALTER TABLE users DROP COLUMN name;
-- App only uses full_name
```
**Add a NOT NULL column (2 deploys):**
```sql
-- Deploy 1: Add nullable column, backfill
ALTER TABLE orders ADD COLUMN currency text; -- nullable first
UPDATE orders SET currency = 'USD' WHERE currency IS NULL; -- backfill
-- Deploy 2: Add constraint (after all rows backfilled)
ALTER TABLE orders ALTER COLUMN currency SET NOT NULL;
ALTER TABLE orders ALTER COLUMN currency SET DEFAULT 'USD';
```
**Add an index without locking:**
```sql
-- ✅ CONCURRENTLY: no table lock, can run on live DB
CREATE INDEX CONCURRENTLY idx_orders_status ON orders(status);
-- ❌ Without CONCURRENTLY: locks table for writes during build
CREATE INDEX idx_orders_status ON orders(status);
```
### Migration Safety Checklist
```
✅ Migration runs in < 30 seconds on production data size
✅ No exclusive table locks (use CONCURRENTLY for indexes)
✅ Rollback plan documented and tested
✅ Backfill runs in batches (not one giant UPDATE)
✅ New column added as nullable first, constraint added later
✅ Old column kept until all code references removed
❌ Never rename/drop columns in one deploy
❌ Never ALTER TYPE on large tables without testing timing
❌ Never run data backfill in a transaction (OOM on large tables)
```
### Batch Backfill Template
```sql
-- Backfill in batches of 10,000 (avoids long-running transactions)
DO $$
DECLARE
batch_size int := 10000;
affected int;
BEGIN
LOOP
UPDATE orders
SET currency = 'USD'
WHERE id IN (
SELECT id FROM orders WHERE currency IS NULL LIMIT batch_size
);
GET DIAGNOSTICS affected = ROW_COUNT;
RAISE NOTICE 'Updated % rows', affected;
EXIT WHEN affected = 0;
PERFORM pg_sleep(0.1); -- brief pause to reduce load
END LOOP;
END $$;
```
---
## 5. Multi-Tenant Design (HIGH)
### Three Approaches
| Approach | Isolation | Complexity | When |
|----------|-----------|------------|------|
| **Row-level** (shared tables + `tenant_id`) | Low | Low | SaaS MVP, < 1,000 tenants |
| **Schema-per-tenant** | Medium | Medium | Regulated industries, moderate scale |
| **Database-per-tenant** | High | High | Enterprise, strict data isolation |
### Row-Level Tenancy (Most Common)
```sql
-- Every table has tenant_id
CREATE TABLE orders (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL REFERENCES tenants(id),
user_id bigint NOT NULL REFERENCES users(id),
total numeric(10,2) NOT NULL,
-- ...
);
-- Composite index: tenant first (most queries filter by tenant)
CREATE INDEX idx_orders_tenant_user ON orders(tenant_id, user_id);
CREATE INDEX idx_orders_tenant_status ON orders(tenant_id, status);
-- Row-Level Security (PostgreSQL)
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.tenant_id')::bigint);
```
**Application-level enforcement:**
```typescript
// Middleware: set tenant context on every request
app.use((req, res, next) => {
const tenantId = req.headers['x-tenant-id'];
if (!tenantId) return res.status(400).json({ error: 'Missing tenant' });
req.tenantId = tenantId;
next();
});
// Repository: ALWAYS filter by tenant
async findOrders(tenantId: string, userId: string) {
return db.order.findMany({
where: { tenantId, userId }, // ← tenant_id in EVERY query
});
}
```
### Rules
```
✅ tenant_id in EVERY table that holds tenant data
✅ tenant_id as FIRST column in every composite index
✅ Application middleware enforces tenant context
✅ Use RLS (PostgreSQL) as defense-in-depth, not sole protection
✅ Test with 2+ tenants to verify isolation
❌ Never allow cross-tenant queries in application code
❌ Never skip tenant_id in WHERE clauses (even in admin tools)
```
---
## 6. Common Schema Patterns (MEDIUM)
### Soft Delete
```sql
ALTER TABLE orders ADD COLUMN deleted_at timestamptz;
-- All queries filter deleted records
CREATE VIEW active_orders AS
SELECT * FROM orders WHERE deleted_at IS NULL;
-- Partial index: only index non-deleted rows
CREATE INDEX idx_orders_active_status
ON orders(status, created_at DESC)
WHERE deleted_at IS NULL;
```
**ORM integration:**
```typescript
// Prisma middleware: auto-filter soft-deleted records
prisma.$use(async (params, next) => {
if (params.action === 'findMany' || params.action === 'findFirst') {
params.args.where = { ...params.args.where, deletedAt: null };
}
return next(params);
});
```
### Audit Trail
```sql
-- Option A: Audit columns on every table
ALTER TABLE orders ADD COLUMN created_by bigint REFERENCES users(id);
ALTER TABLE orders ADD COLUMN updated_by bigint REFERENCES users(id);
-- Option B: Separate audit log table (more detail)
CREATE TABLE audit_log (
id bigserial PRIMARY KEY,
table_name text NOT NULL,
record_id bigint NOT NULL,
action text NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')),
old_data jsonb,
new_data jsonb,
changed_by bigint REFERENCES users(id),
changed_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX idx_audit_table_record ON audit_log(table_name, record_id);
CREATE INDEX idx_audit_changed_at ON audit_log(changed_at DESC);
```
### Enum Columns
```sql
-- Option A: PostgreSQL enum type (strict, but ALTER TYPE is painful)
CREATE TYPE order_status AS ENUM ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled');
ALTER TABLE orders ADD COLUMN status order_status NOT NULL DEFAULT 'pending';
-- Option B: Text + CHECK constraint (easier to migrate)
ALTER TABLE orders ADD COLUMN status text NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled'));
-- Option C: Lookup table (most flexible, best for UI-driven lists)
CREATE TABLE order_statuses (
id serial PRIMARY KEY,
name text UNIQUE NOT NULL,
label text NOT NULL -- display name
);
```
**Recommendation:** Option B (text + CHECK) for most cases. Option C if statuses are managed by non-developers.
### Polymorphic Associations
```sql
-- ❌ Anti-pattern: polymorphic FK (no referential integrity)
CREATE TABLE comments (
id bigserial PRIMARY KEY,
commentable_type text, -- 'Post' or 'Photo'
commentable_id bigint, -- no FK constraint possible!
body text
);
-- ✅ Pattern A: Separate FK columns (nullable)
CREATE TABLE comments (
id bigserial PRIMARY KEY,
post_id bigint REFERENCES posts(id) ON DELETE CASCADE,
photo_id bigint REFERENCES photos(id) ON DELETE CASCADE,
body text NOT NULL,
CHECK (
(post_id IS NOT NULL AND photo_id IS NULL) OR
(post_id IS NULL AND photo_id IS NOT NULL)
)
);
-- ✅ Pattern B: Separate tables (cleanest, best for different schemas)
CREATE TABLE post_comments (..., post_id bigint REFERENCES posts(id));
CREATE TABLE photo_comments (..., photo_id bigint REFERENCES photos(id));
```
### JSONB Columns (Semi-Structured Data)
```sql
-- Good uses: metadata, settings, flexible attributes
CREATE TABLE products (
id bigserial PRIMARY KEY,
name text NOT NULL,
price numeric(10,2) NOT NULL,
attributes jsonb NOT NULL DEFAULT '{}' -- color, size, weight...
);
-- Index for JSONB queries
CREATE INDEX idx_products_attrs ON products USING GIN(attributes);
-- Query
SELECT * FROM products WHERE attributes->>'color' = 'red';
SELECT * FROM products WHERE attributes @> '{"size": "XL"}';
```
```
✅ Use JSONB for truly flexible/optional data (metadata, settings, preferences)
✅ Index JSONB columns with GIN when queried
❌ Never use JSONB for data that should be columns (email, status, price)
❌ Never use JSONB to avoid schema design (it's not MongoDB-in-Postgres)
```
---
## 7. Table Partitioning (MEDIUM)
### When to Partition
```
✅ Table > 100M rows AND growing
✅ Most queries filter on the partition key (date range, tenant)
✅ Old data can be dropped/archived by partition (efficient DELETE)
❌ Table < 10M rows (overhead not worth it)
❌ Queries don't filter on partition key (scans all partitions)
```
### Range Partitioning (Time-Series)
```sql
CREATE TABLE events (
id bigserial,
tenant_id bigint NOT NULL,
event_type text NOT NULL,
payload jsonb,
created_at timestamptz NOT NULL DEFAULT now()
) PARTITION BY RANGE (created_at);
-- Monthly partitions
CREATE TABLE events_2025_01 PARTITION OF events
FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');
CREATE TABLE events_2025_02 PARTITION OF events
FOR VALUES FROM ('2025-02-01') TO ('2025-03-01');
-- Automate partition creation with pg_partman or cron
```
### List Partitioning (Multi-Tenant)
```sql
CREATE TABLE orders (
id bigserial,
tenant_id bigint NOT NULL,
total numeric(10,2)
) PARTITION BY LIST (tenant_id);
CREATE TABLE orders_tenant_1 PARTITION OF orders FOR VALUES IN (1);
CREATE TABLE orders_tenant_2 PARTITION OF orders FOR VALUES IN (2);
```
---
## Anti-Patterns
| # | ❌ Don't | ✅ Do Instead |
|---|---------|--------------|
| 1 | Premature denormalization | Start 3NF, denormalize when measured |
| 2 | Auto-increment IDs as public API identifiers | UUID for public, serial for internal |
| 3 | No foreign key constraints | FK enforced in database, always |
| 4 | Nullable by default | NOT NULL by default, nullable when required |
| 5 | No indexes on FK columns | Index every FK column |
| 6 | Single-step destructive migration | ADD → MIGRATE → REMOVE in separate deploys |
| 7 | `CREATE INDEX` without `CONCURRENTLY` | Always `CONCURRENTLY` on live tables |
| 8 | Polymorphic FK (`commentable_type + commentable_id`) | Separate FK columns or separate tables |
| 9 | JSONB for everything | JSONB for flexible data only, columns for structured |
| 10 | No `created_at` / `updated_at` | Timestamp pair on every table |
| 11 | Comma-separated values in one column | Separate table or PostgreSQL array |
| 12 | `text` without length validation | CHECK constraint or application validation |
---
## Common Issues
### Issue 1: "Query is slow but I already have an index"
**Symptom:** `EXPLAIN ANALYZE` shows Sequential Scan despite existing index.
**Causes:**
1. **Wrong index column order** — composite index `(A, B)` won't help `WHERE B = ?`
2. **Low selectivity** — index on boolean column (50% of rows match), planner prefers seq scan
3. **Stale statistics** — run `ANALYZE table_name;`
4. **Type mismatch** — comparing `varchar` column with `integer` parameter → no index use
**Fix:** Check `EXPLAIN (ANALYZE, BUFFERS)`, verify index matches query pattern, run `ANALYZE`.
### Issue 2: "Migration locks the table for minutes"
**Symptom:** `ALTER TABLE` blocks all writes during execution.
**Cause:** Adding NOT NULL constraint, changing column type, or creating index without `CONCURRENTLY`.
**Fix:**
```sql
-- Add index without lock
CREATE INDEX CONCURRENTLY idx_name ON table(col);
-- Add NOT NULL constraint without lock (Postgres 12+)
ALTER TABLE t ADD CONSTRAINT t_col_nn CHECK (col IS NOT NULL) NOT VALID;
ALTER TABLE t VALIDATE CONSTRAINT t_col_nn; -- non-blocking validation
```
### Issue 3: "How many indexes is too many?"
**Rule of thumb:**
- Read-heavy table (reports, product catalog): 5-10 indexes is fine
- Write-heavy table (events, logs): 2-3 indexes max
- Monitor with `pg_stat_user_indexes` — drop indexes with `idx_scan = 0`
```sql
-- Find unused indexes
SELECT schemaname, relname, indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0 AND indexrelname NOT LIKE '%pkey%'
ORDER BY pg_relation_size(indexrelid) DESC;
```

View File

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

View File

@@ -0,0 +1,207 @@
# Design Routing Guide
When to use each design sub-skill.
## Skill Overview
| Skill | Purpose | Key Files |
|-------|---------|-----------|
| brand | Brand identity, voice, assets | SKILL.md + 10 references + 3 scripts |
| design-system | Token architecture, specs | SKILL.md + 7 references + 2 scripts |
| ui-styling | Component implementation | SKILL.md + 7 references + 2 scripts |
| logo-design | AI logo generation (55 styles, 30 palettes) | SKILL.md + 4 references + 2 scripts |
| cip-design | Corporate Identity Program (50 deliverables) | SKILL.md + 3 references + 3 scripts |
| slides | HTML presentations with Chart.js | SKILL.md + 4 references |
| banner-design | Banners for social, ads, web, print (22 styles) | SKILL.md + 1 reference |
| icon-design | SVG icon generation (15 styles, Gemini 3.1 Pro) | SKILL.md + 1 reference + 1 script |
## Routing by Task Type
### Brand Identity Tasks
**→ brand**
- Define brand colors and typography
- Create logo usage guidelines
- Establish brand voice and tone
- Organize and validate assets
- Create messaging frameworks
- Audit brand consistency
### Token System Tasks
**→ design-system**
- Create design tokens JSON
- Generate CSS variables
- Define component specifications
- Map tokens to Tailwind config
- Validate token usage in code
- Document state and variants
### Implementation Tasks
**→ ui-styling**
- Add shadcn/ui components
- Style with Tailwind classes
- Implement dark mode
- Create responsive layouts
- Build accessible components
### Logo Design Tasks
**→ logo-design**
- Create logos with AI (Gemini Nano Banana)
- Search logo styles, color palettes, industry guidelines
- Generate design briefs
- Explore 55+ styles (minimalist, vintage, luxury, geometric, etc.)
### Corporate Identity Program Tasks
**→ cip-design**
- Generate CIP deliverables (business cards, letterheads, signage, vehicles, apparel)
- Create CIP briefs with industry/style analysis
- Generate mockups with/without logo (Gemini Flash/Pro)
- Render HTML presentations from CIP mockups
### Presentation Tasks
**→ slides**
- Create strategic HTML presentations
- Data visualization with Chart.js
- Apply copywriting formulas to slide content
- Use layout patterns and design tokens
### Banner Design Tasks
**→ banner-design**
- Design banners for social media (Facebook, Twitter, LinkedIn, YouTube, Instagram)
- Create ad banners (Google Ads, Meta Ads)
- Website hero banners and headers
- Print banners and covers
- 22 art direction styles (minimalist, bold typography, gradient, glassmorphism, etc.)
### Icon Design Tasks
**→ icon-design**
- Generate SVG icons with AI (Gemini 3.1 Pro Preview)
- Batch icon variations in multiple styles
- Multi-size export (16px, 24px, 32px, 48px)
- 15 styles: outlined, filled, duotone, rounded, sharp, gradient, etc.
- 12 categories: navigation, action, communication, media, commerce, data
## Routing by Question Type
| Question | Skill |
|----------|-------|
| "What color should this be?" | brand |
| "How do I create a token for X?" | design-system |
| "How do I build a button component?" | ui-styling |
| "Is this on-brand?" | brand |
| "Should I use a CSS variable here?" | design-system |
| "How do I add dark mode?" | ui-styling |
| "Create a logo for my brand" | logo-design |
| "Generate business card mockups" | cip-design |
| "Create a pitch deck" | slides |
| "Design brand identity package" | cip-design |
| "What logo style fits my industry?" | logo-design |
| "Design a Facebook cover" | banner-design |
| "Create ad banners for Google" | banner-design |
| "Make a website hero banner" | banner-design |
| "Generate a settings icon" | icon-design |
| "Create SVG icons for my app" | icon-design |
| "Design an icon set" | icon-design |
## Multi-Skill Workflows
### New Project Setup
```
1. brand → Define identity
- Colors, typography, voice
2. design-system → Create tokens
- Primitive, semantic, component
3. ui-styling → Implement
- Configure Tailwind, add components
```
### Design System Migration
```
1. brand → Audit existing
- Extract brand colors, fonts
2. design-system → Formalize tokens
- Create three-layer architecture
3. ui-styling → Update code
- Replace hardcoded values
```
### Component Creation
```
1. design-system → Reference specs
- Button states, sizes, variants
2. ui-styling → Implement
- Build with shadcn/ui + Tailwind
```
## Skill Dependencies
```
brand
↓ (colors, typography)
design-system
↓ (tokens, specs)
ui-styling
↓ (components)
Application Code
```
## Quick Commands
**Brand:**
```bash
node .claude/skills/brand/scripts/inject-brand-context.cjs
node .claude/skills/brand/scripts/validate-asset.cjs <path>
```
**Tokens:**
```bash
node .claude/skills/design-system/scripts/generate-tokens.cjs -c tokens.json
node .claude/skills/design-system/scripts/validate-tokens.cjs -d src/
```
**Components:**
```bash
npx shadcn@latest add button card input
```
## When to Use Multiple Skills
Use **all eight** when:
- Complete brand package from scratch (logo → CIP → presentation)
Use **brand + design-system + ui-styling** when:
- Design system setup and implementation
Use **logo-design + cip-design** when:
- Complete brand identity package with deliverable mockups
Use **logo-design + cip-design + slides** when:
- Brand pitch: generate logo, create CIP mockups, build pitch deck
Use **banner-design + brand** when:
- Social media presence: branded banners across all platforms
Use **icon-design + design-system** when:
- Custom icon set matching design tokens and component specs
Use **brand + design-system** when:
- Defining design language without implementation
Use **design-system + ui-styling** when:
- Implementing existing brand in code
- Building component library

View File

@@ -0,0 +1,365 @@
# Design Style Guide
Match visual design to app category and target audience for cohesive user experience.
## Style Selection Principle
> **The visual style must match the app's purpose and audience.**
> A finance app should feel trustworthy, not playful.
> A children's app should feel fun, not corporate.
## Style Selection Matrix
| App Category | Visual Style | Color Palette | Typography | Interaction |
|--------------|--------------|---------------|------------|-------------|
| Utility/Tool | Minimalist | Neutral + 1 accent | Clean sans-serif | Direct, efficient |
| Finance/Banking | Professional Trust | Blue/Green/Navy | Conservative | Secure, deliberate |
| Health/Wellness | Calm & Natural | Soft greens, earth tones | Rounded, friendly | Gentle, encouraging |
| Kids (3-5) | Playful Simple | Bright primary colors | Large, rounded | Big targets, forgiving |
| Kids (6-12) | Fun & Engaging | Vibrant, varied | Bold, readable | Gamified feedback |
| Social/Entertainment | Expressive | Brand-driven | Dynamic | Gesture-rich |
| Productivity | Clean & Focused | Minimal, high contrast | Professional | Keyboard-friendly |
| E-commerce | Conversion-focused | Brand + CTA colors | Scannable | Quick actions |
| Gaming | Immersive | Theme-driven | Stylized | Custom gestures |
## Detailed Style Profiles
### Minimalist / iOS-like (Utility Apps)
**When to use**: Tools, utilities, calculators, file managers, settings apps
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | 2-3 colors max, neutral base |
| Whitespace | Generous, 24-48dp margins |
| Typography | Single font family, clear hierarchy |
| Icons | Line-based, consistent stroke |
| Shadows | Subtle or none |
| Borders | Thin (1dp) or none |
| Shapes | Subtle corners (8-12dp) |
**Interaction Style**:
- Direct manipulation
- Immediate feedback
- No unnecessary animations
- Efficient task completion
**Color Palette**:
| Role | Light Mode | Dark Mode |
|------|------------|-----------|
| Background | #FAFAFA | #1C1C1E |
| Surface | #FFFFFF | #2C2C2E |
| Primary | #007AFF | #0A84FF |
| Text | #000000 | #FFFFFF |
| Secondary | #8E8E93 | #8E8E93 |
**Reference Apps**: iOS Settings, Apple Notes, Google Calculator
---
### Professional Trust (Finance/Business)
**When to use**: Banking, investment, enterprise, B2B applications
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | Blues, greens, navy (trust colors) |
| Whitespace | Structured, grid-based |
| Typography | Formal, conservative weights |
| Icons | Filled or outlined, consistent |
| Data visualization | Clear, accurate charts |
| Security indicators | Prominent locks, badges |
**Interaction Style**:
- Confirmatory (double-check important actions)
- Deliberate (not rushed)
- Secure-feeling
- Clear feedback on transactions
**Color Palette**:
| Role | Color | Name |
|------|-------|------|
| Primary | #00695C or #1565C0 | Teal 800 / Blue 800 |
| Secondary | #37474F | Blue Grey 800 |
| Accent | #FFC107 | Amber |
| Background | #ECEFF1 | Blue Grey 50 |
| Success | #2E7D32 | Green 800 |
| Error | #C62828 | Red 800 |
**Key Patterns**:
- Balance summaries prominent
- Transaction history easily scannable
- Secure entry for sensitive data
- Biometric authentication prompts
**Reference Apps**: Banking apps, Trading platforms, Enterprise tools
---
### Calm & Wellness (Health Apps)
**When to use**: Meditation, fitness tracking, health monitoring, therapy
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | Soft, muted, natural |
| Whitespace | Abundant (breathing room) |
| Typography | Rounded, friendly fonts |
| Shapes | Organic, soft corners (16dp+) |
| Animation | Gentle, slow transitions |
| Imagery | Nature, soft gradients |
**Interaction Style**:
- Encouraging, not demanding
- Progress-oriented
- Gentle reminders
- Celebration of achievements
**Color Palette**:
| Role | Color | Name |
|------|-------|------|
| Primary | #4CAF50 | Green 500 |
| Secondary | #81C784 | Green 300 |
| Tertiary | #B2DFDB | Teal 100 |
| Background | #F1F8E9 | Light Green 50 |
| Text | #33691E | Light Green 900 |
| Accent | #FFB74D | Orange 300 |
**Key Patterns**:
- Progress rings and charts
- Streak tracking
- Motivational messages
- Quiet notification style
**Reference Apps**: Headspace, Calm, Apple Fitness
---
### Playful & Kid-Friendly (Children's Apps)
**When to use**: Educational games, children's content, family apps
#### Ages 3-5
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | Bright, saturated primary colors |
| Touch targets | 56dp minimum, 64dp recommended |
| Shapes | Very rounded (full radius) |
| Typography | Large (18sp+ minimum), simple fonts |
| Icons | Large, colorful, recognizable |
| Animation | Frequent, rewarding |
**Interaction Style**:
- Simple gestures only (tap, drag)
- No multi-finger gestures
- Forgiving error handling
- Immediate, multi-sensory feedback (sound + visual + haptic)
- No text-only buttons
**Color Palette**:
| Role | Color | Name |
|------|-------|------|
| Primary | #F44336 | Red 500 |
| Secondary | #FFEB3B | Yellow 500 |
| Tertiary | #2196F3 | Blue 500 |
| Background | #FFFFFF | White or soft pastels |
| Accent | #4CAF50 | Green 500 |
#### Ages 6-12
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | Vibrant, varied palette |
| Touch targets | 48dp minimum |
| Shapes | Rounded but can be varied |
| Typography | Bold, readable, can include text |
| Icons | Stylized, character-driven |
| Animation | Gamified, achievement-based |
**Interaction Style**:
- Can introduce some complexity
- Gamification elements
- Progress and rewards
- Some text is acceptable
**Key Patterns for All Kids Apps**:
- Icon-based navigation (no text-only)
- Home button always visible
- Back navigation clear
- Parent gate for settings (math problem, hold button)
- Multi-sensory feedback
- Encouraging error states (no punishment)
- Joint engagement opportunities with parents
**Reference Apps**: PBS Kids, Khan Academy Kids, Duolingo ABC
---
### Expressive & Social (Entertainment Apps)
**When to use**: Social media, content creation, entertainment
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | Bold brand colors |
| Typography | Dynamic, personality-driven |
| Media | Rich, prominent |
| Animation | Expressive, delightful |
| Shapes | Brand-specific |
**Interaction Style**:
- Gesture-rich
- Quick actions
- Social interactions prominent
- Content-first design
**Key Patterns**:
- Feed-based layouts
- Quick action buttons (like, share, comment)
- Stories/ephemeral content
- Creation tools accessible
- Notification badges
**Reference Apps**: Instagram, TikTok, Snapchat
---
### Clean & Focused (Productivity Apps)
**When to use**: Note-taking, task management, email, documents
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | High contrast, minimal |
| Whitespace | Strategic, content-focused |
| Typography | Highly readable, clear hierarchy |
| Icons | Functional, consistent |
| Density | Adjustable (compact to comfortable) |
**Interaction Style**:
- Keyboard-friendly
- Batch operations
- Drag and drop
- Quick capture
- Search-centric
**Color Palette**:
| Role | Light Mode | Dark Mode |
|------|------------|-----------|
| Primary | #1976D2 | #64B5F6 |
| Background | #FFFFFF | #121212 |
| Surface | #F5F5F5 | #1E1E1E |
| Text | #212121 | #E0E0E0 |
| Accent/Priority | #FF5722 | #FF7043 |
**Key Patterns**:
- List views with swipe actions
- Quick add buttons
- Checkbox interactions
- Due dates and reminders
- Tags and categories
**Reference Apps**: Notion, Todoist, Google Tasks
---
### Conversion-Focused (E-commerce)
**When to use**: Shopping, marketplace, booking apps
**Visual Characteristics**:
| Element | Specification |
|---------|---------------|
| Colors | Brand + clear CTA colors |
| Images | High quality, zoomable |
| Typography | Scannable, price prominent |
| Cards | Product-focused |
| Badges | Sale, new, limited |
**Interaction Style**:
- Quick add to cart
- Easy checkout flow
- Comparison features
- Reviews accessible
- Wishlist/save for later
**Key Patterns**:
- Grid and list view toggle
- Filter and sort
- Product detail with gallery
- Cart always accessible
- One-tap purchase options
**Reference Apps**: Amazon, Shopify apps, Booking.com
---
## Consistency Principles
### Match Style to Subject Matter
| App Purpose | Style Should Feel |
|-------------|-------------------|
| Utility | Efficient, invisible |
| Finance | Trustworthy, secure |
| Health | Supportive, calm |
| Kids | Safe, fun |
| Social | Expressive, personal |
| Productivity | Focused, powerful |
| Shopping | Exciting, trustworthy |
### Internal Consistency Rules
| Rule | Implementation |
|------|----------------|
| Same icon style | All outlined OR all filled |
| Consistent color meaning | Red = destructive, Green = success |
| Uniform spacing | Use 8dp grid |
| Predictable interaction | Same gesture = same result |
| Typography system | Use M3 type scale |
## Anti-Patterns: Style Mismatch
| Mismatch | Problem |
|----------|---------|
| Playful colors in banking app | Undermines trust |
| Complex gestures in kids app | Frustrates young users |
| Cluttered UI in wellness app | Defeats calming purpose |
| Boring visuals in entertainment | Fails to engage |
| Aggressive CTAs in health app | Feels manipulative |
| Childish design in professional tool | Lacks credibility |
| Dense information in casual app | Overwhelms users |
## Implementation Checklist
- [ ] Identified app category and target audience
- [ ] Selected appropriate style profile
- [ ] Color palette matches style
- [ ] Typography matches style
- [ ] Interaction patterns match style
- [ ] Touch targets appropriate for audience
- [ ] Animation style consistent
- [ ] Internal consistency maintained
- [ ] No style mismatches
- [ ] Tested with target users

View File

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

View File

@@ -0,0 +1,392 @@
# Design System
## Color Palette Reference
| # | Name | Colors | Style | Use Cases | Tips |
|---|------|--------|-------|-----------|------|
| 1 | Modern & Wellness | `#006d77` `#83c5be` `#edf6f9` `#ffddd2` `#e29578` | Fresh, soothing | Healthcare, counseling, skincare, yoga/spa | Deep teal for titles, light pink for background |
| 2 | Business & Authority | `#2b2d42` `#8d99ae` `#edf2f4` `#ef233c` `#d90429` | Formal, classic | Annual reports, financial analysis, corporate intro, government | Deep blue for professionalism, bright red to highlight data |
| 3 | Nature & Outdoors | `#606c38` `#283618` `#fefae0` `#dda15e` `#bc6c25` | Grounded, earthy | Outdoor gear, environmental, agriculture, historical culture | Dark green base, cream text |
| 4 | Vintage & Academic | `#780000` `#c1121f` `#fdf0d5` `#003049` `#669bbc` | Classic, scholarly | Academic lectures, history reviews, museums, heritage brands | Strong contrast between deep red and deep blue |
| 5 | Soft & Creative | `#cdb4db` `#ffc8dd` `#ffafcc` `#bde0fe` `#a2d2ff` | Dreamy, candy-toned | Mother & baby, desserts, women's fashion, kindergarten | Use dark gray or black for text |
| 6 | Bohemian | `#ccd5ae` `#e9edc9` `#fefae0` `#faedcd` `#d4a373` | Gentle, muted | Wedding planning, home decor, organic food, slow living | Cream background, green-brown accents |
| 7 | Vibrant & Tech | `#8ecae6` `#219ebc` `#023047` `#ffb703` `#fb8500` | High energy, sporty | Sports events, gyms, startup pitches, youth education | Deep blue for stability, orange as focal accent |
| 8 | Craft & Artisan | `#7f5539` `#a68a64` `#ede0d4` `#656d4a` `#414833` | Rustic, coffee-toned | Coffee shops, handicrafts, traditional culture, bakery | Suited for paper/leather textures |
| 9 | Tech & Night | `#000814` `#001d3d` `#003566` `#ffc300` `#ffd60a` | Deep, luminous | Tech launches, astronomy, night economy, luxury automobiles | Must use dark mode |
| 10 | Education & Charts | `#264653` `#2a9d8f` `#e9c46a` `#f4a261` `#e76f51` | Clear, logical | Statistical reports, education, market analysis, general business | Perfect chart color scheme |
| 11 | Forest & Eco | `#dad7cd` `#a3b18a` `#588157` `#3a5a40` `#344e41` | Monochrome gradient, forest | Landscape design, ESG reports, environmental causes, botanical | Monochrome palette is safe and cohesive |
| 12 | Elegant & Fashion | `#edafb8` `#f7e1d7` `#dedbd2` `#b0c4b1` `#4a5759` | Muted, Morandi tones | Haute couture, art galleries, beauty brands, magazine style | Negative space is key |
| 13 | Art & Food | `#335c67` `#fff3b0` `#e09f3e` `#9e2a2b` `#540b0e` | Rich, vintage-poster | Food documentaries, art exhibitions, ethnic themes, vintage restaurants | Works well with large color blocks |
| 14 | Luxury & Mysterious | `#22223b` `#4a4e69` `#9a8c98` `#c9ada7` `#f2e9e4` | Cool, purple-toned | Jewelry showcases, hotel management, high-end consulting, psychology | Purple evokes premium atmosphere |
| 15 | Pure Tech Blue | `#03045e` `#0077b6` `#00b4d8` `#90e0ef` `#caf0f8` | Futuristic, clean | Cloud/AI, water/ocean, hospitals, clean energy | Deep ocean to sky gradient |
| 16 | Coastal Coral | `#0081a7` `#00afb9` `#fdfcdc` `#fed9b7` `#f07167` | Refreshing, summery | Travel, summer events, beverage brands, ocean themes | Teal and coral as complementary focal colors |
| 17 | Vibrant Orange Mint | `#ff9f1c` `#ffbf69` `#ffffff` `#cbf3f0` `#2ec4b6` | Bright, cheerful | Children's events, promotional posters, FMCG, social media | Orange grabs attention, mint feels fresh |
| 18 | Platinum White Gold | `#0a0a0a` `#0070F3` `#D4AF37` `#f5f5f5` `#ffffff` | Premium, professional | Agent products, corporate websites, fintech, luxury brands | White-gold base, blue for action, gold for emphasis |
---
### Agent Design System — Full Color Scale
Based on the Platinum White-Gold Theme design tokens. Provides complete color scales for fine-grained design work.
#### White Scale (Backgrounds & Light Surfaces)
| Token | Value | Usage |
|-------|-------|-------|
| white-0 | `#ffffff` | Primary background |
| white-50 | `#fefefe` | Slightly warm white |
| white-75 | `#fcfcfc` | Near-white |
| white-100 | `#fafafa` | Secondary background |
| white-200 | `#f7f7f7` | Card background |
| white-300 | `#f5f5f5` | Tertiary background |
| white-400 | `#f0f0f0` | Separator zones |
| white-500 | `#ebebeb` | Light border |
| white-600 | `#e5e5e5` | Disabled background |
| white-700 | `#e0e0e0` | Deep white-gray |
| white-800 | `#d9d9d9` | Placeholder |
| white-900 | `#d4d4d4` | Divider lines |
| white-1000 | `#cccccc` | Deepest white |
#### Gold Scale (Platinum Business Accent)
| Token | Value | Usage |
|-------|-------|-------|
| gold-25 | `#FFFDF5` | Extremely light gold background |
| gold-50 | `#FEF9E7` | Light gold background |
| gold-75 | `#FCF3D0` | Pale gold highlight |
| gold-100 | `#FAECB8` | Gold hover state |
| gold-200 | `#F5DC8A` | Bright gold accent |
| gold-300 | `#E8C860` | Gold hover |
| gold-400 | `#D4AF37` | **Primary gold (core)** |
| gold-500 | `#B8972E` | Gold text |
| gold-600 | `#9A7E26` | Deep gold accent |
| gold-700 | `#7C651E` | Dark gold border |
| gold-800 | `#5E4C16` | Deep gold background |
| gold-900 | `#40330F` | Very deep gold |
| gold-1000 | `#221A08` | Black gold |
#### Blue Scale (Primary Action Color)
| Token | Value | Usage |
|-------|-------|-------|
| blue-25 | `#F0F7FF` | Extremely light blue background |
| blue-50 | `#E0EFFF` | Info alert background |
| blue-75 | `#C2DFFF` | Light blue highlight |
| blue-100 | `#A3CFFF` | Disabled blue |
| blue-200 | `#66AFFF` | Bright blue |
| blue-300 | `#338FFF` | Blue hover |
| blue-400 | `#0070F3` | **Primary blue (core)** |
| blue-500 | `#005FCC` | Blue text |
| blue-600 | `#004FA6` | Deep blue accent |
| blue-700 | `#003F80` | Dark blue border |
| blue-800 | `#002F5A` | Deep blue background |
| blue-900 | `#001F3D` | Very deep blue |
| blue-1000 | `#001026` | Black blue |
#### Gray Scale (Text & Neutral Colors)
| Token | Value | Usage |
|-------|-------|-------|
| gray-0 | `#ffffff` | White |
| gray-50 | `#fafafa` | Extremely light gray |
| gray-75 | `#f5f5f5` | Light gray background |
| gray-100 | `#ededed` | Light divider |
| gray-200 | `#d4d4d4` | Light border |
| gray-300 | `#a3a3a3` | Quaternary text |
| gray-400 | `#737373` | Tertiary text |
| gray-500 | `#525252` | Secondary text |
| gray-600 | `#404040` | Dark gray |
| gray-700 | `#2e2e2e` | Dark background |
| gray-800 | `#1f1f1f` | Deep background |
| gray-900 | `#141414` | Very deep background |
| gray-1000 | `#0a0a0a` | **Primary text (core)** |
#### Opacity Values
##### Opacity Black
| Opacity | Value | Usage |
|---------|-------|-------|
| 0% | `#0a0a0a00` | Fully transparent |
| 2% | `#0a0a0a05` | Subtle overlay |
| 4% | `#0a0a0a0a` | Secondary interactive background |
| 8% | `#0a0a0a14` | Border / divider |
| 15% | `#0a0a0a26` | Pressed state |
| 20% | `#0a0a0a33` | Light overlay |
| 25% | `#0a0a0a40` | Medium overlay |
| 50% | `#0a0a0a80` | Semi-transparent |
| 70% | `#0a0a0ab2` | Deep overlay |
| 80% | `#0a0a0acc` | Hover state |
| 90% | `#0a0a0ae5` | Tooltip |
| 95% | `#0a0a0af2` | Modal |
##### Opacity White
| Opacity | Value | Usage |
|---------|-------|-------|
| 0% | `#ffffff00` | Fully transparent |
| 2% | `#ffffff05` | Subtle overlay |
| 4% | `#ffffff0a` | Secondary interactive background |
| 8% | `#ffffff12` | Border / divider |
| 15% | `#ffffff26` | Pressed state |
| 20% | `#ffffff33` | Light overlay |
| 25% | `#ffffff40` | Medium overlay |
| 50% | `#ffffff80` | Semi-transparent |
| 70% | `#ffffffb2` | Deep overlay |
| 80% | `#ffffffcc` | Hover state |
| 90% | `#ffffffe5` | Tooltip |
| 95% | `#fffffff2` | Modal |
---
## Color Palette Rules (MANDATORY)
### Strict Palette Adherence
**Use ONLY the provided color palette. Do NOT create or modify colors.**
- All colors must come from the user-provided palette
- Do NOT use colors outside the palette
- Do NOT modify palette colors (brightness, saturation, mixing)
- **Only exception**: Add transparency using the `transparency` property (0-100)
```javascript
// Correct: Using palette colors
slide.addShape(pres.shapes.RECTANGLE, { fill: { color: theme.primary } });
slide.addText("Title", { color: theme.accent });
// Wrong: Colors outside palette
slide.addShape(pres.shapes.RECTANGLE, { fill: { color: "1a1a2e" } });
```
### No Gradients
**Gradients are prohibited. Use solid colors only.**
### No Animations
**Animations and transitions are prohibited.** All slides must be static.
---
## Font Reference
### Recommended Fonts
| Language | Default Font | Alternatives |
|----------|-------------|--------------|
| **Chinese** | Microsoft YaHei | — |
| **English** | Arial | Georgia, Calibri, Cambria, Trebuchet MS |
- For mixed Chinese-English content: use Microsoft YaHei for Chinese, the chosen font for English
- Prefer system fonts for cross-platform compatibility
- Titles and body text can use different font pairings (e.g. Georgia + Calibri)
### Recommended Font Pairings
| Header Font | Body Font |
|-------------|-----------|
| Georgia | Calibri |
| Arial Black | Arial |
| Calibri | Calibri Light |
| Cambria | Calibri |
| Trebuchet MS | Calibri |
| Impact | Arial |
| Palatino | Garamond |
| Consolas | Calibri |
**Choose an interesting font pairing** — don't default to Arial for everything. Pick a header font with personality and pair it with a clean body font.
### No Bold for Body Text
**Plain body text and caption/legend text must NOT use bold.**
- Body paragraphs, descriptions → normal weight
- Captions, legends, footnotes → normal weight
- Reserve bold for titles and headings only
```javascript
// Correct
slide.addText("Main Title", { bold: true, fontSize: 36, fontFace: "Arial" });
slide.addText("Body text here.", { bold: false, fontSize: 14, fontFace: "Arial" });
// Wrong
slide.addText("Body text here.", { bold: true, fontSize: 14 });
```
---
## Style Recipes
The same design can be rendered in 4 distinct visual styles by adjusting corner radius (`rectRadius`) and spacing. Choose the style recipe that fits the presentation tone.
> **Unit note**: PptxGenJS uses inches. Slide dimensions are 10" x 5.625" (LAYOUT_16x9).
### Style Overview
| Style | Corner Radius | Spacing | Best For |
|-------|--------------|---------|----------|
| **Sharp & Compact** | 0 ~ 0.05" | Tight | Data-dense, tables, professional reports |
| **Soft & Balanced** | 0.08" ~ 0.12" | Moderate | Corporate, business presentations, general use |
| **Rounded & Spacious** | 0.15" ~ 0.25" | Relaxed | Product intros, marketing, creative showcases |
| **Pill & Airy** | 0.3" ~ 0.5" | Open | Brand showcases, launch events, premium presentations |
### Sharp & Compact
**Visual character**: Geometric, high information density, formal and serious.
| Category | Value (inches) | Notes |
|----------|---------------|-------|
| Corner radius — small | 0" | Full right angle |
| Corner radius — medium | 0.03" | Micro-rounded |
| Corner radius — large | 0.05" | Slight rounding |
| Element padding | 0.1" ~ 0.15" | Compact |
| Element gap | 0.1" ~ 0.2" | Compact |
| Page margin | 0.3" | Narrow |
| Block gap | 0.25" ~ 0.35" | Compact |
### Soft & Balanced
**Visual character**: Moderate rounding, comfortable whitespace, professional yet approachable.
| Category | Value (inches) | Notes |
|----------|---------------|-------|
| Corner radius — small | 0.05" | Slight rounding |
| Corner radius — medium | 0.08" | Medium rounding |
| Corner radius — large | 0.12" | Larger rounding |
| Element padding | 0.15" ~ 0.2" | Moderate |
| Element gap | 0.15" ~ 0.25" | Moderate |
| Page margin | 0.4" | Standard |
| Block gap | 0.35" ~ 0.5" | Moderate |
### Rounded & Spacious
**Visual character**: Large corners, generous whitespace, friendly and modern.
| Category | Value (inches) | Notes |
|----------|---------------|-------|
| Corner radius — small | 0.1" | Medium rounding |
| Corner radius — medium | 0.15" | Large rounding |
| Corner radius — large | 0.25" | Very large rounding |
| Element padding | 0.2" ~ 0.3" | Relaxed |
| Element gap | 0.25" ~ 0.4" | Relaxed |
| Page margin | 0.5" | Wide |
| Block gap | 0.5" ~ 0.7" | Relaxed |
### Pill & Airy
**Visual character**: Full pill-shaped corners, abundant whitespace, light and open feel, strong brand presence.
| Category | Value (inches) | Notes |
|----------|---------------|-------|
| Corner radius — small | 0.2" | Large rounding |
| Corner radius — medium | 0.3" | Pill shape |
| Corner radius — large | 0.5" | Full pill |
| Element padding | 0.25" ~ 0.4" | Open |
| Element gap | 0.3" ~ 0.5" | Open |
| Page margin | 0.6" | Wide |
| Block gap | 0.6" ~ 0.9" | Open |
### Component Style Mapping
| Component | Sharp | Soft | Rounded | Pill |
|-----------|-------|------|---------|------|
| **Button / Tag** | rectRadius: 0 | rectRadius: 0.05 | rectRadius: 0.1 | rectRadius: 0.2 |
| **Card / Container** | rectRadius: 0.03 | rectRadius: 0.1 | rectRadius: 0.2 | rectRadius: 0.3 |
| **Image Container** | rectRadius: 0 | rectRadius: 0.08 | rectRadius: 0.15 | rectRadius: 0.25 |
| **Input Field** | rectRadius: 0 | rectRadius: 0.05 | rectRadius: 0.1 | rectRadius: 0.2 |
| **Badge** | rectRadius: 0.02 | rectRadius: 0.05 | rectRadius: 0.08 | rectRadius: 0.15 |
| **Avatar Frame** | rectRadius: 0 | rectRadius: 0.1 | rectRadius: 0.2 | rectRadius: 0.5 (circle) |
#### PptxGenJS Corner Radius Examples
```javascript
// Sharp style card
slide.addShape("rect", {
x: 0.5, y: 1, w: 4, h: 2.5,
fill: { color: "F5F5F5" },
rectRadius: 0.03
});
// Rounded style card
slide.addShape("rect", {
x: 0.5, y: 1, w: 4, h: 2.5,
fill: { color: "F5F5F5" },
rectRadius: 0.2
});
// Pill style button (height 0.4", rectRadius 0.2" = perfect pill)
slide.addShape("rect", {
x: 3, y: 4, w: 2, h: 0.4,
fill: { color: "4A90D9" },
rectRadius: 0.2
});
```
### Mixing Rules
#### 1. Outer container corner >= inner element corner
```javascript
// Correct: outer > inner
card: rectRadius: 0.2
button: rectRadius: 0.1
// Wrong: inner > outer → visual overflow effect
card: rectRadius: 0.1
button: rectRadius: 0.2
```
#### 2. Information density drives spacing
| Zone Type | Recommended Style |
|-----------|------------------|
| Data display zone | Sharp / Soft (compact spacing) |
| Content browsing zone | Rounded / Pill (relaxed spacing) |
| Title zone | Soft / Rounded (moderate spacing) |
#### 3. Corner radius vs element height
| Element Height | Sharp | Soft | Rounded | Pill |
|---------------|-------|------|---------|------|
| Small (< 0.3") | 0" | 0.03" | 0.08" | height/2 |
| Medium (0.3" ~ 0.6") | 0.02" | 0.05" | 0.12" | height/2 |
| Large (0.6" ~ 1.2") | 0.03" | 0.08" | 0.2" | 0.3" |
| Extra large (> 1.2") | 0.05" | 0.12" | 0.25" | 0.4" |
> **Pill tip**: For a perfect pill shape, set `rectRadius = element height / 2`
### Typography Scale (PPT)
| Usage | Size (pt) | Notes |
|-------|-----------|-------|
| Annotations / Sources | 10 ~ 12 | Minimum readable size |
| Body / Description | 14 ~ 16 | Standard body |
| Subtitle | 18 ~ 22 | Secondary heading |
| Title | 28 ~ 36 | Page title |
| Large Title | 44 ~ 60 | Cover / section title |
| Data Callout | 60 ~ 96 | Key number display |
### Spacing Scale (PPT)
Based on 10" x 5.625" slide dimensions:
| Usage | Recommended (inches) |
|-------|---------------------|
| Icon-to-text gap | 0.08" ~ 0.15" |
| List item spacing | 0.15" ~ 0.25" |
| Card inner padding | 0.2" ~ 0.4" |
| Element group gap | 0.3" ~ 0.5" |
| Page safe margin | 0.4" ~ 0.6" |
| Major block gap | 0.5" ~ 0.8" |
### Quick Selection Guide
| Presentation Type | Recommended Style | Reason |
|------------------|------------------|--------|
| Finance / Data reports | Sharp & Compact | High density, serious and precise |
| Corporate / Business | Soft & Balanced | Balances professionalism and approachability |
| Product intro / Marketing | Rounded & Spacious | Modern feel, friendly |
| Launch events / Brand | Pill & Airy | Premium feel, visual impact |
| Training / Education | Soft / Rounded | Clear, readable, friendly |
| Tech sharing | Sharp / Soft | Professional, information-dense |

View File

@@ -0,0 +1,829 @@
# GOOD vs BAD Document Design — Concrete OpenXML Examples
A side-by-side reference showing common design mistakes and their fixes, with exact OpenXML parameter values. Use this to develop an intuitive sense of what makes a document look professional versus amateur.
Format: Each comparison shows the **BAD** version first (the mistake), then the **GOOD** version (the fix), with OpenXML markup and a short explanation.
---
## 1. Font Size Disasters
### 1a. No Hierarchy — Everything the Same Size
**BAD: Body=12pt, H1=12pt bold**
```
┌──────────────────────────────────┐
│ INTRODUCTION │ ← 12pt bold... same visual weight
│ This is the body text of the │ ← 12pt regular
│ report. It discusses findings │
│ from the quarterly review. │
│ METHODOLOGY │ ← Where does the section start?
│ We collected data from three │
│ sources across the enterprise. │
└──────────────────────────────────┘
```
```xml
<!-- H1: bold but same size as body — no visual separation -->
<w:rPr><w:b/><w:sz w:val="24"/></w:rPr>
<!-- Body -->
<w:rPr><w:sz w:val="24"/></w:rPr>
```
**GOOD: Modular scale — body=11pt, H3=13pt, H2=16pt, H1=20pt**
```
┌──────────────────────────────────┐
│ │
│ Introduction │ ← 20pt, clearly a title
│ │
│ This is the body text of the │ ← 11pt, comfortable reading size
│ report. It discusses findings │
│ from the quarterly review. │
│ │
│ Methodology │ ← 20pt, section break is obvious
│ │
│ We collected data from three │
│ sources across the enterprise. │
└──────────────────────────────────┘
```
```xml
<!-- H1: 20pt = w:sz 40 -->
<w:rPr><w:rFonts w:ascii="Calibri Light"/><w:sz w:val="40"/></w:rPr>
<!-- H2: 16pt = w:sz 32 -->
<w:rPr><w:rFonts w:ascii="Calibri Light"/><w:sz w:val="32"/></w:rPr>
<!-- H3: 13pt = w:sz 26, bold -->
<w:rPr><w:rFonts w:ascii="Calibri"/><w:b/><w:sz w:val="26"/></w:rPr>
<!-- Body: 11pt = w:sz 22 -->
<w:rPr><w:rFonts w:ascii="Calibri"/><w:sz w:val="22"/></w:rPr>
```
**Why better:** A clear size progression (ratio ~1.25x per step) lets readers instantly identify structure without reading a word.
---
### 1b. Too Much Contrast — Children's Book Look
**BAD: H1=28pt with body=10pt (ratio 2.8x)**
```
┌──────────────────────────────────┐
│ │
│ QUARTERLY REPORT │ ← 28pt, dominates the page
│ │
│ This is body text set very small │ ← 10pt, straining to read
│ and the contrast with the title │
│ makes it feel like a poster. │
└──────────────────────────────────┘
```
```xml
<w:rPr><w:b/><w:sz w:val="56"/></w:rPr> <!-- 28pt heading -->
<w:rPr><w:sz w:val="20"/></w:rPr> <!-- 10pt body -->
```
**GOOD: H1=20pt with body=11pt (ratio ~1.8x)**
```xml
<w:rPr><w:sz w:val="40"/></w:rPr> <!-- 20pt heading -->
<w:rPr><w:sz w:val="22"/></w:rPr> <!-- 11pt body -->
```
**Why better:** A heading-to-body ratio between 1.5x and 2.0x reads as "structured" rather than "shouting."
---
## 2. Spacing Crimes
### 2a. Wall of Text — No Paragraph or Line Spacing
**BAD: Single line spacing, 0pt between paragraphs**
```
┌──────────────────────────────────┐
│The findings indicate a strong │
│correlation between training hours│
│and performance metrics. │
│Further analysis revealed that │ ← No gap — where does the new
│departments with higher budgets │ paragraph start?
│achieved better outcomes in all │
│measured categories. │
└──────────────────────────────────┘
```
```xml
<w:pPr>
<w:spacing w:line="240" w:lineRule="auto"/> <!-- 1.0 spacing (240/240) -->
<w:spacing w:after="0"/> <!-- no paragraph gap -->
</w:pPr>
```
**GOOD: 1.15x line spacing, 8pt after each paragraph**
```
┌──────────────────────────────────┐
│The findings indicate a strong │
│correlation between training │ ← Slightly more air between lines
│hours and performance metrics. │
│ │ ← 8pt gap signals new paragraph
│Further analysis revealed that │
│departments with higher budgets │
│achieved better outcomes in all │
│measured categories. │
└──────────────────────────────────┘
```
```xml
<w:pPr>
<w:spacing w:line="276" w:lineRule="auto"/> <!-- 1.15x (276/240) -->
<w:spacing w:after="160"/> <!-- 8pt = 160 twips -->
</w:pPr>
```
**Why better:** Line spacing gives each line room to breathe; paragraph spacing separates ideas without wasting a full blank line.
---
### 2b. Floating Headings — Same Space Above and Below
**BAD: 12pt before and 12pt after heading**
```
┌──────────────────────────────────┐
│ ...end of previous section. │
│ │ ← 12pt gap
│ Section Two │ ← Heading floats in the middle
│ │ ← 12pt gap
│ Start of section two content. │
└──────────────────────────────────┘
```
```xml
<w:pPr>
<w:spacing w:before="240" w:after="240"/> <!-- 12pt both sides -->
</w:pPr>
```
**GOOD: 24pt before, 8pt after heading**
```
┌──────────────────────────────────┐
│ ...end of previous section. │
│ │
│ │ ← 24pt gap — clear section break
│ Section Two │ ← Heading is close to its content
│ │ ← 8pt gap
│ Start of section two content. │
└──────────────────────────────────┘
```
```xml
<w:pPr>
<w:spacing w:before="480" w:after="160"/> <!-- 24pt before, 8pt after -->
</w:pPr>
```
**Why better:** Proximity principle: a heading belongs to the text that follows it, so more space above and less space below anchors it to its content.
---
### 2c. Wasteful Gaps — Huge Spacing Everywhere
**BAD: 24pt after every paragraph, including body text**
```
┌──────────────────────────────────┐
│ First paragraph of text here. │
│ │
│ │ ← 24pt gap after every paragraph
│ │
│ Second paragraph of text here. │
│ │
│ │
│ │
│ Third paragraph. │ ← Document looks mostly white space
└──────────────────────────────────┘
```
```xml
<w:spacing w:after="480"/> <!-- 24pt = 480 twips after every paragraph -->
```
**GOOD: Proportional spacing — body=8pt, H2=6pt after, H1=10pt after**
```xml
<!-- Body paragraph -->
<w:spacing w:after="160"/> <!-- 8pt after body -->
<!-- H1 -->
<w:spacing w:before="480" w:after="200"/> <!-- 24pt before, 10pt after -->
<!-- H2 -->
<w:spacing w:before="320" w:after="120"/> <!-- 16pt before, 6pt after -->
```
**Why better:** Spacing should vary by element role, creating a visual rhythm rather than uniform gaps.
---
## 3. Margin Mistakes
### 3a. Cramped Margins — Text Running to the Edge
**BAD: 0.5in margins all around**
```
┌────────────────────────────────────────────────┐
│Text starts almost at the paper edge and runs │
│all the way across making extremely long lines │
│that are hard to track from end back to start. │
│The eye loses its place on every line return. │
└────────────────────────────────────────────────┘
```
```xml
<w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720"/>
<!-- 720 twips = 0.5in — line length ~7.5in on letter paper -->
```
**GOOD: 1in margins (standard)**
```xml
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"/>
<!-- 1440 twips = 1.0in — line length ~6.5in, ideal for 11pt body -->
```
**Why better:** Optimal line length is 60-75 characters. At 11pt Calibri, 6.5in width achieves roughly 70 characters per line.
---
### 3b. Over-Padded Margins — Looks Like the Content is Hiding
**BAD: 2in margins on a short document**
```xml
<w:pgMar w:top="2880" w:right="2880" w:bottom="2880" w:left="2880"/>
<!-- 2880 twips = 2.0in — only 4.5in of text width, looks padded -->
```
**GOOD: 1in standard, or 1.25in for formal documents**
```xml
<!-- Standard -->
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"/>
<!-- Formal / bound documents with gutter -->
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1800" w:gutter="0"/>
<!-- 1800 twips = 1.25in left for binding margin -->
```
**Why better:** Margins should frame the content, not overwhelm it. 1-1.25in works for virtually all business and academic documents.
---
## 4. Table Ugliness
### 4a. Prison Grid — Full Borders on Every Cell
**BAD: Every cell with 1pt borders on all four sides**
```
┌───────┬───────┬───────┬───────┐
│ Name │ Dept │ Score │ Grade │
├───────┼───────┼───────┼───────┤
│ Alice │ Eng │ 92 │ A │
├───────┼───────┼───────┼───────┤
│ Bob │ Sales │ 85 │ B │
├───────┼───────┼───────┼───────┤
│ Carol │ Eng │ 78 │ C+ │
└───────┴───────┴───────┴───────┘
```
```xml
<w:tcBorders>
<w:top w:val="single" w:sz="4" w:color="000000"/>
<w:left w:val="single" w:sz="4" w:color="000000"/>
<w:bottom w:val="single" w:sz="4" w:color="000000"/>
<w:right w:val="single" w:sz="4" w:color="000000"/>
</w:tcBorders>
```
**GOOD: Three-line table (三线表) — top thick, header-bottom medium, table-bottom thick**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← 1.5pt top border
Name Dept Score Grade
────────────────────────────────── ← 0.75pt header separator
Alice Eng 92 A
Bob Sales 85 B
Carol Eng 78 C+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ← 1.5pt bottom border
```
```xml
<!-- Top border of header row cells -->
<w:top w:val="single" w:sz="12" w:color="000000"/> <!-- 1.5pt -->
<w:left w:val="nil"/><w:right w:val="nil"/>
<w:bottom w:val="single" w:sz="6" w:color="000000"/> <!-- 0.75pt -->
<!-- Data row cells: no left/right/top borders -->
<w:top w:val="nil"/><w:left w:val="nil"/><w:right w:val="nil"/>
<w:bottom w:val="nil"/>
<!-- Last row bottom border -->
<w:bottom w:val="single" w:sz="12" w:color="000000"/> <!-- 1.5pt -->
```
**Why better:** Removing inner borders lets the eye scan data freely. Three lines provide structure without visual clutter.
---
### 4b. Text Touching Borders — No Cell Padding
**BAD: Zero cell margins**
```
┌──────────┬──────────┐
│Name │Department│ ← Text cramped against borders
├──────────┼──────────┤
│Alice │Engineering│
└──────────┴──────────┘
```
```xml
<w:tcMar>
<w:top w:w="0" w:type="dxa"/>
<w:start w:w="0" w:type="dxa"/>
<w:bottom w:w="0" w:type="dxa"/>
<w:end w:w="0" w:type="dxa"/>
</w:tcMar>
```
**GOOD: 0.08in vertical, 0.12in horizontal padding**
```xml
<w:tcMar>
<w:top w:w="115" w:type="dxa"/> <!-- ~0.08in = 115 twips -->
<w:start w:w="173" w:type="dxa"/> <!-- ~0.12in = 173 twips -->
<w:bottom w:w="115" w:type="dxa"/>
<w:end w:w="173" w:type="dxa"/>
</w:tcMar>
```
**Why better:** Padding gives text breathing room inside cells, making every value easier to read.
---
### 4c. Invisible Headers — Header Row Same Style as Data
**BAD: Header row indistinguishable from data**
```xml
<!-- Header cell run properties — identical to data -->
<w:rPr><w:sz w:val="22"/></w:rPr>
```
**GOOD: Bold header text, subtle background fill, bottom border**
```xml
<!-- Header cell run properties -->
<w:rPr><w:b/><w:sz w:val="22"/><w:color w:val="333333"/></w:rPr>
<!-- Header cell shading -->
<w:tcPr>
<w:shd w:val="clear" w:color="auto" w:fill="F2F2F2"/> <!-- light gray bg -->
<w:tcBorders>
<w:bottom w:val="single" w:sz="8" w:color="666666"/> <!-- 1pt separator -->
</w:tcBorders>
</w:tcPr>
<!-- Mark row as header (repeats on page break) -->
<w:trPr><w:tblHeader/></w:trPr>
```
**Why better:** Distinct header styling lets readers instantly locate column meanings, especially in long tables that span pages. The `w:tblHeader` element ensures the header row repeats on every page.
---
## 5. Font Pairing Failures
### 5a. Visual Chaos — Too Many Fonts
**BAD: 4+ fonts in one document**
```xml
<!-- H1 in Impact -->
<w:rPr><w:rFonts w:ascii="Impact"/><w:sz w:val="40"/></w:rPr>
<!-- H2 in Georgia -->
<w:rPr><w:rFonts w:ascii="Georgia"/><w:sz w:val="32"/></w:rPr>
<!-- Body in Verdana -->
<w:rPr><w:rFonts w:ascii="Verdana"/><w:sz w:val="22"/></w:rPr>
<!-- Captions in Courier New -->
<w:rPr><w:rFonts w:ascii="Courier New"/><w:sz w:val="18"/></w:rPr>
```
**GOOD: One font family with weight variation, or two complementary families**
```xml
<!-- H1: Calibri Light (thin weight of Calibri family) -->
<w:rPr><w:rFonts w:ascii="Calibri Light"/><w:sz w:val="40"/></w:rPr>
<!-- H2: Calibri Light -->
<w:rPr><w:rFonts w:ascii="Calibri Light"/><w:sz w:val="32"/></w:rPr>
<!-- Body: Calibri (regular weight) -->
<w:rPr><w:rFonts w:ascii="Calibri"/><w:sz w:val="22"/></w:rPr>
<!-- Captions: Calibri -->
<w:rPr><w:rFonts w:ascii="Calibri"/><w:sz w:val="18"/></w:rPr>
```
**Why better:** Limiting to one or two font families creates visual coherence. Vary by size and weight, not by font.
---
### 5b. Mismatched Personality — Comic Sans Meets Times New Roman
**BAD:**
```xml
<w:rPr><w:rFonts w:ascii="Comic Sans MS"/><w:sz w:val="36"/></w:rPr> <!-- heading -->
<w:rPr><w:rFonts w:ascii="Times New Roman"/><w:sz w:val="24"/></w:rPr> <!-- body -->
```
**GOOD: Fonts with compatible character**
```xml
<w:rPr><w:rFonts w:ascii="Calibri Light"/><w:sz w:val="36"/></w:rPr> <!-- heading -->
<w:rPr><w:rFonts w:ascii="Calibri"/><w:sz w:val="22"/></w:rPr> <!-- body -->
```
**Why better:** Paired fonts should share a similar level of formality and geometric character. Comic Sans is playful/informal; Times New Roman is formal/traditional. They clash.
---
### 5c. Everything Bold — Nothing Stands Out
**BAD: Bold on body, headings, captions, everything**
```xml
<w:rPr><w:b/><w:sz w:val="40"/></w:rPr> <!-- heading: bold -->
<w:rPr><w:b/><w:sz w:val="22"/></w:rPr> <!-- body: also bold -->
<w:rPr><w:b/><w:sz w:val="18"/></w:rPr> <!-- caption: still bold -->
```
**GOOD: Bold reserved for headings and key terms only**
```xml
<w:rPr><w:b/><w:sz w:val="40"/></w:rPr> <!-- H1: bold -->
<w:rPr><w:sz w:val="32"/></w:rPr> <!-- H2: size alone is enough -->
<w:rPr><w:sz w:val="22"/></w:rPr> <!-- body: regular weight -->
<w:rPr><w:b/><w:sz w:val="22"/></w:rPr> <!-- key term inline: bold -->
<w:rPr><w:sz w:val="18"/></w:rPr> <!-- caption: regular, small -->
```
**Why better:** When everything is emphasized, nothing is emphasized. Bold should be a signal, not a default.
---
## 6. Color Abuse
### 6a. Rainbow Headings
**BAD: Each heading level a different bright color**
```xml
<w:rPr><w:color w:val="FF0000"/><w:sz w:val="40"/></w:rPr> <!-- H1: red -->
<w:rPr><w:color w:val="00AA00"/><w:sz w:val="32"/></w:rPr> <!-- H2: green -->
<w:rPr><w:color w:val="0000FF"/><w:sz w:val="26"/></w:rPr> <!-- H3: blue -->
```
**GOOD: Single accent color for headings, black or dark gray for body**
```xml
<!-- All headings use the same muted accent -->
<w:rPr><w:color w:val="1F4E79"/><w:sz w:val="40"/></w:rPr> <!-- H1: dark blue -->
<w:rPr><w:color w:val="1F4E79"/><w:sz w:val="32"/></w:rPr> <!-- H2: same blue -->
<w:rPr><w:color w:val="1F4E79"/><w:sz w:val="26"/></w:rPr> <!-- H3: same blue -->
<!-- Body in near-black -->
<w:rPr><w:color w:val="333333"/><w:sz w:val="22"/></w:rPr>
```
**Why better:** A single accent color establishes brand consistency. Multiple bright colors compete for attention and look unprofessional.
---
### 6b. Low Contrast — Light Gray on White
**BAD: #CCCCCC text on white background**
```xml
<w:rPr><w:color w:val="CCCCCC"/></w:rPr>
<!-- Contrast ratio: ~1.6:1 — fails WCAG AA (minimum 4.5:1) -->
```
**GOOD: #333333 text on white**
```xml
<w:rPr><w:color w:val="333333"/></w:rPr>
<!-- Contrast ratio: ~12:1 — passes WCAG AAA -->
```
**Why better:** Sufficient contrast is not just an accessibility requirement; it makes text physically easier to read for everyone, especially in printed documents.
---
### 6c. Bright Body Text
**BAD: Body text in a saturated color**
```xml
<w:rPr><w:color w:val="0066FF"/><w:sz w:val="22"/></w:rPr> <!-- blue body text -->
```
**GOOD: Color reserved for headings and inline accents only**
```xml
<!-- Body: neutral dark -->
<w:rPr><w:color w:val="333333"/><w:sz w:val="22"/></w:rPr>
<!-- Hyperlink: color is functional here -->
<w:rPr><w:color w:val="0563C1"/><w:u w:val="single"/></w:rPr>
```
**Why better:** Colored body text causes eye fatigue over long reading. Reserve color for elements that need to attract attention (headings, links, warnings).
---
## 7. List Formatting Issues
### 7a. Bullet at the Margin — No Indent
**BAD: List items start at the left margin**
```
┌──────────────────────────────────┐
│Here is a paragraph of text. │
│• First item │ ← Bullet at margin, no indent
│• Second item │
│• Third item │
│Next paragraph continues here. │
└──────────────────────────────────┘
```
```xml
<w:pPr>
<w:ind w:left="0" w:hanging="0"/>
</w:pPr>
```
**GOOD: 0.25in left indent with hanging indent for the bullet**
```
┌──────────────────────────────────┐
│Here is a paragraph of text. │
│ • First item │ ← Indented, clearly a list
│ • Second item │
│ • Third item │
│Next paragraph continues here. │
└──────────────────────────────────┘
```
```xml
<w:pPr>
<w:ind w:left="360" w:hanging="360"/> <!-- 0.25in = 360 twips -->
<w:numPr>
<w:ilvl w:val="0"/>
<w:numId w:val="1"/>
</w:numPr>
</w:pPr>
```
For nested lists, increment by 360 twips per level:
```xml
<!-- Level 1 -->
<w:ind w:left="720" w:hanging="360"/> <!-- 0.5in left -->
<!-- Level 2 -->
<w:ind w:left="1080" w:hanging="360"/> <!-- 0.75in left -->
```
**Why better:** Indentation visually separates lists from body text and makes nesting levels clear.
---
### 7b. List Items with Full Paragraph Spacing
**BAD: List items have the same 8-10pt spacing as body paragraphs**
```
┌──────────────────────────────────┐
│ • First item │
│ │ ← 10pt gap — looks like separate
│ • Second item │ paragraphs, not a list
│ │
│ • Third item │
└──────────────────────────────────┘
```
```xml
<w:spacing w:after="200"/> <!-- 10pt after each list item -->
```
**GOOD: Tight spacing between list items (2-4pt)**
```
┌──────────────────────────────────┐
│ • First item │
│ • Second item │ ← 2pt gap — cohesive list
│ • Third item │
└──────────────────────────────────┘
```
```xml
<w:spacing w:after="40" w:line="276" w:lineRule="auto"/> <!-- 2pt after -->
<!-- Or 4pt: -->
<w:spacing w:after="80"/>
```
**Why better:** Tight spacing groups list items as a single unit, matching how readers expect a list to behave.
---
## 8. Header/Footer Problems
### 8a. Header Text Too Large — Competes with Body
**BAD: Header in 12pt, same as body**
```
┌──────────────────────────────────┐
│ Quarterly Report - Q3 2025 │ ← 12pt header, same as body
│──────────────────────────────────│
│ Introduction │
│ This is the body text... │ ← 12pt body — header distracts
└──────────────────────────────────┘
```
```xml
<!-- Header paragraph -->
<w:rPr><w:sz w:val="24"/></w:rPr> <!-- 12pt, same as body -->
```
**GOOD: Header in 9pt, gray color, subtle**
```
┌──────────────────────────────────┐
│ Quarterly Report - Q3 2025 │ ← 9pt, gray — present but quiet
│──────────────────────────────────│
│ Introduction │
│ This is the body text... │ ← Body stands out as primary
└──────────────────────────────────┘
```
```xml
<!-- Header paragraph -->
<w:rPr>
<w:sz w:val="18"/> <!-- 9pt -->
<w:color w:val="808080"/> <!-- medium gray -->
</w:rPr>
<w:pPr>
<w:pBdr>
<w:bottom w:val="single" w:sz="4" w:color="D9D9D9"/> <!-- subtle separator -->
</w:pBdr>
</w:pPr>
```
**Why better:** Headers are reference information, not primary content. They should be legible but visually subordinate.
---
### 8b. No Page Numbers on a Long Document
**BAD: 20-page document with no page numbers**
```xml
<!-- Footer section: empty or missing -->
```
**GOOD: Page numbers in footer, right-aligned or centered**
```xml
<!-- Footer paragraph with page number field -->
<w:p>
<w:pPr>
<w:jc w:val="center"/>
<w:rPr><w:sz w:val="18"/><w:color w:val="808080"/></w:rPr>
</w:pPr>
<w:r>
<w:rPr><w:sz w:val="18"/><w:color w:val="808080"/></w:rPr>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:instrText> PAGE </w:instrText>
</w:r>
<w:r>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:t>1</w:t>
</w:r>
<w:r>
<w:fldChar w:fldCharType="end"/>
</w:r>
</w:p>
```
**Why better:** Page numbers are essential for navigation in any document over ~3 pages. Readers need to reference specific pages, and printed documents need an ordering mechanism.
---
## 9. CJK-Specific Mistakes
### 9a. Using Italic for Chinese Emphasis
**BAD: Italic applied to Chinese text**
```xml
<w:rPr>
<w:i/>
<w:rFonts w:eastAsia="SimSun"/>
<w:sz w:val="24"/>
</w:rPr>
```
CJK glyphs have no true italic form. The renderer applies a synthetic slant that looks broken and ugly — characters appear to lean awkwardly.
**GOOD: Use bold or emphasis dots (着重号) for Chinese emphasis**
```xml
<!-- Option A: Bold emphasis -->
<w:rPr>
<w:b/>
<w:rFonts w:eastAsia="SimHei"/> <!-- Switch to bold-capable font -->
<w:sz w:val="24"/>
</w:rPr>
<!-- Option B: Emphasis marks (dots under characters) -->
<w:rPr>
<w:em w:val="dot"/>
<w:rFonts w:eastAsia="SimSun"/>
<w:sz w:val="24"/>
</w:rPr>
```
**Why better:** Chinese typography has its own emphasis traditions. Bold and emphasis dots are native CJK conventions; italic is a Latin-script concept that does not translate.
---
### 9b. Latin Font for Chinese Characters
**BAD: Only ASCII font set, no EastAsia font specified**
```xml
<w:rPr>
<w:rFonts w:ascii="Arial"/> <!-- No eastAsia attribute -->
<w:sz w:val="24"/>
</w:rPr>
<!-- Word falls back to a random font. Chinese characters may render
with wrong metrics, inconsistent stroke widths, or missing glyphs. -->
```
**GOOD: Explicit EastAsia font alongside ASCII font**
```xml
<w:rPr>
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri" w:eastAsia="Microsoft YaHei"/>
<w:sz w:val="22"/>
</w:rPr>
```
For formal/academic Chinese documents:
```xml
<w:rPr>
<w:rFonts w:ascii="Times New Roman" w:hAnsi="Times New Roman"
w:eastAsia="SimSun"/>
<w:sz w:val="24"/> <!-- 小四 12pt -->
</w:rPr>
```
**Why better:** Setting `w:eastAsia` ensures Chinese characters render in a font designed for CJK glyphs, with correct stroke widths, spacing, and metrics.
---
### 9c. English Line Spacing for Dense CJK Text
**BAD: 1.15x line spacing for Chinese body text**
```xml
<w:spacing w:line="276" w:lineRule="auto"/> <!-- 1.15x — too tight for CJK -->
```
CJK characters are taller and denser than Latin letters. At 1.15x, lines of Chinese text feel cramped and hard to read.
**GOOD: 1.5x line spacing or fixed 28pt for CJK body at 12pt (小四)**
```xml
<!-- Option A: 1.5x proportional -->
<w:spacing w:line="360" w:lineRule="auto"/> <!-- 360/240 = 1.5x -->
<!-- Option B: Fixed 28pt (standard for 小四/12pt CJK body) -->
<w:spacing w:line="560" w:lineRule="exact"/> <!-- 28pt = 560 twips -->
```
For 公文 (government documents) at 三号/16pt body:
```xml
<w:spacing w:line="580" w:lineRule="exact"/> <!-- 29pt fixed line spacing -->
```
**Why better:** CJK characters occupy a full em square with no ascenders/descenders providing natural gaps. Extra line spacing compensates, improving readability of dense text blocks.
---
## 10. Overall Document Feel
### Student Homework vs Professional Document
**BAD: "Student homework" — every setting is Word's default, no intentional choices**
```xml
<!-- Default everything: Calibri 11pt, no heading styles, 1.08 spacing -->
<w:rPr><w:rFonts w:ascii="Calibri"/><w:sz w:val="22"/></w:rPr>
<w:pPr><w:spacing w:after="160" w:line="259" w:lineRule="auto"/></w:pPr>
<!-- Headings: just bold body text, no style applied -->
<w:rPr><w:b/><w:sz w:val="22"/></w:rPr>
<!-- No section breaks, no headers/footers, no page numbers -->
<!-- Tables with default full grid borders -->
<!-- No intentional color or spacing variations -->
```
**GOOD: Intentional design at every level**
```xml
<!-- Theme fonts defined -->
<w:rFonts w:asciiTheme="minorHAnsi" w:hAnsiTheme="minorHAnsi"/>
<!-- H1: Calibri Light 20pt, dark blue, generous spacing -->
<w:pPr>
<w:pStyle w:val="Heading1"/>
<w:spacing w:before="480" w:after="200"/>
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light"/>
<w:color w:val="1F4E79"/>
<w:sz w:val="40"/>
</w:rPr>
<!-- H2: Calibri Light 16pt, same blue -->
<w:pPr>
<w:pStyle w:val="Heading2"/>
<w:spacing w:before="320" w:after="120"/>
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri Light"/>
<w:color w:val="1F4E79"/>
<w:sz w:val="32"/>
</w:rPr>
<!-- Body: Calibri 11pt, dark gray, 1.15 spacing, 8pt after -->
<w:pPr>
<w:spacing w:after="160" w:line="276" w:lineRule="auto"/>
</w:pPr>
<w:rPr>
<w:rFonts w:ascii="Calibri"/>
<w:color w:val="333333"/>
<w:sz w:val="22"/>
</w:rPr>
<!-- Tables: three-line style, padded cells, repeated headers -->
<!-- Headers/footers: 9pt gray with page numbers -->
<!-- Margins: 1in all around -->
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"/>
```
**Why better:** Professional documents result from deliberate, consistent choices across all design dimensions. Each element reinforces the same visual language. The reader may not consciously notice good typography, but they feel the difference in credibility and readability.
---
## Quick Reference: Safe Defaults
A cheat sheet of values that produce a professional result for most Western business documents:
| Element | Value | OpenXML |
|---------|-------|---------|
| Body font | Calibri 11pt | `w:sz="22"` |
| H1 | Calibri Light 20pt | `w:sz="40"` |
| H2 | Calibri Light 16pt | `w:sz="32"` |
| H3 | Calibri 13pt bold | `w:sz="26"`, `w:b` |
| Body color | #333333 | `w:color="333333"` |
| Heading color | #1F4E79 | `w:color="1F4E79"` |
| Line spacing | 1.15x | `w:line="276" w:lineRule="auto"` |
| Para spacing after | 8pt | `w:after="160"` |
| H1 spacing | 24pt before, 10pt after | `w:before="480" w:after="200"` |
| H2 spacing | 16pt before, 6pt after | `w:before="320" w:after="120"` |
| Margins | 1in all around | `w:pgMar` all `"1440"` |
| Table cell padding | 0.08in / 0.12in | `w:w="115"` / `w:w="173"` |
| Header/footer size | 9pt gray | `w:sz="18" w:color="808080"` |
| List indent | 0.25in per level | `w:left="360" w:hanging="360"` |
| List item spacing | 2pt after | `w:after="40"` |
For CJK documents, adjust: body font to SimSun/YaHei, line spacing to 1.5x (`w:line="360"`), and set `w:eastAsia` on all `w:rFonts`.

View File

@@ -0,0 +1,819 @@
# Design Principles for Document Typography
WHY certain typographic choices look good -- the perceptual and psychological
reasons behind professional document design. Use this to make judgment calls
when exact specs are not provided.
## Table of Contents
1. [White Space & Breathing Room](#1-white-space--breathing-room)
2. [Contrast & Scale](#2-contrast--scale)
3. [Proximity & Grouping](#3-proximity--grouping)
4. [Alignment & Grid](#4-alignment--grid)
5. [Repetition & Consistency](#5-repetition--consistency)
6. [Visual Hierarchy & Flow](#6-visual-hierarchy--flow)
---
## 1. White Space & Breathing Room
### Why It Works
The human eye does not read continuously. It jumps in saccades, fixating on
small clusters of words. White space provides landing zones for these fixations
and gives the reader's peripheral vision a "frame" that makes each text block
feel manageable. When a page is packed to the edges, every glance returns more
text than working memory can buffer, triggering fatigue and avoidance.
Research on content density consistently shows:
- **60-70% content coverage** feels comfortable and professional.
- **80%+** starts to feel dense and bureaucratic.
- **90%+** feels oppressive -- the reader unconsciously rushes or skips.
- **Below 50%** feels wasteful or pretentious (unless intentional, like poetry).
Wider margins also carry cultural signals. Academic and luxury documents use
generous margins (1.25-1.5 inches). Internal memos and drafts use narrower
margins (0.75-1.0 inches). The margin width tells the reader how much care
went into the document before they read a single word.
Line spacing has a direct physiological basis: the eye must track back to the
start of the next line after each line break. If lines are too close, the eye
"slips" to the wrong line. If too far apart, the eye loses its sense of
continuity. The sweet spot is 120-145% of the font size.
**Rule of thumb: when in doubt, add more space, not less.**
### Good Example
```
Margins: 1 inch (1440 twips) all sides for business documents.
Line spacing: 1.15 (276 twips at 240 twips-per-line = 115%).
Paragraph spacing after: 8pt (160 twips) between body paragraphs.
```
```xml
<!-- Page margins: 1 inch = 1440 twips on all sides -->
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
w:header="720" w:footer="720" w:gutter="0"/>
<!-- Body paragraph: 1.15 line spacing, 8pt after -->
<w:pPr>
<w:spacing w:after="160" w:line="276" w:lineRule="auto"/>
</w:pPr>
```
This produces a page where content occupies roughly 65% of the area. The
reader sees clear top/bottom breathing room, and paragraphs are distinct
without feeling disconnected.
```
Page layout (good):
+----------------------------------+
| 1" margin |
| +------------------------+ |
| | Heading | |
| | | |
| | Body text here with | |
| | comfortable spacing | |
| | between lines. | |
| | | | <- visible gap between paragraphs
| | Another paragraph of | |
| | body text follows. | |
| | | |
| +------------------------+ |
| 1" margin |
+----------------------------------+
```
### Bad Example
```xml
<!-- Cramped margins: 0.5 inch = 720 twips -->
<w:pgMar w:top="720" w:right="720" w:bottom="720" w:left="720"
w:header="360" w:footer="360" w:gutter="0"/>
<!-- No paragraph spacing, single line spacing -->
<w:pPr>
<w:spacing w:after="0" w:line="240" w:lineRule="auto"/>
</w:pPr>
```
This fills ~85% of the page. Text runs edge-to-edge with no visual rest stops.
The reader sees a wall of text.
```
Page layout (bad):
+----------------------------------+
| Heading |
| Body text crammed right up to |
| the margins with no spacing |
| between lines or paragraphs. |
| Another paragraph starts here |
| and the reader cannot tell where |
| one idea ends and another begins |
| because everything blurs into a |
| single dense block of text. |
+----------------------------------+
```
### Quick Test
1. Zoom out to 50% in your document viewer. If you cannot see clear "channels"
of white between text blocks, the spacing is too tight.
2. Print a test page. Hold it at arm's length. The text area should look like
a rectangle floating in white, not filling the page.
3. Check: is the line spacing value at least 264 (`w:line` for 1.1x) for body
text? If it is 240 (single), it is too tight for anything over 10pt.
---
## 2. Contrast & Scale
### Why It Works
The brain processes visual hierarchy through relative difference, not absolute
size. A 20pt heading above 11pt body text creates a clear "this is important"
signal. But if every heading is 20pt and every sub-heading is 19pt, the brain
cannot distinguish them -- they merge into the same level.
The key insight is **modular scale**: font sizes that grow by a consistent
ratio. This mirrors natural proportions and feels harmonious for the same
reason musical intervals do.
Common scales and their character:
| Ratio | Name | Character | Example progression (from 11pt) |
|-------|----------------|---------------------------------|---------------------------------|
| 1.200 | Minor third | Subtle, refined | 11 → 13.2 → 15.8 → 19.0 |
| 1.250 | Major third | Balanced, professional | 11 → 13.75 → 17.2 → 21.5 |
| 1.333 | Perfect fourth | Strong, authoritative | 11 → 14.7 → 19.5 → 26.0 |
| 1.414 | Augmented 4th | Dramatic, presentation-style | 11 → 15.6 → 22.0 → 31.1 |
For most business documents, 1.25 (major third) works best:
```
Body = 11pt (w:sz="22")
H3 = 13pt (w:sz="26") -- 11 * 1.25 ≈ 13.75, round to 13
H2 = 16pt (w:sz="32") -- 13 * 1.25 ≈ 16.25, round to 16
H1 = 20pt (w:sz="40") -- 16 * 1.25 = 20
```
Beyond size, **weight contrast** creates hierarchy without consuming vertical
space. Regular (400) vs Bold (700) is visible at any size. Semi-bold (600) vs
Regular is subtle and best avoided unless you also vary size or color.
**Color contrast** adds a third dimension. Dark blue headings (#1F3864) against
softer dark gray body text (#333333) signals "heading" without needing a huge
size jump. Pure black (#000000) body text is harsher than necessary on white
backgrounds -- #333333 or #2D2D2D reduces glare without losing legibility.
### Good Example
```xml
<!-- H1: 20pt, bold, dark navy -->
<w:rPr>
<w:b/>
<w:sz w:val="40"/>
<w:color w:val="1F3864"/>
</w:rPr>
<!-- H2: 16pt, bold, dark navy -->
<w:rPr>
<w:b/>
<w:sz w:val="32"/>
<w:color w:val="1F3864"/>
</w:rPr>
<!-- H3: 13pt, bold, dark navy -->
<w:rPr>
<w:b/>
<w:sz w:val="26"/>
<w:color w:val="1F3864"/>
</w:rPr>
<!-- Body: 11pt, regular, dark gray -->
<w:rPr>
<w:sz w:val="22"/>
<w:color w:val="333333"/>
</w:rPr>
```
```
Visual hierarchy (good):
[████████████████████] <- H1: 20pt bold navy (clearly dominant)
(generous space)
[██████████████] <- H2: 16pt bold navy (distinct step down)
(moderate space)
[████████████] <- H3: 13pt bold navy (smaller but still bold)
[░░░░░░░░░░░░░░░░░░░░░░] <- Body: 11pt regular gray
[░░░░░░░░░░░░░░░░░░░░░░]
[░░░░░░░░░░░░░░░░░░░░░░]
```
Each level is visually distinct from its neighbors. You can identify the
hierarchy even in peripheral vision.
### Bad Example
```xml
<!-- H1: 14pt bold black -->
<w:rPr>
<w:b/>
<w:sz w:val="28"/>
<w:color w:val="000000"/>
</w:rPr>
<!-- H2: 13pt bold black -->
<w:rPr>
<w:b/>
<w:sz w:val="26"/>
<w:color w:val="000000"/>
</w:rPr>
<!-- H3: 12pt bold black -->
<w:rPr>
<w:b/>
<w:sz w:val="24"/>
<w:color w:val="000000"/>
</w:rPr>
<!-- Body: 12pt regular black -->
<w:rPr>
<w:sz w:val="24"/>
<w:color w:val="000000"/>
</w:rPr>
```
Problems:
- H3 (12pt bold) and body (12pt regular) differ only by weight -- too subtle.
- H1 (14pt) to H2 (13pt) is a 1pt step -- invisible at reading distance.
- Everything is pure black so color provides no differentiating signal.
- The ratio between levels is ~1.07, far too flat.
### Quick Test
1. **The squint test**: blur your eyes or step back from the screen. Can you
count the number of heading levels? If two levels merge, their contrast
is insufficient.
2. **Ratio check**: divide each heading size by the next smaller size. If any
ratio is below 1.15, the levels will look too similar.
3. **Color check**: do headings look distinct from body text when you glance
at the page? If everything is the same color, you are relying solely on
size/weight, which limits your hierarchy to ~3 effective levels.
---
## 3. Proximity & Grouping
### Why It Works
The Gestalt principle of proximity: items that are close together are perceived
as belonging to the same group. In document typography, this means a heading
must be **closer to the content it introduces** than to the content above it.
If a heading sits equidistant between two paragraphs, it looks orphaned -- the
reader's eye does not know if it belongs to the text above or below. The fix
is asymmetric spacing: **large space before the heading, small space after**.
The recommended ratio is 2:1 or 3:1 (space-before : space-after).
This same principle applies to:
- **List items**: spacing between items should be less than spacing between
paragraphs. Items in a list are a group and should visually cluster.
- **Captions**: a figure caption should be close to its figure, not floating
in the middle between the figure and the next paragraph.
- **Table titles**: the title sits close above the table, with more space
separating the title from preceding text.
### Good Example
```xml
<!-- H2: 18pt before, 6pt after (3:1 ratio) -->
<w:pPr>
<w:pStyle w:val="Heading2"/>
<w:spacing w:before="360" w:after="120"/>
</w:pPr>
<!-- Body paragraph: 0pt before, 8pt after -->
<w:pPr>
<w:spacing w:before="0" w:after="160"/>
</w:pPr>
<!-- List item: 0pt before, 2pt after (tight grouping) -->
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:spacing w:before="0" w:after="40"/>
</w:pPr>
```
```
Proximity (good):
...end of previous section text.
<- 18pt gap (w:before="360")
## Section Heading
<- 6pt gap (w:after="120")
First paragraph of new section
continues here with content.
<- 8pt gap (w:after="160")
Second paragraph follows.
The heading clearly "belongs to" the text below it.
```
```
List grouping (good):
Consider these factors:
- First item <- 2pt gap between items
- Second item <- items cluster as a group
- Third item
<- 8pt gap after list
The next paragraph starts here.
```
### Bad Example
```xml
<!-- H2: 12pt before, 12pt after (1:1 ratio -- orphaned heading) -->
<w:pPr>
<w:pStyle w:val="Heading2"/>
<w:spacing w:before="240" w:after="240"/>
</w:pPr>
<!-- List item: same spacing as body (10pt after) -->
<w:pPr>
<w:pStyle w:val="ListParagraph"/>
<w:spacing w:before="0" w:after="200"/>
</w:pPr>
```
```
Proximity (bad):
...end of previous section text.
<- 12pt gap
## Section Heading
<- 12pt gap (same!)
First paragraph of new section.
The heading floats between sections. It is unclear what it belongs to.
```
```
List grouping (bad):
Consider these factors:
<- 10pt gap
- First item
<- 10pt gap (same as paragraphs)
- Second item
<- 10pt gap
- Third item
<- 10pt gap
Next paragraph.
The list does not feel like a group. Each item looks like a
separate paragraph that happens to have a bullet.
```
### Quick Test
1. **Cover test**: cover the heading text. Looking only at the whitespace,
can you tell which block of text the heading belongs to? If the gaps above
and below are equal, the answer is "no."
2. **Number check**: `w:before` on headings should be at least 2x `w:after`.
Common good values: before=360 / after=120, or before=240 / after=80.
3. **List check**: `w:after` on list items should be less than half of
`w:after` on body paragraphs. If body uses 160, list items should use
40-60.
---
## 4. Alignment & Grid
### Why It Works
Alignment creates invisible lines that the eye follows down the page. When
elements share the same left edge, the reader perceives order and intention.
When elements are slightly misaligned (off by a few twips), the page looks
sloppy even if the reader cannot consciously identify why.
**Left-align vs Justify:**
- **Left-aligned** (ragged right) is best for English and other Latin-script
languages. The uneven right edge actually helps reading because each line
has a unique silhouette, making it easier for the eye to find the next line.
Justified text forces uneven word spacing that creates distracting "rivers"
of white running vertically through paragraphs.
- **Justified** is best for CJK text. Chinese, Japanese, and Korean characters
are monospaced by design -- each occupies the same cell in an invisible grid.
Justification preserves this grid perfectly. Ragged right in CJK text breaks
the grid and looks untidy.
**Indentation rule:** Use first-line indent OR paragraph spacing to separate
paragraphs -- never both. They serve the same purpose (marking paragraph
boundaries). Using both wastes space and creates visual stutter.
- Western convention: paragraph spacing (no indent) is more modern.
- CJK convention: first-line indent of 2 characters is standard.
- Academic convention: first-line indent of 0.5 inch is traditional.
### Good Example
```xml
<!-- English body: left-aligned, paragraph spacing, no indent -->
<w:pPr>
<w:jc w:val="left"/>
<w:spacing w:after="160" w:line="276" w:lineRule="auto"/>
<!-- No w:ind firstLine -->
</w:pPr>
<!-- CJK body: justified, first-line indent 2 chars, no paragraph spacing -->
<w:pPr>
<w:jc w:val="both"/>
<w:spacing w:after="0" w:line="360" w:lineRule="auto"/>
<w:ind w:firstLineChars="200"/>
</w:pPr>
<!-- Tab stops creating aligned columns -->
<w:pPr>
<w:tabs>
<w:tab w:val="left" w:pos="2880"/> <!-- 2 inches -->
<w:tab w:val="right" w:pos="9360"/> <!-- 6.5 inches (right margin) -->
</w:tabs>
</w:pPr>
```
```
English paragraph separation (good -- spacing, no indent):
This is the first paragraph with some text
that wraps to a second line naturally.
This is the second paragraph. The gap above
clearly marks the boundary.
CJK paragraph separation (good -- indent, no spacing):
  第一段正文内容从这里开始,使用两个字符
的首行缩进来标记段落边界。
  第二段紧跟其后,没有段间距,但首行缩进
清晰地标识了新段落的开始。
```
### Bad Example
```xml
<!-- English body: justified (creates word-spacing rivers) -->
<w:pPr>
<w:jc w:val="both"/>
<w:spacing w:after="160" w:line="276" w:lineRule="auto"/>
<w:ind w:firstLine="720"/> <!-- BOTH indent AND spacing: redundant -->
</w:pPr>
<!-- CJK body: left-aligned (breaks character grid) -->
<w:pPr>
<w:jc w:val="left"/>
<w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
<!-- No indent, using spacing instead -- unidiomatic for CJK -->
</w:pPr>
```
Problems:
- Justified English text with narrow columns creates uneven word gaps.
- Using both first-line indent AND paragraph spacing is redundant.
- Left-aligned CJK breaks the character grid that CJK readers expect.
- CJK with spacing-based separation looks like translated western layout.
### Quick Test
1. **River test**: in justified English text, squint and look for vertical
white streaks running through the paragraph. If you see them, switch to
left-align or increase the column width.
2. **Double signal check**: does the document use BOTH first-line indent AND
paragraph spacing? If yes, remove one. Choose indent for CJK/academic,
spacing for modern western.
3. **Tab alignment**: if you use tabs for columns, do all tab stops across
the document use the same positions? Inconsistent tab stops create jagged
invisible grid lines.
---
## 5. Repetition & Consistency
### Why It Works
Consistency is a trust signal. When a reader sees that every H2 looks the same,
every table follows the same pattern, and every page number sits in the same
spot, they unconsciously trust that the document was crafted with care. A single
inconsistency -- one H2 that is 15pt instead of 14pt, one table with different
borders -- breaks that trust and makes the reader question the content.
Consistency also reduces cognitive load. Once the reader learns "bold dark blue
= section heading," they stop spending mental effort on identifying structure
and focus entirely on content. Every inconsistency forces them to re-evaluate:
"Is this a different kind of heading, or did someone just forget to apply the
style?"
The implementation rule is simple: **use named styles, not direct formatting.**
If you define Heading2 as a style and apply it everywhere, consistency is
automatic. If you manually set font size, bold, and color on each heading
individually, inconsistency is inevitable.
### Good Example
```xml
<!-- Define styles once in styles.xml -->
<w:style w:type="paragraph" w:styleId="Heading2">
<w:name w:val="heading 2"/>
<w:basedOn w:val="Normal"/>
<w:next w:val="Normal"/>
<w:pPr>
<w:keepNext/>
<w:keepLines/>
<w:spacing w:before="360" w:after="120"/>
<w:outlineLvl w:val="1"/>
</w:pPr>
<w:rPr>
<w:rFonts w:asciiTheme="majorHAnsi" w:hAnsiTheme="majorHAnsi"/>
<w:b/>
<w:sz w:val="32"/>
<w:color w:val="1F3864"/>
</w:rPr>
</w:style>
<!-- Apply consistently: every H2 references the style -->
<w:p>
<w:pPr>
<w:pStyle w:val="Heading2"/>
<!-- No direct formatting overrides -->
</w:pPr>
<w:r><w:t>Market Analysis</w:t></w:r>
</w:p>
```
When using a table style, define it once and reference it for every table:
```xml
<!-- All tables reference the same style -->
<w:tblPr>
<w:tblStyle w:val="GridTable4Accent1"/>
<w:tblW w:w="0" w:type="auto"/>
</w:tblPr>
```
### Bad Example
```xml
<!-- First H2: manually formatted -->
<w:p>
<w:pPr>
<w:spacing w:before="360" w:after="120"/>
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="32"/>
<w:color w:val="1F3864"/>
</w:rPr>
<w:t>Market Analysis</w:t>
</w:r>
</w:p>
<!-- Second H2: slightly different (16pt instead of 16pt? No, 15pt!) -->
<w:p>
<w:pPr>
<w:spacing w:before="240" w:after="160"/> <!-- different spacing! -->
</w:pPr>
<w:r>
<w:rPr>
<w:b/>
<w:sz w:val="30"/> <!-- 15pt instead of 16pt! -->
<w:color w:val="2E74B5"/> <!-- different shade of blue! -->
</w:rPr>
<w:t>Financial Overview</w:t>
</w:r>
</w:p>
```
Problems:
- No style references -- everything is direct formatting.
- Second H2 has different size (30 vs 32), color, and spacing.
- If there are 20 headings, each could drift slightly differently.
- Changing the design later means editing every heading individually.
### Quick Test
1. **Style audit**: does every paragraph reference a `w:pStyle`? If you find
paragraphs with only direct formatting and no style, that is a consistency
risk.
2. **Search for variance**: search the XML for all `w:sz` values used with
`w:b` (bold). If you find three different sizes for what should be the same
heading level, there is an inconsistency.
3. **Table check**: do all tables in the document reference the same
`w:tblStyle`? If some tables have manual border definitions while others
use a style, the document will look patchy.
4. **Page numbers**: check that header/footer content is defined in the
default section properties and inherited by all sections, not redefined
inconsistently in each section.
---
## 6. Visual Hierarchy & Flow
### Why It Works
A well-designed document guides the reader's eye in a predictable path:
title at the top, subtitle below it, section headings as signposts, body text
as the main content, footnotes and captions as supporting details. This flow
mirrors reading priority -- the most important information is the most visually
prominent.
Each level in the hierarchy must be **distinguishable from its adjacent
levels**. It is not enough for H1 to differ from body text; H1 must also
clearly differ from H2, and H2 from H3. If any two adjacent levels are too
similar, the hierarchy collapses at that point.
Effective hierarchy uses **multiple simultaneous signals**:
| Level | Size | Weight | Color | Spacing above |
|----------|-------|---------|---------|---------------|
| Title | 26pt | Bold | #1F3864 | 0 (top) |
| Subtitle | 15pt | Regular | #4472C4 | 4pt |
| H1 | 20pt | Bold | #1F3864 | 24pt |
| H2 | 16pt | Bold | #1F3864 | 18pt |
| H3 | 13pt | Bold | #1F3864 | 12pt |
| Body | 11pt | Regular | #333333 | 0pt |
| Caption | 9pt | Italic | #666666 | 4pt |
| Footnote | 9pt | Regular | #666666 | 0pt |
Notice how each level differs from its neighbors on at least two dimensions
(size + weight, or size + color, or weight + style). Single-dimension
differences are fragile and can be missed.
**Section breaks** create rhythm in long documents. A page break before each
major section (H1) gives the reader a mental reset. Within sections, consistent
heading + body patterns create a predictable cadence that makes long documents
less intimidating.
### Good Example
```xml
<!-- Title: large, bold, navy, centered -->
<w:style w:type="paragraph" w:styleId="Title">
<w:pPr>
<w:jc w:val="center"/>
<w:spacing w:after="80"/>
</w:pPr>
<w:rPr>
<w:b/>
<w:sz w:val="52"/>
<w:color w:val="1F3864"/>
</w:rPr>
</w:style>
<!-- Subtitle: medium, regular weight, lighter blue, centered -->
<w:style w:type="paragraph" w:styleId="Subtitle">
<w:pPr>
<w:jc w:val="center"/>
<w:spacing w:after="320"/>
</w:pPr>
<w:rPr>
<w:sz w:val="30"/>
<w:color w:val="4472C4"/>
</w:rPr>
</w:style>
<!-- H1: page break before, large bold navy -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:pPr>
<w:pageBreakBefore/>
<w:keepNext/>
<w:keepLines/>
<w:spacing w:before="480" w:after="160"/>
<w:outlineLvl w:val="0"/>
</w:pPr>
<w:rPr>
<w:b/>
<w:sz w:val="40"/>
<w:color w:val="1F3864"/>
</w:rPr>
</w:style>
<!-- Caption: small, italic, gray -->
<w:style w:type="paragraph" w:styleId="Caption">
<w:pPr>
<w:spacing w:before="80" w:after="200"/>
</w:pPr>
<w:rPr>
<w:i/>
<w:sz w:val="18"/>
<w:color w:val="666666"/>
</w:rPr>
</w:style>
```
```
Visual flow (good):
+----------------------------------+
| |
| ANNUAL REPORT 2025 | <- Title: 26pt bold navy centered
| Acme Corporation | <- Subtitle: 15pt regular blue
| |
| |
+----------------------------------+
+----------------------------------+
| |
| 1. Executive Summary | <- H1: 20pt bold navy (page break)
| |
| Body text introducing the | <- Body: 11pt regular gray
| main findings of the year. |
| |
| 1.1 Revenue Highlights | <- H2: 16pt bold navy
| |
| Revenue grew by 23% year | <- Body
| over year, driven by... |
| |
| Figure 1: Revenue Growth | <- Caption: 9pt italic gray
| |
+----------------------------------+
Each level is immediately identifiable. The eye flows naturally
from title -> heading -> body -> caption.
```
### Bad Example
```xml
<!-- All headings same color as body, minimal size difference -->
<w:style w:type="paragraph" w:styleId="Heading1">
<w:rPr>
<w:b/>
<w:sz w:val="28"/> <!-- 14pt -- only 3pt above body -->
<w:color w:val="000000"/> <!-- same color as body -->
</w:rPr>
</w:style>
<!-- Caption same size as body, not italic -->
<w:style w:type="paragraph" w:styleId="Caption">
<w:rPr>
<w:sz w:val="22"/> <!-- same 11pt as body! -->
<w:color w:val="000000"/> <!-- same color as body -->
</w:rPr>
</w:style>
<!-- No page breaks between major sections -->
<!-- H1 has no pageBreakBefore, keepNext, or keepLines -->
```
Problems:
- H1 at 14pt is too close to body at 11pt (ratio 1.27 -- acceptable in
isolation but with black color matching body, the hierarchy is weak).
- Caption is indistinguishable from body text.
- No page breaks means major sections bleed into each other with no
visual rhythm.
- Everything is black, so color provides zero hierarchy signal.
### Quick Test
1. **The squint test**: blur your eyes while looking at a full page. You
should see 3-4 distinct "weight levels" of gray. If the page looks like
one uniform shade, the hierarchy is too flat.
2. **The scan test**: flip through pages quickly. Can you identify section
boundaries in under one second per page? If yes, the visual hierarchy is
working. If pages blur together, you need stronger differentiation at H1.
3. **Adjacent level test**: for each heading level, check that it differs
from the next level on at least 2 of: size, weight, color, style (italic).
Single-dimension differences get lost.
4. **Rhythm test**: in a document over 10 pages, do major sections (H1) start
on new pages? If not, long documents will feel like an undifferentiated
stream. Add `w:pageBreakBefore` to Heading1.
---
## Summary: Decision Checklist
When you are unsure about a typographic choice, run through these checks:
| Principle | Question | If No... |
|-----------|----------|----------|
| White Space | Does the page have at least 30% white space? | Increase margins or spacing |
| Contrast | Can I count heading levels by squinting? | Increase size ratios (target 1.25x) |
| Proximity | Does each heading clearly belong to text below it? | Make space-before > space-after (2:1) |
| Alignment | Is English left-aligned and CJK justified? | Switch alignment mode |
| Repetition | Do all same-level elements use the same style? | Replace direct formatting with styles |
| Hierarchy | Can I see the document structure at arm's length? | Add more differentiation signals |
**When two principles conflict, prioritize in this order:**
1. **Readability** (white space, line spacing) -- always wins
2. **Hierarchy** (contrast, scale) -- readers must find what they need
3. **Consistency** (repetition) -- builds trust
4. **Aesthetics** (alignment, grouping) -- the finishing touch

View File

@@ -0,0 +1,466 @@
# Django Best Practices
Production-grade guide for Django 5.x and Django REST Framework. 40+ rules across 8 categories.
## Core Principles (7 Rules)
```
1. ✅ Custom User model BEFORE first migration (can't change later)
2. ✅ One Django app per domain concept (users, orders, payments)
3. ✅ Fat models, thin views — business logic in models/managers, not views
4. ✅ Always use select_related/prefetch_related (prevent N+1)
5. ✅ Settings split by environment (base + dev + prod)
6. ✅ Test with pytest-django + factory_boy (not fixtures)
7. ✅ Never use runserver in production (Gunicorn + Nginx)
```
---
## 1. Project Structure (CRITICAL)
### App-Per-Domain
```
myproject/
├── config/ # Project config
│ ├── __init__.py
│ ├── settings/
│ │ ├── base.py # Shared settings
│ │ ├── dev.py # DEBUG=True, SQLite ok
│ │ └── prod.py # DEBUG=False, Postgres, HTTPS
│ ├── urls.py
│ ├── wsgi.py
│ └── asgi.py
├── apps/
│ ├── users/ # Custom User model
│ │ ├── models.py
│ │ ├── serializers.py
│ │ ├── views.py
│ │ ├── urls.py
│ │ ├── admin.py
│ │ ├── services.py # Business logic
│ │ ├── selectors.py # Complex queries
│ │ └── tests/
│ │ ├── test_models.py
│ │ ├── test_views.py
│ │ └── factories.py
│ ├── orders/
│ └── payments/
├── manage.py
├── requirements/
│ ├── base.txt
│ ├── dev.txt
│ └── prod.txt
└── docker-compose.yml
```
### Rules
```
✅ One app = one bounded context (users, orders, payments)
✅ Business logic in services.py / selectors.py, not views
✅ Each app has its own urls.py, admin.py, tests/
❌ Never put everything in one app
❌ Never import across app boundaries at the model level (use IDs)
❌ Never put business logic in views or serializers
```
---
## 2. Models & Migrations (CRITICAL)
### Custom User Model (Day 1!)
```python
# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
import uuid
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
class Meta:
db_table = 'users'
# config/settings/base.py
AUTH_USER_MODEL = 'users.User'
```
**This MUST be done before `migrate`. Cannot change after.**
### Model Best Practices
```python
class TimeStampedModel(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
class Order(TimeStampedModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='orders')
status = models.CharField(max_length=20, choices=OrderStatus.choices, default=OrderStatus.PENDING, db_index=True)
total = models.DecimalField(max_digits=10, decimal_places=2)
class Meta:
db_table = 'orders'
ordering = ['-created_at']
indexes = [
models.Index(fields=['user', 'status']),
]
def can_cancel(self) -> bool:
return self.status in [OrderStatus.PENDING, OrderStatus.CONFIRMED]
def cancel(self):
if not self.can_cancel():
raise ValueError(f"Cannot cancel order in {self.status} status")
self.status = OrderStatus.CANCELLED
self.save(update_fields=['status', 'updated_at'])
```
### Migration Rules
```
✅ Review migration SQL: python manage.py sqlmigrate app_name 0001
✅ Name migrations descriptively: --name add_status_index_to_orders
✅ Separate data migrations from schema migrations
✅ Non-destructive first: add column → backfill → remove old column
❌ Never edit or delete applied migrations
❌ Never use RunPython without reverse function
```
---
## 3. Views & Serializers — DRF (HIGH)
### Service Layer Pattern
```python
# apps/orders/services.py
from django.db import transaction
class OrderService:
@staticmethod
@transaction.atomic
def create_order(user, items_data: list[dict]) -> Order:
total = sum(item['price'] * item['quantity'] for item in items_data)
order = Order.objects.create(user=user, total=total)
OrderItem.objects.bulk_create([
OrderItem(order=order, **item) for item in items_data
])
return order
@staticmethod
def cancel_order(order_id: str, user) -> Order:
order = Order.objects.select_for_update().get(id=order_id, user=user)
order.cancel()
return order
```
### Serializers
```python
class OrderSerializer(serializers.ModelSerializer):
items = OrderItemSerializer(many=True, read_only=True)
class Meta:
model = Order
fields = ['id', 'status', 'total', 'items', 'created_at']
read_only_fields = ['id', 'total', 'created_at']
class CreateOrderSerializer(serializers.Serializer):
"""Input-only serializer — separate from output."""
items = serializers.ListField(
child=serializers.DictField(), min_length=1, max_length=50,
)
def validate_items(self, items):
for item in items:
if item.get('quantity', 0) < 1:
raise serializers.ValidationError("Quantity must be at least 1")
return items
```
### Views (Thin!)
```python
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def create_order(request):
serializer = CreateOrderSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
order = OrderService.create_order(request.user, serializer.validated_data['items'])
return Response({'data': OrderSerializer(order).data}, status=status.HTTP_201_CREATED)
```
### Rules
```
✅ Separate input serializers from output serializers
✅ Views only: validate → call service → serialize → respond
✅ Use @transaction.atomic for multi-model writes
❌ Never put business logic in views or serializers
❌ Never use ModelSerializer for write operations (too implicit)
```
---
## 4. Authentication (HIGH)
| Method | When | Frontend |
|--------|------|----------|
| Session | Same-domain, SSR, Django templates | Django templates / htmx |
| JWT | Different domain, SPA, mobile | React, Vue, mobile apps |
| OAuth2 | Third-party login, API consumers | Any |
### JWT Config (djangorestframework-simplejwt)
```python
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
}
```
---
## 5. Performance Optimization (HIGH)
### N+1 Query Prevention
```python
# ❌ N+1: 1 query for orders + N queries for users
orders = Order.objects.all()
for o in orders:
print(o.user.email) # hits DB each iteration
# ✅ select_related (FK/OneToOne — JOIN)
orders = Order.objects.select_related('user').all()
# ✅ prefetch_related (ManyToMany/reverse FK — 2 queries)
orders = Order.objects.prefetch_related('items').all()
# ✅ Combined
orders = Order.objects.select_related('user').prefetch_related('items').all()
```
### Query Optimization Toolkit
```python
# Only fetch needed columns
User.objects.values('id', 'email')
User.objects.values_list('email', flat=True)
# Annotate instead of Python loops
from django.db.models import Count, Sum
Order.objects.annotate(item_count=Count('items'), revenue=Sum('items__price'))
# Bulk operations
OrderItem.objects.bulk_create([...])
Order.objects.filter(status='pending').update(status='cancelled')
# Database indexes
class Meta:
indexes = [
models.Index(fields=['user', 'status']),
models.Index(fields=['-created_at']),
models.Index(fields=['email'], condition=Q(is_active=True)),
]
# Pagination
from rest_framework.pagination import CursorPagination
class OrderPagination(CursorPagination):
page_size = 20
ordering = '-created_at'
```
### Caching
```python
from django.core.cache import cache
def get_product(product_id: str):
cache_key = f'product:{product_id}'
product = cache.get(cache_key)
if product is None:
product = Product.objects.get(id=product_id)
cache.set(cache_key, product, timeout=300)
return product
```
---
## 6. Testing (MEDIUM-HIGH)
### pytest-django + factory_boy
```python
# conftest.py
@pytest.fixture
def api_client():
return APIClient()
@pytest.fixture
def authenticated_client(api_client, user_factory):
user = user_factory()
api_client.force_authenticate(user=user)
return api_client
```
```python
# factories.py
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = User
email = factory.Sequence(lambda n: f'user{n}@example.com')
username = factory.Sequence(lambda n: f'user{n}')
class OrderFactory(factory.django.DjangoModelFactory):
class Meta:
model = 'orders.Order'
user = factory.SubFactory(UserFactory)
total = factory.Faker('pydecimal', left_digits=3, right_digits=2, positive=True)
```
```python
# test_views.py
@pytest.mark.django_db
class TestListOrders:
def test_returns_user_orders(self, authenticated_client):
OrderFactory.create_batch(3, user=authenticated_client.handler._force_user)
response = authenticated_client.get('/api/orders/')
assert response.status_code == 200
assert len(response.data['data']) == 3
def test_requires_authentication(self, api_client):
response = api_client.get('/api/orders/')
assert response.status_code == 401
```
---
## 7. Admin Customization (MEDIUM)
```python
class OrderItemInline(admin.TabularInline):
model = OrderItem
extra = 0
readonly_fields = ['price']
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
list_display = ['id', 'user', 'status', 'total', 'created_at']
list_filter = ['status', 'created_at']
search_fields = ['user__email', 'id']
readonly_fields = ['id', 'created_at', 'updated_at']
inlines = [OrderItemInline]
date_hierarchy = 'created_at'
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')
```
---
## 8. Production Deployment (MEDIUM)
### Security Settings
```python
# settings/prod.py
DEBUG = False
ALLOWED_HOSTS = ['example.com', 'www.example.com']
CSRF_TRUSTED_ORIGINS = ['https://example.com']
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
```
### Deployment Stack
```
Nginx → Gunicorn → Django
PostgreSQL + Redis (cache)
Celery (background tasks)
```
```bash
gunicorn config.wsgi:application \
--bind 0.0.0.0:8000 \
--workers 4 \
--timeout 120 \
--access-logfile -
```
### WhiteNoise for Static Files
```python
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # right after Security
...
]
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
```
### Rules
```
✅ Gunicorn + Nginx (or Cloud Run / Railway)
✅ PostgreSQL (not SQLite)
✅ python manage.py check --deploy
✅ Sentry for error tracking
❌ Never use runserver in production
❌ Never use DEBUG=True in production
❌ Never use SQLite in production
```
---
## Anti-Patterns
| # | ❌ Don't | ✅ Do Instead |
|---|---------|--------------|
| 1 | Business logic in views | Service layer (`services.py`) |
| 2 | One giant app | App-per-domain |
| 3 | Default User model | Custom User before first migrate |
| 4 | No `select_related` | Always eager-load related objects |
| 5 | Django fixtures for tests | `factory_boy` factories |
| 6 | `settings.py` single file | Split: base + dev + prod |
| 7 | `runserver` in production | Gunicorn + Nginx |
| 8 | SQLite in production | PostgreSQL |
| 9 | `ModelSerializer` for writes | Explicit input serializer |
| 10 | Raw SQL in views | ORM querysets + `selectors.py` |
---
## Common Issues
### Issue 1: "Can't change User model after first migration"
**Fix:** If starting fresh: delete all migrations + DB, set custom User, re-migrate. If data exists: complex migration (use `django-allauth` or incremental field migration).
### Issue 2: "Serializer is too slow on large querysets"
**Fix:** Missing `select_related` / `prefetch_related` → N+1 queries.
```python
queryset = Order.objects.select_related('user').prefetch_related('items')
```
### Issue 3: "Circular import between apps"
**Fix:** Use string references: `models.ForeignKey('orders.Order', ...)` instead of importing the model class. For services, import inside the function.

684
skills/references/edit.md Normal file
View File

@@ -0,0 +1,684 @@
# Minimal-Invasive Editing of Existing xlsx
Make precise, surgical changes to existing xlsx files while preserving everything you do not touch: styles, macros, pivot tables, charts, sparklines, named ranges, data validation, conditional formatting, and all other embedded content.
---
## 1. When to Use This Path
Use the edit (unpack → XML edit → pack) path whenever the task involves **modifying an existing xlsx file**:
- Template filling — populating designated input cells with values or formulas
- Data updates — replacing outdated numbers, text, or dates in a live file
- Content corrections — fixing wrong values, broken formulas, or mistyped labels
- Adding new data rows to an existing table
- Renaming a sheet
- Applying a new style to specific cells
Do NOT use this path for creating a brand-new workbook from scratch. For that, see `create.md`.
---
## 2. Why openpyxl round-trip Is Forbidden for Existing Files
openpyxl `load_workbook()` followed by `workbook.save()` is a **destructive operation** on any file that contains advanced features. The library silently drops content it does not understand:
| Feature | openpyxl behavior | Consequence |
|---------|-------------------|-------------|
| VBA macros (`vbaProject.bin`) | Dropped entirely | All automation is lost; file saved as `.xlsx` not `.xlsm` |
| Pivot tables (`xl/pivotTables/`) | Dropped | Interactive analysis is destroyed |
| Slicers | Dropped | Filter UI is lost |
| Sparklines (`<sparklineGroups>`) | Dropped | In-cell mini-charts disappear |
| Chart formatting details | Partially lost | Series colors, custom axes may revert |
| Print area / page breaks | Sometimes lost | Print layout changes |
| Custom XML parts | Dropped | Third-party data bindings broken |
| Theme-linked colors | May be de-themed | Colors converted to absolute, breaking theme switching |
Even on a "plain" file without these features, openpyxl may normalize whitespace in XML that Excel relies on, alter namespace declarations, or reset `calcMode` flags.
**The rule is absolute: never open an existing file with openpyxl for the purpose of re-saving it.**
The XML direct-edit approach is safe because it operates on the raw bytes. You only change the nodes you touch. Everything else is byte-equivalent to the original.
---
## 3. Standard Operating Procedure
### Step 1 — Unpack
```bash
python3 SKILL_DIR/scripts/xlsx_unpack.py input.xlsx /tmp/xlsx_work/
```
The script unzips the xlsx, pretty-prints every XML and `.rels` file, and prints a categorized inventory of key files plus a warning if high-risk content is detected (VBA, pivot tables, charts).
Read the printed output carefully before proceeding. If the script reports `xl/vbaProject.bin` or `xl/pivotTables/`, follow the constraints in Section 7.
### Step 2 — Reconnaissance
Map the structure before touching anything.
**Identify sheet names and their XML files:**
```
xl/workbook.xml → <sheet name="Revenue" sheetId="1" r:id="rId1"/>
xl/_rels/workbook.xml.rels → <Relationship Id="rId1" Target="worksheets/sheet1.xml"/>
```
The sheet named "Revenue" lives in `xl/worksheets/sheet1.xml`. Always resolve this mapping before editing a worksheet.
**Understand the shared strings table:**
```bash
# Count existing entries in xl/sharedStrings.xml
grep -c "<si>" /tmp/xlsx_work/xl/sharedStrings.xml
```
Every text cell uses a zero-based index into this table. Know the current count before appending.
**Understand the styles table:**
```bash
# Count existing cellXfs entries
grep -c "<xf " /tmp/xlsx_work/xl/styles.xml
```
New style slots are appended after existing ones. The index of the first new slot = current count.
**Scan for high-risk XML regions in the target worksheet:**
Look for these elements in the target `sheet*.xml` before editing:
- `<mergeCell>` — merged cell ranges; row/column insertion shifts these
- `<conditionalFormatting>` — condition ranges; row/column insertion shifts these
- `<dataValidations>` — validation ranges; row/column insertion shifts these
- `<tableParts>` — table definitions; row insertion inside a table needs `<tableColumn>` updates
- `<sparklineGroups>` — sparklines; preserve without modification
### Step 3 — Map Intent to Minimal XML Changes
Before writing a single character, produce a written list of exactly which XML nodes change. This prevents scope creep.
| User intent | Files to change | Nodes to change |
|-------------|----------------|-----------------|
| Change a cell's numeric value | `xl/worksheets/sheetN.xml` | `<v>` inside target `<c>` |
| Change a cell's text | `xl/sharedStrings.xml` (append) + `xl/worksheets/sheetN.xml` | New `<si>`, update cell `<v>` index |
| Change a cell's formula | `xl/worksheets/sheetN.xml` | `<f>` text inside target `<c>` |
| Add a new data row at the bottom | `xl/worksheets/sheetN.xml` + possibly `xl/sharedStrings.xml` | Append `<row>` element |
| Apply a new style to cells | `xl/styles.xml` + `xl/worksheets/sheetN.xml` | Append `<xf>` in `<cellXfs>`, update `s` attribute on `<c>` |
| Rename a sheet | `xl/workbook.xml` | `name` attribute on `<sheet>` element |
| Rename a sheet (with cross-sheet formulas) | `xl/workbook.xml` + all `xl/worksheets/*.xml` | `name` attribute + `<f>` text referencing old name |
### Step 4 — Execute Changes
Use the Edit tool. Edit the minimum. Never rewrite whole files.
See Section 4 for precise XML patterns for each operation type.
### Step 5 — Cascade Check
After any change that shifts row or column positions, audit all affected XML regions. See Section 5.
### Step 6 — Pack and Validate
```bash
python3 SKILL_DIR/scripts/xlsx_pack.py /tmp/xlsx_work/ output.xlsx
python3 SKILL_DIR/scripts/formula_check.py output.xlsx
```
The pack script validates XML well-formedness before creating the ZIP. Fix any reported parse errors before packing. After packing, run `formula_check.py` to confirm no formula errors were introduced.
---
## 4. Precise XML Patterns for Common Edits
### 4.1 Changing a Numeric Cell Value
Find the `<c r="B5">` element in the worksheet XML and replace the `<v>` text.
**Before:**
```xml
<c r="B5">
<v>1000</v>
</c>
```
**After (new value 1500):**
```xml
<c r="B5">
<v>1500</v>
</c>
```
Rules:
- Do not add or remove the `s` attribute (style) unless explicitly changing the style.
- Do not add a `t` attribute — numbers omit `t` or use `t="n"`.
- Do not change the `r` attribute (cell reference).
---
### 4.2 Changing a Text Cell Value
Text cells reference the shared strings table by index (`t="s"`). You cannot edit the string in-place without affecting every other cell that uses the same index. The safe approach is to append a new entry.
**Before — shared strings file (`xl/sharedStrings.xml`):**
```xml
<sst count="4" uniqueCount="4">
<si><t>Revenue</t></si>
<si><t>Cost</t></si>
<si><t>Margin</t></si>
<si><t>Old Label</t></si>
</sst>
```
**After — append new string, increment counts:**
```xml
<sst count="5" uniqueCount="5">
<si><t>Revenue</t></si>
<si><t>Cost</t></si>
<si><t>Margin</t></si>
<si><t>Old Label</t></si>
<si><t>New Label</t></si>
</sst>
```
New string is at index 4 (zero-based).
**Before — cell in worksheet XML:**
```xml
<c r="A7" t="s">
<v>3</v>
</c>
```
**After — point to new index:**
```xml
<c r="A7" t="s">
<v>4</v>
</c>
```
Rules:
- Never modify or delete existing `<si>` entries. Only append.
- Both `count` and `uniqueCount` must be incremented together.
- If the new string contains `&`, `<`, or `>`, escape them: `&amp;`, `&lt;`, `&gt;`.
- If the string has leading or trailing spaces, add `xml:space="preserve"` to `<t>`:
```xml
<si><t xml:space="preserve"> indented text </t></si>
```
---
### 4.3 Changing a Formula
Formulas are stored in `<f>` elements **without a leading `=`** (unlike what you type in Excel's UI).
**Before:**
```xml
<c r="C10">
<f>SUM(C2:C9)</f>
<v>4800</v>
</c>
```
**After (extended range):**
```xml
<c r="C10">
<f>SUM(C2:C11)</f>
<v></v>
</c>
```
Rules:
- Clear `<v>` to an empty string when changing the formula. The cached value is now stale.
- Do not add `t="s"` or any type attribute to formula cells. The `t` attribute is absent or uses a result-type value, not a formula marker.
- Cross-sheet references use `SheetName!CellRef`. If the sheet name contains spaces, wrap in single quotes: `'Q1 Data'!B5`.
- The `<f>` text must not include the leading `=`.
**Before (converting a hardcoded value to a live formula):**
```xml
<c r="D15">
<v>95000</v>
</c>
```
**After:**
```xml
<c r="D15">
<f>SUM(D2:D14)</f>
<v></v>
</c>
```
---
### 4.4 Adding a New Data Row
Append after the last `<row>` element inside `<sheetData>`. Row numbers in OOXML are 1-based and must be sequential.
**Before (last row is row 10):**
```xml
<row r="10">
<c r="A10" t="s"><v>3</v></c>
<c r="B10"><v>2023</v></c>
<c r="C10"><v>88000</v></c>
<c r="D10"><f>C10*1.1</f><v></v></c>
</row>
</sheetData>
```
**After (new row 11 appended):**
```xml
<row r="10">
<c r="A10" t="s"><v>3</v></c>
<c r="B10"><v>2023</v></c>
<c r="C10"><v>88000</v></c>
<c r="D10"><f>C10*1.1</f><v></v></c>
</row>
<row r="11">
<c r="A11" t="s"><v>4</v></c>
<c r="B11"><v>2024</v></c>
<c r="C11"><v>96000</v></c>
<c r="D11"><f>C11*1.1</f><v></v></c>
</row>
</sheetData>
```
Rules:
- Every `<c>` inside the row must have `r` set to the correct cell address (e.g., `A11`).
- Text cells need `t="s"` and a sharedStrings index in `<v>`. Numeric cells omit `t`.
- Formula cells use `<f>` and an empty `<v>`.
- Copy the `s` attribute from the row above if you want matching styles. Do not invent a style index that does not exist in `styles.xml`.
- If the sheet contains a `<dimension>` element (e.g., `<dimension ref="A1:D10"/>`), update it to include the new row: `<dimension ref="A1:D11"/>`.
- If the sheet contains a `<tableparts>` referencing a table, update the table's `ref` attribute in the corresponding `xl/tables/tableN.xml` file.
---
### 4.5 Adding a New Column
Append new `<c>` elements to each existing `<row>` and, if present, update the `<cols>` section.
**Before (rows have columns AC):**
```xml
<cols>
<col min="1" max="3" width="14" customWidth="1"/>
</cols>
<sheetData>
<row r="1">
<c r="A1" t="s"><v>0</v></c>
<c r="B1" t="s"><v>1</v></c>
<c r="C1" t="s"><v>2</v></c>
</row>
<row r="2">
<c r="A2"><v>100</v></c>
<c r="B2"><v>200</v></c>
<c r="C2"><v>300</v></c>
</row>
</sheetData>
```
**After (adding column D):**
```xml
<cols>
<col min="1" max="3" width="14" customWidth="1"/>
<col min="4" max="4" width="14" customWidth="1"/>
</cols>
<sheetData>
<row r="1">
<c r="A1" t="s"><v>0</v></c>
<c r="B1" t="s"><v>1</v></c>
<c r="C1" t="s"><v>2</v></c>
<c r="D1" t="s"><v>5</v></c>
</row>
<row r="2">
<c r="A2"><v>100</v></c>
<c r="B2"><v>200</v></c>
<c r="C2"><v>300</v></c>
<c r="D2"><f>A2+B2+C2</f><v></v></c>
</row>
</sheetData>
```
Rules:
- Adding a column at the end (after the last existing column) is safe — no existing formula references shift.
- Inserting a column in the middle shifts all columns to the right, which requires the same cascade updates as row insertion (see Section 5).
- Update the `<dimension>` element if present.
---
### 4.6 Modifying or Adding Styles
Styles use a multi-level indirect reference chain. Read `ooxml-cheatsheet.md` for the full chain. The key rule: **only append new entries, never modify existing ones**.
**Scenario:** Add a blue-font style (for hardcoded input cells) that doesn't yet exist.
**Step 1 — Check if a matching font already exists in `xl/styles.xml`:**
```xml
<!-- Look inside <fonts> for an existing blue font -->
<font>
<color rgb="000000FF"/>
<!-- other attributes -->
</font>
```
If found, note its index (zero-based position in the `<fonts>` list). If not found, append.
**Step 2 — Append the new font if needed:**
Before:
```xml
<fonts count="3">
<font>...</font> <!-- index 0 -->
<font>...</font> <!-- index 1 -->
<font>...</font> <!-- index 2 -->
</fonts>
```
After:
```xml
<fonts count="4">
<font>...</font> <!-- index 0 -->
<font>...</font> <!-- index 1 -->
<font>...</font> <!-- index 2 -->
<font>
<b/>
<sz val="11"/>
<color rgb="000000FF"/>
<name val="Calibri"/>
</font> <!-- index 3 (new) -->
</fonts>
```
**Step 3 — Append a new `<xf>` in `<cellXfs>`:**
Before:
```xml
<cellXfs count="5">
<xf .../> <!-- index 0 -->
<xf .../> <!-- index 1 -->
<xf .../> <!-- index 2 -->
<xf .../> <!-- index 3 -->
<xf .../> <!-- index 4 -->
</cellXfs>
```
After:
```xml
<cellXfs count="6">
<xf .../> <!-- index 0 -->
<xf .../> <!-- index 1 -->
<xf .../> <!-- index 2 -->
<xf .../> <!-- index 3 -->
<xf .../> <!-- index 4 -->
<xf numFmtId="0" fontId="3" fillId="0" borderId="0" xfId="0"
applyFont="1"/> <!-- index 5 (new) -->
</cellXfs>
```
**Step 4 — Apply to target cells:**
Before:
```xml
<c r="B3">
<v>0.08</v>
</c>
```
After:
```xml
<c r="B3" s="5">
<v>0.08</v>
</c>
```
Rules:
- Never delete or reorder existing entries in `<fonts>`, `<fills>`, `<borders>`, `<cellXfs>`.
- Always update the `count` attribute when appending.
- The new `cellXfs` index = the old `count` value before appending (zero-based: if count was 5, new index is 5).
- Custom `numFmt` IDs must be 164 or above. IDs 0163 are built-in and must not be re-declared.
- If the desired style already exists elsewhere in the file (on a similar cell), reuse its `s` index rather than creating a duplicate.
---
### 4.7 Renaming a Sheet
**Only `xl/workbook.xml` needs to change** — unless cross-sheet formulas reference the old name.
**Before (`xl/workbook.xml`):**
```xml
<sheet name="Sheet1" sheetId="1" r:id="rId1"/>
```
**After:**
```xml
<sheet name="Revenue" sheetId="1" r:id="rId1"/>
```
**If any formula in any worksheet references the old name, update those too:**
Before (`xl/worksheets/sheet2.xml`):
```xml
<c r="B5"><f>Sheet1!C10</f><v></v></c>
```
After:
```xml
<c r="B5"><f>Revenue!C10</f><v></v></c>
```
If the new name contains spaces:
```xml
<c r="B5"><f>'Q1 Revenue'!C10</f><v></v></c>
```
Scan all worksheet XML files for the old name:
```bash
grep -r "Sheet1!" /tmp/xlsx_work/xl/worksheets/
```
Rules:
- The `.rels` file and `[Content_Types].xml` do NOT need to change — they reference the XML file path, not the sheet name.
- `sheetId` must not change; it is a stable internal identifier.
- Sheet names are case-sensitive in formula references.
---
## 5. High-Risk Operations — Cascade Effects
### 5.1 Inserting a Row in the Middle
Inserting a row at position N shifts all rows from N downward. Every reference to those rows in every XML file must be updated.
**Files to check and update:**
| XML region | What to update | Example shift |
|------------|---------------|---------------|
| Worksheet `<row r="...">` attributes | Increment row number for all rows >= N | `r="7"` → `r="8"` |
| All `<c r="...">` within those rows | Increment row number in cell address | `r="A7"` → `r="A8"` |
| All `<f>` formula text in any sheet | Shift absolute row references >= N | `B7` → `B8` |
| `<mergeCell ref="...">` | Shift start and end rows | `A7:C7` → `A8:C8` |
| `<conditionalFormatting sqref="...">` | Shift range | `A5:D20` → `A5:D21` |
| `<dataValidations sqref="...">` | Shift range | `B6:B50` → `B7:B51` |
| `xl/charts/chartN.xml` data source ranges | Shift series ranges | `Sheet1!$B$5:$B$20` → `Sheet1!$B$6:$B$21` |
| `xl/pivotTables/*.xml` source ranges | Shift source data range | Handle with extreme care — see Section 7 |
| `<dimension ref="...">` | Expand to include new extent | `A1:D20` → `A1:D21` |
| `xl/tables/tableN.xml` `ref` attribute | Expand table boundary | `A1:D20` → `A1:D21` |
**Do not attempt row insertion manually in large or formula-heavy files.** Use the dedicated shift script instead:
```bash
# Insert 1 row at row 5: all rows 5 and below shift down by 1
python3 SKILL_DIR/scripts/xlsx_shift_rows.py /tmp/xlsx_work/ insert 5 1
# Delete 1 row at row 8: all rows 9 and above shift up by 1
python3 SKILL_DIR/scripts/xlsx_shift_rows.py /tmp/xlsx_work/ delete 8 1
```
The script updates in one pass: `<row r="...">` attributes, `<c r="...">` cell addresses, all `<f>` formula text across every worksheet, `<mergeCell>` ranges, `<conditionalFormatting sqref="...">`, `<dataValidation sqref="...">`, `<dimension ref="...">`, table `ref` attributes in `xl/tables/`, chart series ranges in `xl/charts/`, and pivot cache source ranges in `xl/pivotCaches/`.
**After running the shift script, always repack and validate:**
```bash
python3 SKILL_DIR/scripts/xlsx_pack.py /tmp/xlsx_work/ output.xlsx
python3 SKILL_DIR/scripts/formula_check.py output.xlsx
```
**What the script does NOT update (review manually):**
- Named ranges in `xl/workbook.xml` `<definedNames>` — check and update if they reference shifted rows.
- Structured table references (`Table[@Column]`) inside formulas.
- External workbook links in `xl/externalLinks/`.
### 5.2 Inserting a Column in the Middle
Same cascade logic as row insertion, but for columns. Column references in formulas (`B`, `$C`, etc.) and in merged cell ranges, conditional formatting ranges, and chart data sources all need updating.
Column letter shifting is harder to automate safely. Prefer **appending columns at the end** whenever possible.
### 5.3 Deleting a Row or Column
Deletion is more dangerous than insertion because any formula that referenced a deleted row or column will become `#REF!`. Before deleting:
1. Search all `<f>` elements for references to the deleted range.
2. If any formula references a cell in the deleted row/column, do not delete — instead, either clear the row's data or consult the user.
3. After deletion, shift all references to rows/columns beyond the deletion point downward/leftward.
---
## 6. Template Filling — Identifying and Populating Input Cells
Templates designate certain cells as input zones. Common patterns to recognize them:
### 6.1 How Templates Signal Input Zones
| Signal | XML manifestation | What to look for |
|--------|-------------------|-----------------|
| Blue font color | `s` attribute pointing to a `cellXfs` entry with `fontId` → `<color rgb="000000FF"/>` | Check `styles.xml` to decode `s` values |
| Yellow fill (highlight) | `s` → `fillId` → `<fill><patternFill><fgColor rgb="00FFFF00"/>` | |
| Empty `<v>` element | `<c r="B5"><v></v></c>` or cell entirely absent from `<row>` | The cell has no value yet |
| Comment/annotation near cell | `xl/comments1.xml` with `ref="B5"` | Comments often label input fields |
| Named ranges | `xl/workbook.xml` `<definedName>` elements | Template may define `InputRevenue` etc. |
### 6.2 Filling a Template Cell
Do not change `s` attributes. Do not change `t` attributes unless you must change from empty to typed. Only change `<v>` or add `<f>`.
**Before (empty input cell with style preserved):**
```xml
<c r="C5" s="3">
<v></v>
</c>
```
**After (filled with a number, style unchanged):**
```xml
<c r="C5" s="3">
<v>125000</v>
</c>
```
**After (filled with text — requires shared string entry first):**
```xml
<!-- 1. Append to sharedStrings.xml: <si><t>North Region</t></si> at index 7 -->
<c r="C5" t="s" s="3">
<v>7</v>
</c>
```
**After (filled with a formula, preserving style):**
```xml
<c r="C5" s="3">
<f>Assumptions!D12</f>
<v></v>
</c>
```
### 6.3 Locating Input Zones Without Opening the File in Excel
After unpacking, decode the style index on suspected input cells to determine if they have the template's input color:
1. Note the `s` value on the cell (e.g., `s="4"`).
2. In `xl/styles.xml`, find `<cellXfs>` and look at the 5th entry (index 4).
3. Note its `fontId` (e.g., `fontId="2"`).
4. In `<fonts>`, look at the 3rd entry (index 2) and check for `<color rgb="000000FF"/>` (blue) or other input marker.
If the template uses named ranges as input fields, read them from `xl/workbook.xml`:
```xml
<definedNames>
<definedName name="InputGrowthRate">Assumptions!$B$5</definedName>
<definedName name="InputDiscountRate">Assumptions!$B$6</definedName>
</definedNames>
```
Fill the target cells (`Assumptions!B5`, `Assumptions!B6`) directly.
### 6.4 Template Filling Rules
- Fill only cells the template designated as inputs. Do not fill cells that are formula-driven.
- Do not apply new styles when filling. The template's formatting is the deliverable.
- Do not add or remove rows inside the template's data area unless the template explicitly has an "append here" zone.
- After filling, verify that no formula errors were introduced: some templates have input-validation formulas that produce `#VALUE!` if the wrong data type is entered.
---
## 7. Files You Must Never Modify
### 7.1 Absolute no-touch list
| File / location | Why |
|-----------------|-----|
| `xl/vbaProject.bin` | Binary VBA bytecode. Any byte modification corrupts the macro project. Editing even one bit makes the macros fail to load. |
| `xl/pivotCaches/pivotCacheDefinition*.xml` | The cache definition ties the pivot table to its source data. Editing it without also updating the corresponding `pivotTable*.xml` will corrupt the pivot. |
| `xl/pivotTables/*.xml` | Pivot table XML is tightly coupled with the cache definition and with internal state Excel rebuilds on load. Do not edit. If you shifted rows and the pivot's source range now points to wrong data, update only the `<cacheSource>` range in the cache definition, and only the `ref` attribute in the pivot table — no other changes. |
| `xl/slicers/*.xml` | Slicers are connected to specific cache IDs and pivot fields. Breaking these connections silently corrupts the file. |
| `xl/connections.xml` | External data connections. Editing breaks live data refresh. |
| `xl/externalLinks/` | External workbook links. The binary `.bin` files in here must not be modified. |
### 7.2 Conditionally safe files (update only specific attributes)
| File | What you may update | What to leave alone |
|------|--------------------|--------------------|
| `xl/charts/chartN.xml` | Data series range references (`<numRef><f>`) after a row/column shift | Chart type, formatting, layout |
| `xl/tables/tableN.xml` | `ref` attribute on `<table>` after adding rows | Column definitions, style info |
| `xl/pivotCaches/pivotCacheDefinition*.xml` | `ref` attribute on `<cacheSource><worksheetSource>` after shifting source data | All other content |
---
## 8. Validation After Every Edit
Never skip validation. Even a one-character change in a formula can cause cascading errors.
```bash
# Pack
python3 SKILL_DIR/scripts/xlsx_pack.py /tmp/xlsx_work/ output.xlsx
# Static formula validation (always run)
python3 SKILL_DIR/scripts/formula_check.py output.xlsx
# Dynamic validation (if LibreOffice available)
python3 SKILL_DIR/scripts/libreoffice_recalc.py output.xlsx /tmp/recalc.xlsx
python3 SKILL_DIR/scripts/formula_check.py /tmp/recalc.xlsx
```
If `formula_check.py` reports any error:
1. Unpack the output file again (it is the packed version).
2. Locate the reported cell in the worksheet XML.
3. Fix the `<f>` element.
4. Repack and re-validate.
Do not deliver the file until `formula_check.py` reports zero errors.
---
## 9. Absolute Rules Summary
| Rule | Rationale |
|------|-----------|
| Never use openpyxl `load_workbook` + `save` on an existing file | Round-trip destroys pivot tables, VBA, sparklines, slicers |
| Never delete or reorder existing `<si>` entries in sharedStrings | Breaks every cell referencing that index |
| Never delete or reorder existing `<xf>` entries in `<cellXfs>` | Breaks every cell using that style index |
| Never modify `vbaProject.bin` | Binary file; any change corrupts VBA |
| Never change `sheetId` when renaming a sheet | Internal ID is stable; changing it breaks relationships |
| Never skip post-edit validation | Leaves broken references undetected |
| Never edit more XML nodes than required | Extra changes risk introducing subtle corruption |
| Clear `<v>` to empty string when changing a formula | Prevents stale cached value from misleading downstream consumers |
| Append-only to sharedStrings | Existing indexes must remain valid |
| Append-only to styles collections | Existing style indexes must remain valid |

View File

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

View File

@@ -0,0 +1,162 @@
# Editing Existing Presentations
## Template-Based Workflow
When using an existing presentation as a template:
1. **Copy and analyze**:
```bash
cp /path/to/user-provided.pptx template.pptx
python -m markitdown template.pptx > template.md
```
Review `template.md` to see placeholder text and slide structure.
2. **Plan slide mapping**: For each content section, choose a template slide.
**USE VARIED LAYOUTS** — monotonous presentations are a common failure mode. Don't default to basic title + bullet slides. Actively seek out:
- Multi-column layouts (2-column, 3-column)
- Image + text combinations
- Full-bleed images with text overlay
- Quote or callout slides
- Section dividers
- Stat/number callouts
- Icon grids or icon + text rows
**Avoid:** Repeating the same text-heavy layout for every slide.
Match content type to layout style (e.g., key points -> bullet slide, team info -> multi-column, testimonials -> quote slide).
3. **Unpack**: Extract the PPTX into an editable XML tree using Python's `zipfile` module. Pretty-print the XML for readability.
4. **Build presentation** (do this yourself, not with subagents):
- Delete unwanted slides (remove from `<p:sldIdLst>`)
- Duplicate slides you want to reuse (copy slide XML, relationships, and update `Content_Types.xml` and `presentation.xml`)
- Reorder slides in `<p:sldIdLst>`
- **Complete all structural changes before step 5**
5. **Edit content**: Update text in each `slide{N}.xml`.
**Use subagents here if available** — slides are separate XML files, so subagents can edit in parallel.
6. **Clean**: Remove orphaned files — slides not in `<p:sldIdLst>`, unreferenced media, orphaned rels.
7. **Pack**: Repack the XML tree into a PPTX file. Validate, repair, condense XML, re-encode smart quotes.
Always write to `/tmp/` first, then copy to the final path. Python's `zipfile` module uses `seek` internally, which fails on some volume mounts (e.g. Docker bind mounts). Writing to a local temp path avoids this.
## Output Structure
Copy the user-provided file to `template.pptx` in cwd. This preserves the original and gives a predictable name for all downstream operations.
```bash
cp /path/to/user-provided.pptx template.pptx
```
```text
./
├── template.pptx # Copy of user-provided file (never modified)
├── template.md # markitdown extraction
├── unpacked/ # Editable XML tree
└── edited.pptx # Final repacked deck
```
Minimum expected deliverable: `edited.pptx`.
## Slide Operations
Slide order is in `ppt/presentation.xml` -> `<p:sldIdLst>`.
**Reorder**: Rearrange `<p:sldId>` elements.
**Delete**: Remove `<p:sldId>`, then clean orphaned files.
**Add**: Copy the source slide's XML file, its `.rels` file, and update `Content_Types.xml` and `presentation.xml`. Never manually copy slide files without updating all references — this causes broken notes references and missing relationship IDs.
## Editing Content
**Subagents:** If available, use them here (after completing step 4). Each slide is a separate XML file, so subagents can edit in parallel. In your prompt to subagents, include:
- The slide file path(s) to edit
- **"Use the Edit tool for all changes"**
- The formatting rules and common pitfalls below
For each slide:
1. Read the slide's XML
2. Identify ALL placeholder content — text, images, charts, icons, captions
3. Replace each placeholder with final content
**Use the Edit tool, not sed or Python scripts.** The Edit tool forces specificity about what to replace and where, yielding better reliability.
## Formatting Rules
- **Bold all headers, subheadings, and inline labels**: Use `b="1"` on `<a:rPr>`. This includes:
- Slide titles
- Section headers within a slide
- Inline labels like (e.g.: "Status:", "Description:") at the start of a line
- **Never use unicode bullets**: Use proper list formatting with `<a:buChar>` or `<a:buAutoNum>`
- **Bullet consistency**: Let bullets inherit from the layout. Only specify `<a:buChar>` or `<a:buNone>`.
## Common Pitfalls — Template Editing
### Template Adaptation
When source content has fewer items than the template:
- **Remove excess elements entirely** (images, shapes, text boxes), don't just clear text
- Check for orphaned visuals after clearing text content
- Run content QA with `markitdown` to catch mismatched counts
When replacing text with different length content:
- **Shorter replacements**: Usually safe
- **Longer replacements**: May overflow or wrap unexpectedly
- Verify with `markitdown` after text changes
- Consider truncating or splitting content to fit the template's design constraints
**Template slots != Source items**: If template has 4 team members but source has 3 users, delete the 4th member's entire group (image + text boxes), not just the text.
### Multi-Item Content
If source has multiple items (numbered lists, multiple sections), create separate `<a:p>` elements for each — **never concatenate into one string**.
**WRONG** — all items in one paragraph:
```xml
<a:p>
<a:r><a:rPr .../><a:t>Step 1: Do the first thing. Step 2: Do the second thing.</a:t></a:r>
</a:p>
```
**CORRECT** — separate paragraphs with bold headers:
```xml
<a:p>
<a:pPr algn="l"><a:lnSpc><a:spcPts val="3919"/></a:lnSpc></a:pPr>
<a:r><a:rPr lang="en-US" sz="2799" b="1" .../><a:t>Step 1</a:t></a:r>
</a:p>
<a:p>
<a:pPr algn="l"><a:lnSpc><a:spcPts val="3919"/></a:lnSpc></a:pPr>
<a:r><a:rPr lang="en-US" sz="2799" .../><a:t>Do the first thing.</a:t></a:r>
</a:p>
<a:p>
<a:pPr algn="l"><a:lnSpc><a:spcPts val="3919"/></a:lnSpc></a:pPr>
<a:r><a:rPr lang="en-US" sz="2799" b="1" .../><a:t>Step 2</a:t></a:r>
</a:p>
<!-- continue pattern -->
```
Copy `<a:pPr>` from the original paragraph to preserve line spacing. Use `b="1"` on headers.
### Smart Quotes
The Edit tool converts smart quotes to ASCII. **When adding new text with quotes, use XML entities:**
```xml
<a:t>the &#x201C;Agreement&#x201D;</a:t>
```
| Character | Name | Unicode | XML Entity |
|-----------|------|---------|------------|
| \u201c | Left double quote | U+201C | `&#x201C;` |
| \u201d | Right double quote | U+201D | `&#x201D;` |
| \u2018 | Left single quote | U+2018 | `&#x2018;` |
| \u2019 | Right single quote | U+2019 | `&#x2019;` |
### Other
- **Whitespace**: Use `xml:space="preserve"` on `<a:t>` with leading/trailing spaces
- **XML parsing**: Use `defusedxml.minidom`, not `xml.etree.ElementTree` (corrupts namespaces)

View File

@@ -0,0 +1,33 @@
# Getting Started
## 1. Set API key
```bash
export MINIMAX_API_KEY="<paste-your-key-here>"
```
## 2. Install dependencies
```bash
pip install requests
# FFmpeg (optional, for audio post-processing)
# macOS:
brew install ffmpeg
# Ubuntu:
sudo apt install ffmpeg
```
## 3. Quick test
```bash
python scripts/minimax_tts.py "Hello world" -o test.mp3
```
If successful, you'll see `OK: xxxxx bytes -> test.mp3`.
## Next steps
- **Voice selection**: See [minimax-voice-catalog.md](minimax-voice-catalog.md)
- **TTS workflows**: See [minimax-tts-guide.md](minimax-tts-guide.md)
- **Troubleshooting**: See [troubleshooting.md](troubleshooting.md)

View File

@@ -0,0 +1,78 @@
# Environment & CORS Management
Patterns for managing environment variables, API URLs, and CORS configuration across frontend and backend stacks.
---
## Standard Environment Pattern
```
# .env.local (gitignored, for local dev)
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_WS_URL=ws://localhost:3001
# Staging (set in Vercel/CI)
NEXT_PUBLIC_API_URL=https://api-staging.example.com
# Production (set in Vercel/CI)
NEXT_PUBLIC_API_URL=https://api.example.com
```
---
## Environment Variable Rules
```
✅ API base URL from environment variable — NEVER hardcoded
✅ Prefix client-side vars with NEXT_PUBLIC_ (Next.js) or VITE_ (Vite)
✅ Backend URL = server-only env var (for SSR calls, not exposed to browser)
✅ CORS on backend: explicit list of allowed origins per environment
❌ Never use localhost URLs in production builds
❌ Never expose backend-only secrets with NEXT_PUBLIC_ prefix
❌ Never commit .env.local (commit .env.example with placeholders)
```
---
## CORS Configuration
```typescript
// Backend: environment-aware CORS
const ALLOWED_ORIGINS = {
development: ['http://localhost:3000', 'http://localhost:5173'],
staging: ['https://staging.example.com'],
production: ['https://example.com', 'https://www.example.com'],
};
app.use(cors({
origin: ALLOWED_ORIGINS[process.env.NODE_ENV || 'development'],
credentials: true, // needed for cookies (auth)
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
}));
```
---
## Common Issues
### Issue 1: "CORS error in browser but works in Postman"
**Cause:** CORS is a browser security feature. Postman/curl skip it.
**Fix:**
1. Backend must return `Access-Control-Allow-Origin: https://your-frontend.com`
2. For cookies/auth: `credentials: true` on both sides
3. Check that preflight `OPTIONS` request returns correct headers
### Issue 2: "Environment variable undefined in browser"
**Cause:** Missing `NEXT_PUBLIC_` or `VITE_` prefix for client-side access.
**Fix:** Client-side vars MUST have the framework prefix. Rebuild after adding new env vars (they are embedded at build time).
### Issue 3: "Works locally, fails in staging"
**Cause:** Different origins, missing CORS config for staging domain.
**Fix:** Add staging origin to `ALLOWED_ORIGINS`, verify env vars are set in deployment platform.

File diff suppressed because it is too large Load Diff

37
skills/references/fix.md Normal file
View File

@@ -0,0 +1,37 @@
# FIX — Repair Broken Formulas in an Existing xlsx
This is an EDIT task. You MUST preserve all original sheets and data. Never create a new workbook.
## Workflow
```bash
# Step 1: Identify errors
python3 SKILL_DIR/scripts/formula_check.py input.xlsx --json
# Step 2: Unpack
python3 SKILL_DIR/scripts/xlsx_unpack.py input.xlsx /tmp/xlsx_work/
# Step 3: Fix each broken <f> element in the worksheet XML using the Edit tool
# (see Error-to-Fix mapping below)
# Step 4: Pack and validate
python3 SKILL_DIR/scripts/xlsx_pack.py /tmp/xlsx_work/ output.xlsx
python3 SKILL_DIR/scripts/formula_check.py output.xlsx
```
## Error-to-Fix Mapping
| Error | Fix Strategy |
|-------|-------------|
| `#DIV/0!` | Wrap: `IFERROR(original_formula, "-")` |
| `#NAME?` | Fix misspelled function (e.g. `SUMM``SUM`) |
| `#REF!` | Reconstruct the broken reference |
| `#VALUE!` | Fix type mismatch |
For the full list of Excel error types and advanced diagnostics, see `validate.md`.
## Critical Rules
- The output MUST contain the same sheets as the input. Do NOT create a new workbook.
- Only modify the specific `<f>` elements that are broken — everything else must be untouched.
- After packing, always run `formula_check.py` to confirm all errors are resolved.

768
skills/references/format.md Normal file
View File

@@ -0,0 +1,768 @@
# Financial Formatting & Output Standards — Complete Agent Guide
> This document is the complete reference manual for the agent when applying professional financial formatting to xlsx files. All operations target direct XML surgery on `xl/styles.xml` without using openpyxl. Every operational step provides ready-to-use XML snippets.
---
## 1. When to Use This Path
This document (FORMAT path) applies to the following two scenarios:
**Scenario A — Dedicated Formatting of an Existing File**
The user provides an existing xlsx file and requests that financial modeling formatting standards be applied or unified. The starting point is to unpack the file, audit the existing `styles.xml`, then append missing styles and batch-update cell `s` attributes. No cell values or formulas are modified.
**Scenario B — Applying Format Standards After CREATE/EDIT**
After completing data entry or formula writing, formatting is applied as the final step. At this point, `styles.xml` may come from the minimal_xlsx template (which pre-defines 13 style slots) or from a user file. In either case, follow the principle of "append only, never modify existing xf entries."
**Not applicable**: Reading or analyzing file contents only (use the READ path); modifying formulas or data (use the EDIT path).
---
## 2. Financial Format Semantic System
### 2.1 Font Color = Cell Role (Color = Role)
The primary convention of financial modeling: **font color encodes the cell's role, not decoration**. A reviewer can glance at colors to determine which cells are adjustable parameters and which are model-calculated results. This is an industry-wide convention (followed by investment banks, the Big Four, and corporate finance teams).
| Role | Font Color | AARRGGBB | Use Case |
|------|-----------|----------|----------|
| Hard-coded input / assumption | Blue | `000000FF` | Growth rates, discount rates, tax rates, and other user-modifiable parameters |
| Formula / calculated result | Black | `00000000` | All cells containing a `<f>` element |
| Same-workbook cross-sheet reference | Green | `00008000` | Cells whose formula starts with `SheetName!` |
| External file link | Red | `00FF0000` | Cells whose formula contains `[FileName.xlsx]` (flagged as fragile links) |
| Label / text | Black (default) | theme color | Row labels, category headings |
| Key assumption requiring review | Blue font + yellow fill | Font `000000FF` / Fill `00FFFF00` | Provisional values, parameters pending confirmation |
**Decision tree**:
```
Does the cell contain a <f> element?
+-- Yes -> Does the formula start with [FileName]?
| +-- Yes -> Red (external link)
| +-- No -> Does the formula contain SheetName!?
| +-- Yes -> Green (cross-sheet reference)
| +-- No -> Black (same-sheet formula)
+-- No -> Is the value a user-adjustable parameter?
+-- Yes -> Blue (input/assumption)
+-- No -> Black default (label)
```
**Strictly prohibited**: Blue font + `<f>` element coexisting (color role contradiction — must be corrected).
### 2.2 Number Format Matrix
| Data Type | formatCode | numFmtId | Display Example | Applicable Scenario |
|-----------|-----------|----------|-----------------|---------------------|
| Standard currency (whole dollars) | `$#,##0;($#,##0);"-"` | 164 | $1,234 / ($1,234) / - | P&L, balance sheet amount rows |
| Standard currency (with cents) | `$#,##0.00;($#,##0.00);"-"` | 169 | $1,234.56 / ($1,234.56) / - | Unit prices, detailed costs |
| Thousands (K) | `#,##0,"K"` | 171 | 1,234K | Simplified display for management reports |
| Millions (M) | `#,##0,,"M"` | 172 | 1M | Macro-level summary rows |
| Percentage (1 decimal) | `0.0%` | 165 | 12.5% | Growth rates, gross margins |
| Percentage (2 decimals) | `0.00%` | 170 | 12.50% | IRR, precise interest rates |
| Multiple / valuation multiplier | `0.0x` | 166 | 8.5x | EV/EBITDA, P/E |
| Integer (thousands separator) | `#,##0` | 167 | 12,345 | Employee count, unit quantities |
| Year | `0` | 1 (built-in, no declaration needed) | 2024 | Column header years, prevents 2,024 |
| Date | `m/d/yyyy` | 14 (built-in, no declaration needed) | 3/21/2026 | Timelines |
| General text | General | 0 (built-in, no declaration needed) | — | Label rows, cells with no format requirement |
numFmtId 169172 are custom formats that need to be appended beyond the 4 formats (164167) pre-defined in the minimal_xlsx template. When appending, assign IDs according to the rules (see Section 3.4).
**Built-in format IDs do not need to be declared in `<numFmts>`** (IDs 0163 are built into Excel/LibreOffice; simply reference the numFmtId in `<xf>`):
| numFmtId | formatCode | Description |
|----------|-----------|-------------|
| 0 | General | General format |
| 1 | `0` | Integer, no thousands separator (use this ID for years) |
| 3 | `#,##0` | Thousands-separated integer (no decimals) |
| 9 | `0%` | Percentage integer |
| 10 | `0.00%` | Percentage with two decimals |
| 14 | `m/d/yyyy` | Short date |
### 2.3 Negative Number Display Standards
Financial reports have two mainstream conventions for negative numbers — choose one and **maintain consistency** throughout the entire workbook:
**Parenthetical style (investment banking standard, recommended for external deliverables)**
```
Positive: $1,234 Negative: ($1,234) Zero: -
formatCode: $#,##0;($#,##0);"-"
```
**Red minus sign style (suitable for internal operational analysis reports)**
```
Positive: $1,234 Negative: -$1,234 (red)
formatCode: $#,##0;[Red]-$#,##0;"-"
```
Rule: Once a style is determined, maintain it across the entire workbook. Do not mix two negative number display styles within the same workbook.
### 2.4 Zero Value Display Standards
In financial models, "0" and "no data" have different semantics and should be visually distinct:
| Scenario | Recommended Display | formatCode Third Segment |
|----------|-------------------|--------------------------|
| Sparse matrix (most rows have zero-value periods) | Dash `-` | `"-"` |
| Quantity counts (zero itself is meaningful) | `0` | `0` or omit |
| Placeholder row (explicitly empty) | Leave blank | Do not write to cell |
Four-segment format syntax: `positive format;negative format;zero value format;text format`
Zero as dash: `$#,##0;($#,##0);"-"`
Zero preserved as 0: `#,##0;(#,##0);0`
---
## 3. styles.xml Surgical Operations
### 3.1 Auditing Existing Styles: Understanding the cellXfs Indirect Reference Chain
A cell's `s` attribute points to a position index (0-based) in `cellXfs`, and each `<xf>` entry in `cellXfs` references its respective definition libraries through `fontId`, `fillId`, `borderId`, and `numFmtId`.
Reference chain diagram:
```
Cell <c s="6">
| Look up cellXfs by 0-based index
cellXfs[6] -> numFmtId="164" fontId="2" fillId="0" borderId="0"
| | | |
numFmts fonts[2] fills[0] borders[0]
id=164 color=00000000 (no fill) (no border)
$#,##0... black
```
Audit steps:
**Step 1**: Read `<numFmts>` and record all declared custom formats and their IDs:
```xml
<numFmts count="4">
<numFmt numFmtId="164" formatCode="$#,##0;($#,##0);&quot;-&quot;"/>
<numFmt numFmtId="165" formatCode="0.0%"/>
<numFmt numFmtId="166" formatCode="0.0x"/>
<numFmt numFmtId="167" formatCode="#,##0"/>
</numFmts>
```
Record: current maximum custom numFmtId = 167, next available ID = 168.
**Step 2**: Read `<fonts>` and list each `<font>` by 0-based index with its color and style:
```
fontId=0 -> No explicit color (theme default black)
fontId=1 -> color rgb="000000FF" (blue, input role)
fontId=2 -> color rgb="00000000" (black, formula role)
fontId=3 -> color rgb="00008000" (green, cross-sheet reference role)
fontId=4 -> <b/> + color rgb="00000000" (bold black, header)
```
**Step 3**: Read `<fills>` and confirm that fills[0] and fills[1] are spec-mandated reserved entries (never delete):
```
fillId=0 -> patternType="none" (spec-mandated)
fillId=1 -> patternType="gray125" (spec-mandated)
fillId=2 -> Yellow highlight (if present)
```
**Step 4**: Read `<cellXfs>` and list each `<xf>` entry by 0-based index with its combination:
```
index 0 -> numFmtId=0, fontId=0, fillId=0 -> Default style
index 1 -> numFmtId=0, fontId=1, fillId=0 -> Blue font general (input)
index 5 -> numFmtId=164, fontId=1, fillId=0 -> Blue font currency (currency input)
index 6 -> numFmtId=164, fontId=2, fillId=0 -> Black font currency (currency formula)
...
```
**Step 5**: Verify that all count attributes match the actual number of elements (count mismatches will cause Excel to refuse to open the file).
### 3.2 Safely Appending New Styles (Golden Rule: Append Only, Never Modify Existing xf)
**Never modify existing `<xf>` entries**. Modifications will affect all cells that already reference that index, breaking existing formatting. Only append new entries at the end.
Complete atomic operation sequence for appending new styles (all 5 steps must be executed):
**Step 1**: Determine if a new `<numFmt>` is needed
Built-in formats (ID 0163) skip this step. Custom formats are appended to the end of `<numFmts>`:
```xml
<numFmts count="5"> <!-- count +1 -->
<!-- Keep existing entries unchanged -->
<numFmt numFmtId="164" formatCode="$#,##0;($#,##0);&quot;-&quot;"/>
<numFmt numFmtId="165" formatCode="0.0%"/>
<numFmt numFmtId="166" formatCode="0.0x"/>
<numFmt numFmtId="167" formatCode="#,##0"/>
<!-- Newly appended -->
<numFmt numFmtId="168" formatCode="$#,##0.00;($#,##0.00);&quot;-&quot;"/>
</numFmts>
```
**Step 2**: Determine if a new `<font>` is needed
Check whether the existing fonts already contain a matching color+style combination. If not, append to the end of `<fonts>`:
```xml
<fonts count="6"> <!-- count +1 -->
<!-- Keep existing entries unchanged -->
...
<!-- Newly appended: red font (external link role), new fontId = 5 -->
<font>
<sz val="11"/>
<name val="Calibri"/>
<color rgb="00FF0000"/>
</font>
</fonts>
```
New fontId = the count value before appending (when original count=5, new fontId=5).
**Step 3**: Determine if a new `<fill>` is needed
If a new background color is needed, append to the end of `<fills>` (note: fills[0] and fills[1] must never be modified):
```xml
<fills count="4"> <!-- count +1 -->
<fill><patternFill patternType="none"/></fill> <!-- 0: spec-mandated -->
<fill><patternFill patternType="gray125"/></fill> <!-- 1: spec-mandated -->
<fill> <!-- 2: yellow highlight -->
<patternFill patternType="solid">
<fgColor rgb="00FFFF00"/>
<bgColor indexed="64"/>
</patternFill>
</fill>
<!-- Newly appended: light gray fill (projection period distinction), new fillId = 3 -->
<fill>
<patternFill patternType="solid">
<fgColor rgb="00D3D3D3"/>
<bgColor indexed="64"/>
</patternFill>
</fill>
</fills>
```
**Step 4**: Append a new `<xf>` combination at the end of `<cellXfs>`
```xml
<cellXfs count="14"> <!-- count +1 -->
<!-- Keep existing entries 0-12 unchanged -->
...
<!-- Newly appended index=13: currency with cents formula (black font + numFmtId=168) -->
<xf numFmtId="168" fontId="2" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
</cellXfs>
```
New style index = the count value before appending (when original count=13, new index=13).
**Step 5**: Record the new style index; subsequently set the `s` attribute of corresponding cells in the sheet XML to this value.
### 3.3 AARRGGBB Color Format Explanation
OOXML's `rgb` attribute uses **8-digit hexadecimal AARRGGBB** format (not HTML's 6-digit RRGGBB):
```
AA RR GG BB
| | | |
Alpha Red Green Blue
```
- Alpha channel: `00` = fully opaque (normal use value); `FF` = fully transparent (invisible, never use this)
- Financial color standards always use `00` as the Alpha prefix
| Color | AARRGGBB | Corresponding Role |
|-------|----------|-------------------|
| Blue (input) | `000000FF` | Hard-coded assumptions |
| Black (formula) | `00000000` | Calculated results |
| Green (cross-sheet reference) | `00008000` | Same-workbook cross-sheet |
| Red (external link) | `00FF0000` | References to other files |
| Yellow (review-required fill) | `00FFFF00` | Key assumption highlight |
| Light gray (projection period fill) | `00D3D3D3` | Distinguishing historical vs. forecast periods |
| White | `00FFFFFF` | Pure white fill |
**Common mistake**: Mistakenly writing HTML format `#0000FF` as `FF0000FF` (Alpha=FF makes the color fully transparent and invisible). Correct format: `000000FF`.
### 3.4 numFmtId Assignment Rules
```
ID 0-163 -> Excel/LibreOffice built-in formats, no declaration needed in <numFmts>, reference directly in <xf>
ID 164+ -> Custom formats, must be explicitly declared as <numFmt> elements in <numFmts>
```
Rules for assigning new IDs:
1. Read all `numFmtId` attribute values in the current `<numFmts>`
2. Take the maximum value + 1 as the next custom format ID
3. Do not reuse existing IDs; do not skip numbers
The minimal_xlsx template pre-defines IDs: 164, 165, 166, 167. The next available ID is 168.
---
## 4. Pre-defined Style Index Complete Reference Table (13 Slots)
The following are the 13 style slots (cellXfs index 012) pre-defined in the minimal_xlsx template's `styles.xml`, which can be directly referenced in the cell `s` attribute in sheet XML:
| Index | Semantic Role | Font Color | Fill | numFmtId | Format Display | Typical Use |
|-------|--------------|------------|------|----------|---------------|-------------|
| **0** | Default style | Theme black | None | 0 | General | Cells requiring no special formatting |
| **1** | Input / assumption (general) | Blue `000000FF` | None | 0 | General | Text-type assumptions, flags |
| **2** | Formula / calculated result (general) | Black `00000000` | None | 0 | General | Text concatenation formulas, non-numeric calculations |
| **3** | Cross-sheet reference (general) | Green `00008000` | None | 0 | General | Values pulled from cross-sheet (general format) |
| **4** | Header (bold) | Bold black | None | 0 | General | Row/column headings |
| **5** | Currency input | Blue `000000FF` | None | 164 | $1,234 / ($1,234) / - | Amount inputs in the assumptions area |
| **6** | Currency formula | Black `00000000` | None | 164 | $1,234 / ($1,234) / - | Amount calculations in the model area (revenue, EBITDA) |
| **7** | Percentage input | Blue `000000FF` | None | 165 | 12.5% | Rate inputs in the assumptions area (growth rate, gross margin assumptions) |
| **8** | Percentage formula | Black `00000000` | None | 165 | 12.5% | Rate calculations in the model area (actual gross margin) |
| **9** | Integer (comma) input | Blue `000000FF` | None | 167 | 12,345 | Quantity inputs in the assumptions area (employee count) |
| **10** | Integer (comma) formula | Black `00000000` | None | 167 | 12,345 | Quantity calculations in the model area |
| **11** | Year input | Blue `000000FF` | None | 1 | 2024 | Column header years (no thousands separator) |
| **12** | Key assumption highlight | Blue `000000FF` | Yellow `00FFFF00` | 0 | General | Key parameters pending review or confirmation |
**Selection guide**:
- Determine "input" vs. "formula" -> Choose odd-numbered (input/blue) or even-numbered (formula/black) paired slots
- Determine data type -> Choose the corresponding currency (5/6) / percentage (7/8) / integer (9/10) / year (11) slot
- Cross-sheet reference needing number format -> Append a new green + number format combination (see Section 5.4)
- Parameter pending review -> index 12
---
## 5. Assumption Separation Principle: XML-Level Implementation
### 5.1 Structural Design
Assumption separation principle: **Input assumptions are centralized in a dedicated area (sheet or block); the model calculation area contains only formulas, no hard-coded values**.
Recommended structure:
```
Workbook sheet layout
sheet 1 "Assumptions" -> All blue-font cells (style 1/5/7/9/11/12)
sheet 2 "Model" -> All black or green-font cells (style 2/3/4/6/8/10)
```
Same-sheet zoning approach for simple models:
```
Rows 1-5: [Assumptions block - blue font]
Row 6: [Empty row separator]
Rows 7+: [Model block - black/green font formulas referencing assumptions area]
```
### 5.2 Assumptions Area XML Example
```xml
<!-- Assumptions sheet (sheet1.xml) example -->
<!-- Row 1: Block title -->
<row r="1">
<c r="A1" s="4" t="inlineStr"><is><t>Model Assumptions</t></is></c>
</row>
<!-- Row 2: Growth rate assumption - blue font percentage input, s="7" -->
<row r="2">
<c r="A2" t="inlineStr"><is><t>Revenue Growth Rate</t></is></c>
<c r="B2" s="7"><v>0.08</v></c>
</row>
<!-- Row 3: Gross margin assumption - blue font percentage input, s="7" -->
<row r="3">
<c r="A3" t="inlineStr"><is><t>Gross Margin</t></is></c>
<c r="B3" s="7"><v>0.65</v></c>
</row>
<!-- Row 4: Base revenue - blue font currency input, s="5" -->
<row r="4">
<c r="A4" t="inlineStr"><is><t>Base Revenue (Year 0)</t></is></c>
<c r="B4" s="5"><v>1000000</v></c>
</row>
<!-- Row 5: Key assumption (pending review) - blue font yellow fill, s="12" -->
<row r="5">
<c r="A5" t="inlineStr"><is><t>Terminal Growth Rate</t></is></c>
<c r="B5" s="12"><v>0.03</v></c>
</row>
```
### 5.3 Model Area XML Example (Referencing Assumptions Area)
```xml
<!-- Model sheet (sheet2.xml) example -->
<!-- Row 1: Column headers (years) - bold header, s="4"; year cells, s="11" -->
<row r="1">
<c r="A1" s="4" t="inlineStr"><is><t>Metric</t></is></c>
<c r="B1" s="11"><v>2024</v></c>
<c r="C1" s="11"><v>2025</v></c>
<c r="D1" s="11"><v>2026</v></c>
</row>
<!-- Row 2: Revenue row -->
<row r="2">
<c r="A2" t="inlineStr"><is><t>Revenue</t></is></c>
<!-- B2: Base year revenue, cross-sheet reference from Assumptions, green, s="3" (general format) -->
<!-- If currency format is needed, append new style s="13" (see Section 5.4) -->
<c r="B2" s="3"><f>Assumptions!B4</f><v></v></c>
<!-- C2, D2: Next year revenue = prior year * (1 + growth rate), black font currency formula, s="6" -->
<c r="C2" s="6"><f>B2*(1+Assumptions!B2)</f><v></v></c>
<c r="D2" s="6"><f>C2*(1+Assumptions!B2)</f><v></v></c>
</row>
<!-- Row 3: Gross profit row - black font currency formula, s="6" -->
<row r="3">
<c r="A3" t="inlineStr"><is><t>Gross Profit</t></is></c>
<c r="B3" s="6"><f>B2*Assumptions!B3</f><v></v></c>
<c r="C3" s="6"><f>C2*Assumptions!B3</f><v></v></c>
<c r="D3" s="6"><f>D2*Assumptions!B3</f><v></v></c>
</row>
<!-- Row 4: Gross margin row - black font percentage formula, s="8" -->
<row r="4">
<c r="A4" t="inlineStr"><is><t>Gross Margin %</t></is></c>
<c r="B4" s="8"><f>B3/B2</f><v></v></c>
<c r="C4" s="8"><f>C3/C2</f><v></v></c>
<c r="D4" s="8"><f>D3/D2</f><v></v></c>
</row>
```
### 5.4 Appending "Green + Number Format" Combinations
Pre-defined index 3 is green font + general format. If a cross-sheet reference involves a currency amount, a green style with a number format must be appended:
```xml
<!-- Append at the end of <cellXfs> in styles.xml (assuming current count=13, new index=13) -->
<!-- index 13: cross-sheet reference + currency format (green font + $#,##0) -->
<xf numFmtId="164" fontId="3" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1"/>
<!-- Update count to 14 -->
```
After appending, cross-sheet reference currency cells use `s="13"`.
---
## 6. Complete Operational Workflow
### 6.1 Workflow Overview
```
[Existing xlsx or file after CREATE/EDIT]
|
Step 1: Unpack (extract to temporary directory)
|
Step 2: Audit styles.xml (review existing styles, build index mapping table)
|
Step 3: Audit sheet XML (identify cells needing formatting and their semantic roles)
|
Step 4: Append missing styles (numFmt -> font -> fill -> xf, update counts)
|
Step 5: Batch-update the s attribute of each cell in the sheet XML
|
Step 6: XML validity + style reference integrity verification
|
Step 7: Pack (recompress as xlsx)
```
### 6.2 Step 1 — Unpack
```bash
python3 SKILL_DIR/scripts/xlsx_unpack.py input.xlsx /tmp/xlsx_fmt/
```
If the script is unavailable, unpack manually:
```bash
mkdir -p /tmp/xlsx_fmt && cp input.xlsx /tmp/xlsx_fmt/input.xlsx
cd /tmp/xlsx_fmt && unzip input.xlsx -d unpacked/
```
### 6.3 Step 2 — Audit styles.xml
Execute according to the method in Section 3.1. Quick check for minimal_xlsx template initial state:
- `<cellXfs count="13">` and `<numFmts count="4">` -> Template initial state, all 13 pre-defined slots can be used directly
- Otherwise -> A complete review of the existing index mapping is required
### 6.4 Step 3 — Audit Sheet XML, Build Formatting Plan
Read `xl/worksheets/sheet*.xml` and evaluate each cell:
1. Does it contain a `<f>` element (formula)? -> Requires black/green/red style
2. Is it a hard-coded numeric parameter? -> Requires blue style
3. Is the data type currency/percentage/integer/year? -> Select the corresponding number format slot
4. Is it a header? -> Bold style (index 4)
Build a formatting mapping table: `{cell coordinate: target style index}`
### 6.5 Step 4 — Append Styles
Execute according to the atomic operation sequence in Section 3.2. Update the corresponding count attribute immediately after appending each component.
### 6.6 Step 5 — Batch-Update Cell s Attributes
```xml
<!-- Before formatting: no style -->
<c r="B5"><v>0.08</v></c>
<!-- After formatting: growth rate assumption, blue font percentage, s="7" -->
<c r="B5" s="7"><v>0.08</v></c>
```
```xml
<!-- Before formatting: formula without style -->
<c r="C10"><f>B10*(1+Assumptions!B2)</f><v></v></c>
<!-- After formatting: currency formula, black font, s="6" -->
<c r="C10" s="6"><f>B10*(1+Assumptions!B2)</f><v></v></c>
```
For consecutive rows of the same type, row-level default styles can be used to reduce repetition:
```xml
<!-- Entire row uses style=6, only override for exception cells -->
<row r="5" s="6" customFormat="1">
<c r="A5" s="0" t="inlineStr"><is><t>Operating Income</t></is></c> <!-- Text overridden to default -->
<c r="B5"><f>B3-B4</f><v></v></c> <!-- Inherits row-level s=6 -->
<c r="C5"><f>C3-C4</f><v></v></c>
</row>
```
### 6.7 Step 6 — Verification
```bash
# XML validity verification is handled automatically by xlsx_pack.py, no need to manually run xmllint
# The pack script validates styles.xml and sheet XML legality before packaging; it aborts and reports on errors
# Style audit (optional, audit the entire unpacked directory after formatting is complete)
python3 SKILL_DIR/scripts/style_audit.py /tmp/xlsx_fmt/unpacked/
# Formula error static scan (must specify a single .xlsx file, does not accept directories)
# Pack first, then scan:
python3 SKILL_DIR/scripts/xlsx_pack.py /tmp/xlsx_fmt/unpacked/ /tmp/output.xlsx
python3 SKILL_DIR/scripts/formula_check.py /tmp/output.xlsx
```
Manual style reference integrity check:
```bash
# Find the maximum s attribute value in the sheet XML
grep -o 's="[0-9]*"' /tmp/xlsx_fmt/unpacked/xl/worksheets/sheet1.xml \
| grep -o '[0-9]*' | sort -n | tail -1
# Compare with the cellXfs count attribute (max s value must be < count)
grep 'cellXfs count' /tmp/xlsx_fmt/unpacked/xl/styles.xml
```
### 6.8 Step 7 — Pack
```bash
python3 SKILL_DIR/scripts/xlsx_pack.py /tmp/xlsx_fmt/unpacked/ output.xlsx
```
If the script is unavailable, pack manually:
```bash
cd /tmp/xlsx_fmt/unpacked/
zip -r ../output.xlsx . -x "*.DS_Store"
```
---
## 7. Formatting Completeness Checklist
Verify each item before delivery:
### Color Role Consistency
- [ ] All numeric cells containing `<f>` elements: fontId corresponds to black (formula) or green (cross-sheet reference)
- [ ] All hard-coded numeric values that are user-adjustable parameters: fontId corresponds to blue (input)
- [ ] Cross-sheet references (formula contains `SheetName!`): fontId corresponds to green
- [ ] External file references (formula contains `[FileName.xlsx]`): fontId corresponds to red
- [ ] No cell simultaneously contains a `<f>` element and uses blue font (color role contradiction)
### Number Format Correctness
- [ ] Year columns: numFmtId="1" (`0` format), displays as 2024 not 2,024
- [ ] Currency rows: numFmtId="164" or variant, negative numbers display as ($1,234) not -$1,234
- [ ] Percentage rows: values stored as decimals (0.08 = 8%), format numFmtId="165", displays as 8.0%
- [ ] Zero values: displayed as `-` in sparse matrices rather than `0` (formatCode third segment contains `"-"`)
- [ ] Multiple rows (EV/EBITDA, etc.): numFmtId="166" (`0.0x` format)
- [ ] Negative number display style is consistent throughout the entire workbook (parenthetical or red minus sign)
### styles.xml Structural Integrity
- [ ] `<numFmts count>` = actual number of `<numFmt>` elements
- [ ] `<fonts count>` = actual number of `<font>` elements
- [ ] `<fills count>` = actual number of `<fill>` elements (including spec-mandated fills[0] and fills[1])
- [ ] `<cellXfs count>` = actual number of `<xf>` elements
- [ ] fills[0] is `patternType="none"`, fills[1] is `patternType="gray125"` (spec-mandated)
- [ ] All `<xf>` referenced fontId / fillId / borderId are within the valid range of their respective collections
- [ ] All cell `s` attribute values < `cellXfs count` (no out-of-bounds references)
### Assumption Separation Verification
- [ ] No black-font numeric cells in the assumptions area/sheet (black numeric = formula, should not be in assumptions)
- [ ] No blue-font non-year numeric cells in the model area/sheet (blue numeric = hard-coded, should be in assumptions)
- [ ] Input parameters in the model area reference the assumptions area via formulas, not by directly copying values
### Formula and Format Linkage
- [ ] All cells with `<f>` elements have an explicit `s` attribute (must not use default style=0, whose font color is not explicitly black)
- [ ] SUM summary rows: style uses black font + corresponding number format (e.g., s="6" for currency summaries)
- [ ] Percentage formulas: values stored as decimals, format is `0.0%`; do not multiply values by 100 before applying percentage format
### Visual Hierarchy
- [ ] Header rows (years/metric names): style=4 (bold black)
- [ ] Summary rows (Total/EBITDA/Net Income): bold + corresponding number format (append style if needed)
- [ ] Unit description rows (e.g., "$ thousands"): use style=0 or style=2 (blue not needed)
---
## 8. Prohibited Actions (What You Must NOT Do)
- **Do not modify existing `<xf>` entries**: This will batch-change the style of all cells referencing that index
- **Do not delete fills[0] and fills[1]**: Required by OOXML specification; deletion causes file corruption
- **Do not modify cell values or formulas**: The FORMAT path only changes styles, not content
- **Do not use openpyxl for formatting**: openpyxl rewrites the entire styles.xml on save, losing unsupported features
- **Do not apply global override styles**: Do not cover the entire workbook with a single style; assign precisely by semantic role
- **Do not write FF in the Alpha channel**: `rgb="FF0000FF"` makes the color fully transparent; the correct format is `rgb="000000FF"`
---
## 9. Common Errors and Fixes
### Error 1: Year displays as 2,024
Cause: The year cell's `s` attribute uses a format with thousands separator (e.g., numFmtId="3" or numFmtId="167").
```xml
<!-- Incorrect -->
<c r="B1" s="9"><v>2024</v></c>
<!-- Fix: Change to s="11" (numFmtId="1", format 0) -->
<c r="B1" s="11"><v>2024</v></c>
```
### Error 2: Percentage displays as 800% (value was multiplied by 100)
Cause: 8% was stored as `<v>8</v>` instead of `<v>0.08</v>`. Excel's `%` format automatically multiplies the value by 100 for display.
```xml
<!-- Incorrect -->
<c r="B2" s="7"><v>8</v></c>
<!-- Fix: Value must be stored in decimal form -->
<c r="B2" s="7"><v>0.08</v></c>
```
### Error 3: File corruption after appending styles without updating count
Cause: A `<font>` or `<xf>` element was appended but the count attribute was not updated; Excel reads beyond bounds using the old count.
Fix: Update the corresponding count immediately after appending each element:
```xml
<!-- After appending the 6th font, count must be changed from 5 to 6 -->
<fonts count="6">
...
</fonts>
```
### Error 4: Blue font + formula (color role contradiction)
Cause: A formula cell mistakenly uses an input style (e.g., s="5" for currency input).
```xml
<!-- Incorrect: Formula cell uses blue input style -->
<c r="C5" s="5"><f>B5*1.08</f><v></v></c>
<!-- Fix: Change formula cell to corresponding black formula style (5->6, 7->8, 9->10) -->
<c r="C5" s="6"><f>B5*1.08</f><v></v></c>
```
### Error 5: AARRGGBB color missing Alpha (only 6 digits)
```xml
<!-- Incorrect: 6-digit format, behavior depends on implementation, usually causes wrong color -->
<color rgb="0000FF"/>
<!-- Fix: Always use 8-digit AARRGGBB, Alpha fixed at 00 -->
<color rgb="000000FF"/>
```
### Error 6: Modifying existing xf (affects all cells referencing that index)
Cause: Directly modifying attributes of the Nth `<xf>` in cellXfs, causing all cells with `s="N"` to be batch-changed.
Fix: Keep existing entries unchanged, append a new entry at the end, and only change the `s` attribute of cells that need the new style to the new index:
```xml
<!-- Incorrect: Modified the existing xf at index=6 -->
<xf numFmtId="164" fontId="2" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1" applyAlignment="1">
<alignment horizontal="right"/> <!-- New attribute added, affects ALL cells already using s="6" -->
</xf>
<!-- Fix: Append new index (when original count=13, new index=13), only change the s attribute of cells needing right alignment -->
<!-- Keep index=6 as-is -->
<xf numFmtId="164" fontId="2" fillId="0" borderId="0" xfId="0"
applyFont="1" applyNumberFormat="1" applyAlignment="1">
<alignment horizontal="right"/>
</xf> <!-- New index=13 -->
```
---
## 10. Financial Model Structure Conventions
### 10.1 Header Rows
- Bold font (corresponds to style index 4 in this skill's template)
- Year columns: use number format `0` (numFmtId="1", no thousands separator) to prevent 2024 from displaying as 2,024
- A unit description row may be added below headers: gray or italic text, e.g., "$ thousands" or "% of Revenue"
### 10.2 Row Type Standards
| Row Type | Style Recommendation | Example |
|----------|---------------------|---------|
| Category heading row | Bold, optionally with fill color | "Revenue" |
| Line item row | Normal style | "Product A", "Product B" |
| Subtotal row | Bold + top border | "Total Revenue" |
| Operating metric row | Normal style | "Gross Margin %" |
| Separator row | Empty row | (empty) |
### 10.3 Multi-Year Model Column Layout
```
Col A: Label column (width 28, left-aligned text, s="4" for headers or s="0" for labels)
Col B: FY2022 Actual (width 12, year header s="11", data cells styled by semantic role)
Col C: FY2023 Actual
Col D: FY2024E (forecast period - can use light gray fill fillId=3 to differentiate)
Col E: FY2025E
Col F: FY2026E
```
### 10.4 Cross-Sheet Reference Patterns
Complete XML example of parameters passing from assumptions sheet to model sheet:
```xml
<!-- Assumptions sheet, cell B5: 8% growth rate, blue percentage input -->
<c r="B5" s="7"><v>0.08</v></c>
<!-- Model sheet, cell C10: references assumption area growth rate, green percentage formula -->
<!-- Requires appending index=13: green + percentage format (fontId=3, numFmtId=165) -->
<c r="C10" s="13"><f>Assumptions!B5</f><v></v></c>
```
---
## 11. Assumption Categories
In the assumptions area (Assumptions sheet or assumptions block), organize assumptions in the following standard order for ease of review and maintenance:
1. **Revenue assumptions**: Growth rates, pricing, sales volume
2. **Cost assumptions**: Gross margin, fixed/variable cost ratios
3. **Working capital**: DSO (Days Sales Outstanding), DPO (Days Payable Outstanding), inventory days
4. **Capital expenditures (CapEx)**: As a percentage of revenue or absolute amounts
5. **Financing assumptions**: Interest rates, debt repayment schedules
6. **Tax and other**: Effective tax rate, depreciation & amortization (D&A)
---
## 12. Audit Trail Best Practices
- Use `s="12"` (blue font + yellow fill highlight) to mark cells requiring review or pending changes, making them immediately visible to reviewers
- In sensitivity analysis rows or a separate Sensitivity tab, show the impact of +/-1% changes in key assumptions on results
- **Do not hide rows containing assumptions**: Assumption rows must be visible to reviewers; do not use the `hidden="1"` attribute
- Note a "Last Updated" date at the top of the assumptions area or in a dedicated cell, recording the last modification time of the model
---
## 13. Pre-Delivery Checklist (Common Financial Model Checklist)
Before outputting the final file, confirm each item:
- [ ] Formula rows contain no hard-coded values (can use `formula_check.py` to scan the packaged `.xlsx` file)
- [ ] Year columns display as 2024 not 2,024 (numFmtId="1", format `0`)
- [ ] Negative numbers display as (1,234) not -1,234 (use parenthetical style for externally delivered financial reports)
- [ ] Zero values display as `-` in sparse rows rather than `0` (formatCode third segment is `"-"`)
- [ ] Growth rates and percentages are stored as decimals (0.08 = 8%), format is `0.0%`
- [ ] All cross-sheet reference cells use green font (style index 3 or an appended green + number format combination)
- [ ] Assumptions block and model block are clearly separated (different sheets or separated by empty rows within the same sheet)
- [ ] Summary rows use `SUM()` formulas, not manually hard-coded totals
- [ ] Balance verification: summary rows = sum of their respective line items (a check row can be added at the end of the model to verify)

View File

@@ -0,0 +1,98 @@
# Full-Deck Templates
Self-contained multi-slide HTML decks under `templates/full-decks/<name>/`. Each folder contains:
- `index.html` — complete multi-slide deck (cover / section / content / code / chart or diagram / CTA / thanks, 7+ slides)
- `style.css` — scoped with `.tpl-<name>` class prefix so multiple templates can coexist
- `README.md` — short rationale, inspiration, and use guidance
All templates pull the shared `assets/fonts.css`, `assets/base.css`, and `assets/runtime.js` from the skill root. Navigate with `← →` / `space`, use `F` for fullscreen, `O` for overview.
Use these when you want a coherent, opinionated look for an entire deck — not a mix-and-match of layouts. Each template is visually distinctive enough to be identified at a glance.
---
## 1. xhs-white-editorial — 白底杂志风
- **Source inspiration:** `20260409 升级版知识库/小红书图文/v2-白底版/slide_01_cover.html` + `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
- **Key visual traits:** pure-white background, top 10-color rainbow bar, 80-110px display headlines, purple→blue→green→orange→pink gradient text, macaron soft-card set (soft-purple/pink/blue/green/orange), black-on-white `.focus` pills, hero quote box.
- **When to use:** dual-purpose XHS image + horizontal deck; dense text with strong emphasis; Chinese-first audience.
- **Path:** `templates/full-decks/xhs-white-editorial/index.html`
## 2. graphify-dark-graph — 暗底知识图谱
- **Source inspiration:** `20260413-graphify/ppt/graphify.html`
- **Key visual traits:** `#06060c→#0e1020` deep-night gradient, drifting blur orbs, SVG force-directed graph overlay on cover, rainbow-shift gradient headlines, JetBrains Mono command-line glow, glass-morphism cards (warm/blue/green/purple/danger). Accent palette: amber `#e8a87c`, mint `#7ed3a4`, mist-blue `#7eb8da`, lilac `#b8a4d6`.
- **When to use:** dev-tool / CLI / knowledge-graph / data-viz launches; live-demo decks that want an "AI-native + sci-fi + warm" vibe.
- **Path:** `templates/full-decks/graphify-dark-graph/index.html`
## 3. knowledge-arch-blueprint — 奶油蓝图架构
- **Source inspiration:** `20260405-Karpathy-知识库/20260405 架构图v2.html`
- **Key visual traits:** cream paper `#F0EAE0` base, single rust accent `#B5392A`, 48px blueprint grid mask, hard 2px black border cards, pipeline step-boxes with one hero raised, right-side rust insight callout, Playfair serif big numbers, SVG dashed feedback-loop arrows. Zero gradients, zero soft shadows.
- **When to use:** system architecture diagrams, data-flow maps, engineering white-papers; you want a serious, printable, README-friendly feel.
- **Path:** `templates/full-decks/knowledge-arch-blueprint/index.html`
## 4. hermes-cyber-terminal — 暗终端 honest-review
- **Source inspiration:** `20260414-hermes-agent/ppt/hermes-record.html` + `hermes-vs-openclaw.html`
- **Key visual traits:** `#0a0c10` black, 56px cyber grid + CRT vignette + scanlines, window traffic-light chrome, `$ prompt` command-line headlines, mint-green `#7ed3a4` glow big text, JetBrains Mono throughout, stroke-only bar charts, blinking cursor, amber/green/red tag hierarchy, dark code box.
- **When to use:** reviews of CLI / agent / dev tools with trace, diff, and benchmarks; when you want the "honest technical reviewer" voice.
- **Path:** `templates/full-decks/hermes-cyber-terminal/index.html`
## 5. obsidian-claude-gradient — GitHub 暗紫渐变
- **Source inspiration:** `20260406-obsidian-claude/slides.html`
- **Key visual traits:** GitHub-dark `#0d1117`, purple+blue radial ambient plus 60px masked grid, center-aligned layout, purple pill tags, three-stop gradient text `#a855f7→#60a5fa→#34d399`, GitHub-ish code palette (`#010409` bg + purple/blue/orange/green tokens), purple-left-border highlight block.
- **When to use:** developer workflow / MCP / Agent / dev-tool tutorials; feels like GitHub Blog / Linear Changelog; config + steps heavy content.
- **Path:** `templates/full-decks/obsidian-claude-gradient/index.html`
## 6. testing-safety-alert — 红琥珀警示
- **Source inspiration:** `20260412-AI测试与安全/html/xhs-ai-testing-safety-v2.html`
- **Key visual traits:** top and bottom 45° red-black hazard stripes, red strike-through negation headlines, L1/L2/L3 green/amber/red tier cards, alert-box with circular status dot, policy-yaml code block with red left border and `bad` keyword highlighting, red/green checklist, Q1 incident stacked bar chart.
- **When to use:** safety / risk / incident post-mortem / red-team / pre-launch AI review / policy-as-code; when the audience needs to feel "this is serious, don't skim".
- **Path:** `templates/full-decks/testing-safety-alert/index.html`
## 7. xhs-pastel-card — 柔和马卡龙慢生活
- **Source inspiration:** `20260412-obsidian-skills/html/xhs-obsidian-skills.html` + pastel patterns shared with `20260409` v2-白底版
- **Key visual traits:** cream `#fef8f1` base, three soft blurred blobs, Playfair italic serif display headlines mixed with sans body, full-color 28px rounded macaron cards (peach / mint / sky / lilac / lemon / rose), italic Playfair `01-04` numerals, SVG donut chart, chip+page topbar.
- **When to use:** lifestyle / personal-growth / slow-living / emotional content; when you want a "magazine, handmade, not-so-techy" feel; themes like rest, pause, softness.
- **Path:** `templates/full-decks/xhs-pastel-card/index.html`
## 8. dir-key-nav-minimal — 方向键 8 色极简
- **Source inspiration:** `20260405-Karpathy-知识库/20260405 演示幻灯片【方向键版】.html`
- **Key visual traits:** 8 slides each on its own mono background (indigo / cream / crimson / emerald / slate / violet / white / charcoal), each with its own accent color, 160px display headline + 4px stubby accent line divider, arrow `→` prefixed Mono list, bottom-left `← →` kbd hint plus bottom-right page label, huge breathing negative space.
- **When to use:** keynote-style minimalist talk where you have something to say and not much to show; one idea per slide; talks / launches / public presentations.
- **Path:** `templates/full-decks/dir-key-nav-minimal/index.html`
---
## Scenario decks (generic, reusable)
These are not extracted from a single source — they are generic scaffolds for the most common presentation jobs. Each is visually distinctive and content-rich out of the box.
| # | Name | Slides | Feel | When to use |
|---|---|---|---|---|
| 9 | `pitch-deck` | 10 | White + blue→purple gradient, YC/VC vibe, big numbers, traction chart | Fundraising, startup pitch, investor meeting |
| 10 | `product-launch` | 8 | Dark hero + light content, warm orange→peach, feature cards, pricing tiers, CTA | Announcing a product, launch keynote |
| 11 | `tech-sharing` | 8 | GitHub-dark, JetBrains Mono, terminal code blocks, agenda + Q&A | 技术分享, internal tech talk, conference talk |
| 12 | `weekly-report` | 7 | Corporate clarity, 8-cell KPI grid, shipped list, 8-week bar chart, next-week table | 周报, team status update, business review |
| 13 | `xhs-post` | 9 | **3:4 @ 810×1080**, warm pastel, dashed sticker cards, page dots | 小红书 图文 post, Instagram carousel |
| 14 | `course-module` | 7 | Warm paper + Playfair serif, persistent left sidebar of learning objectives, MCQ self-check | 教学模块, online course, workshop module |
| 15 | `presenter-mode-reveal` 🎤 | 6 | **演讲者模式专用** · tokyo-night 默认 · 5 主题 T 键切换 · 每页带 150300 字逐字稿示例 | **技术分享/演讲/课程**—需要按 S 键看逐字稿的场景 ✨ |
Each folder: `index.html`, scoped `style.css` (prefixed `.tpl-<name>`), `README.md`. The `xhs-post` template overrides the default `.slide` box to fixed `810×1080` for 3:4 portrait.
> 🎤 **任何演讲场景(技术分享 / 课程 / 路演)都推荐用 `presenter-mode-reveal`**,或者参考 [presenter-mode.md](./presenter-mode.md) 指南给其他模板加 `<aside class="notes">` 逐字稿。
---
## Authoring notes
- Every template scopes its CSS under `.tpl-<name>` so two or more templates can load on the same page without collisions.
- Swap demo content, but keep the structural classes — they are what gives each template its identity.
- The shared runtime (`assets/runtime.js`) provides keyboard nav, fullscreen, overview grid, theme cycling — you don't need to add any JS.
- Charts are hand-rolled SVG (no CDN dependency). Feel free to replace with chart.js / echarts if you need interactive data.

View File

@@ -0,0 +1,229 @@
# Functional Requirements
Audio, video, notifications, and other functional behavior requirements.
## Audio
### Playback Initialization
| Requirement | Specification |
|-------------|---------------|
| Response time | < 1 second |
| If delayed | Show visual progress indicator |
| User feedback | Immediate acknowledgment of action |
### Audio Focus Rules
| Event | Required Action |
|-------|-----------------|
| Another app requests focus | Pause or reduce volume |
| Focus regained | Resume or restore volume |
| Playback stops | Abandon focus |
### Audio Focus Handling
| Focus Change | Action |
|--------------|--------|
| AUDIOFOCUS_LOSS | Stop playback |
| AUDIOFOCUS_LOSS_TRANSIENT | Pause playback |
| AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK | Reduce volume |
| AUDIOFOCUS_GAIN | Resume playback |
### Background Playback
| Requirement | Implementation |
|-------------|----------------|
| Continue when backgrounded | Use Foreground Service |
| Notification | MediaStyle notification required |
| Media controls | System media controls integration |
| Session | MediaSession for system integration |
## Video
### Picture-in-Picture (PiP)
| Requirement | Specification |
|-------------|---------------|
| Video apps | Should support PiP |
| Aspect ratio | 16:9 to 2.39:1 |
| Auto-enter | When user navigates away during playback |
### Video Encoding
| Standard | Requirement |
|----------|-------------|
| Compression | HEVC (H.265) recommended |
| Fallback | H.264 for compatibility |
| Quality | Adaptive based on network |
### Video Player Requirements
| Feature | Implementation |
|---------|----------------|
| Fullscreen | Support landscape |
| Controls | Play, pause, seek, volume |
| Captions | Support closed captions |
| Resume | Remember playback position |
## Notifications
### Channel Best Practices
| Practice | Reason |
|----------|--------|
| Multiple channels | User can control each type |
| Descriptive names | User understands purpose |
| Appropriate importance | Match user expectation |
| Don't share channels | Different content = different channel |
### Notification Priority
| Importance | Usage |
|------------|-------|
| HIGH | Time-sensitive (messages, calls) |
| DEFAULT | Normal notifications |
| LOW | Background info |
| MIN | Minimal interruption |
### Notification Content Rules
| Do | Don't |
|-----|-------|
| Relevant information | Cross-promotion |
| Clear, concise text | Advertising other products |
| Actionable content | Unnecessary interruptions |
| Set timeouts | Persistent non-ongoing notifications |
### Messaging Apps Requirements
| Feature | Description |
|---------|-------------|
| MessagingStyle | Use for conversation notifications |
| Direct reply | Support inline reply action |
| Conversation shortcuts | Enable direct share |
| Bubbles | Support floating conversations |
### Notification Grouping
Group related notifications together with a summary notification. Set appropriate group keys and summary flags.
## Sharing
### Android Sharesheet
Use the system sharesheet for sharing content. Create an ACTION_SEND intent with appropriate type and extras, then use createChooser().
### Direct Share
Provide conversation shortcuts for Direct Share ranking:
- Create ShortcutInfo for each conversation
- Set appropriate categories
- Push dynamic shortcuts
## Background Services
### Service Restrictions
| Rule | Implementation |
|------|----------------|
| Avoid long-running services | Use WorkManager |
| No background starts (API 26+) | Use foreground service or JobScheduler |
| Battery-efficient | Batch work, respect Doze |
### Poor Background Service Uses
| Don't Use For | Alternative |
|---------------|-------------|
| Maintaining network connection | FCM (push notifications) |
| Persistent Bluetooth | Companion device manager |
| Keeping GPS on | Geofencing, fused location |
| Polling server | FCM or WorkManager |
## State Management
### State Preservation Requirements
| Scenario | Required Behavior |
|----------|-------------------|
| App switcher return | Exact previous state |
| Device wake | Exact previous state |
| Process death | Restore critical state |
| Configuration change | Seamless transition |
### State Categories
| State Type | Storage |
|------------|---------|
| UI state (scroll, selection) | ViewModel + SavedState |
| User input (forms) | SavedState |
| Navigation | NavController state |
| Persistent data | Room database |
## Navigation
### Back Button/Gesture
| Requirement | Implementation |
|-------------|----------------|
| System back | Navigate to previous screen |
| Gesture navigation | Support back gesture |
| No custom back buttons | Use system navigation |
| Predictable | User knows what back does |
## Gestures
### Gesture Navigation Support
| Gesture | Default Action |
|---------|----------------|
| Swipe from left edge | Back |
| Swipe up from bottom | Home |
| Swipe up and hold | Recent apps |
### Custom Gestures
| Practice | Reason |
|----------|--------|
| Avoid edge swipes | Conflicts with navigation |
| Provide alternatives | Not all users gesture-capable |
| Test with gesture nav | Ensure no conflicts |
Handle system gesture insets to avoid conflicts with edge gestures.
## Functional Checklist
### Audio
- [ ] Playback starts within 1 second
- [ ] Audio focus requested and released
- [ ] Responds to focus changes (duck/pause)
- [ ] Background playback with notification
- [ ] MediaSession integration
### Video
- [ ] Picture-in-picture supported
- [ ] HEVC encoding used
- [ ] Playback position remembered
- [ ] Captions supported
### Notifications
- [ ] Appropriate channels defined
- [ ] Correct importance levels
- [ ] No promotional content
- [ ] Grouped when appropriate
- [ ] Timeouts set where applicable
### Messaging (if applicable)
- [ ] MessagingStyle used
- [ ] Direct reply supported
- [ ] Conversation shortcuts
- [ ] Bubbles supported
### Background
- [ ] WorkManager for background work
- [ ] No long-running services
- [ ] Battery-efficient design
### Navigation
- [ ] Standard back behavior
- [ ] Gesture navigation supported
- [ ] State preserved across lifecycle

View File

@@ -0,0 +1,350 @@
# Graphics & Animation
iOS graphics and animation guide covering CAShapeLayer, CAGradientLayer, UIBezierPath, and Core Animation.
## CAShapeLayer
For custom shapes, paths, and animations:
```swift
class CircularProgressView: UIView {
private let trackLayer = CAShapeLayer()
private let progressLayer = CAShapeLayer()
var progress: CGFloat = 0 {
didSet { updateProgress() }
}
override init(frame: CGRect) {
super.init(frame: frame)
setupLayers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupLayers()
}
private func setupLayers() {
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = min(bounds.width, bounds.height) / 2 - 10
let startAngle = -CGFloat.pi / 2
let endAngle = startAngle + 2 * CGFloat.pi
let circularPath = UIBezierPath(
arcCenter: center,
radius: radius,
startAngle: startAngle,
endAngle: endAngle,
clockwise: true
)
trackLayer.path = circularPath.cgPath
trackLayer.strokeColor = UIColor.systemGray5.cgColor
trackLayer.fillColor = UIColor.clear.cgColor
trackLayer.lineWidth = 10
trackLayer.lineCap = .round
layer.addSublayer(trackLayer)
progressLayer.path = circularPath.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.lineWidth = 10
progressLayer.lineCap = .round
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
override func layoutSubviews() {
super.layoutSubviews()
setupLayers()
}
private func updateProgress() {
progressLayer.strokeEnd = progress
}
func animateProgress(to value: CGFloat, duration: TimeInterval = 0.5) {
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = progressLayer.strokeEnd
animation.toValue = value
animation.duration = duration
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
progressLayer.strokeEnd = value
progressLayer.add(animation, forKey: "progressAnimation")
}
}
```
## UIBezierPath
### Common Shapes
```swift
let roundedRect = UIBezierPath(
roundedRect: bounds,
cornerRadius: 12
)
let customCorners = UIBezierPath(
roundedRect: bounds,
byRoundingCorners: [.topLeft, .topRight],
cornerRadii: CGSize(width: 16, height: 16)
)
let triangle = UIBezierPath()
triangle.move(to: CGPoint(x: bounds.midX, y: 0))
triangle.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
triangle.addLine(to: CGPoint(x: 0, y: bounds.maxY))
triangle.close()
let circle = UIBezierPath(
arcCenter: CGPoint(x: bounds.midX, y: bounds.midY),
radius: bounds.width / 2,
startAngle: 0,
endAngle: .pi * 2,
clockwise: true
)
```
### Custom Paths
```swift
let customPath = UIBezierPath()
customPath.move(to: CGPoint(x: 0, y: bounds.height))
customPath.addCurve(
to: CGPoint(x: bounds.width, y: 0),
controlPoint1: CGPoint(x: bounds.width * 0.3, y: bounds.height),
controlPoint2: CGPoint(x: bounds.width * 0.7, y: 0)
)
```
## CAGradientLayer
### Linear Gradient Button
```swift
class GradientButton: UIButton {
private let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupGradient()
}
private func setupGradient() {
gradientLayer.colors = [
UIColor.systemBlue.cgColor,
UIColor.systemPurple.cgColor
]
gradientLayer.startPoint = CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1, y: 0.5)
gradientLayer.cornerRadius = 12
layer.insertSublayer(gradientLayer, at: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
}
```
### Gradient Background View
```swift
class GradientBackgroundView: UIView {
private let gradientLayer = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame: frame)
setupGradient()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupGradient()
}
private func setupGradient() {
gradientLayer.colors = [
UIColor.systemBackground.cgColor,
UIColor.secondarySystemBackground.cgColor
]
gradientLayer.locations = [0.0, 1.0]
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
layer.insertSublayer(gradientLayer, at: 0)
}
override func layoutSubviews() {
super.layoutSubviews()
gradientLayer.frame = bounds
}
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
gradientLayer.colors = [
UIColor.systemBackground.cgColor,
UIColor.secondarySystemBackground.cgColor
]
}
}
```
### Gradient Types
| Type | Configuration |
|------|---------------|
| Linear (horizontal) | `startPoint: (0, 0.5)`, `endPoint: (1, 0.5)` |
| Linear (vertical) | `startPoint: (0.5, 0)`, `endPoint: (0.5, 1)` |
| Diagonal | `startPoint: (0, 0)`, `endPoint: (1, 1)` |
| Radial | Use `CAGradientLayer.type = .radial` |
## Core Animation
### Basic Animation
```swift
func animateScale() {
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.fromValue = 1.0
animation.toValue = 1.2
animation.duration = 0.3
animation.autoreverses = true
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
layer.add(animation, forKey: "scaleAnimation")
}
func animatePosition() {
let animation = CABasicAnimation(keyPath: "position")
animation.fromValue = layer.position
animation.toValue = CGPoint(x: 200, y: 200)
animation.duration = 0.5
layer.add(animation, forKey: "positionAnimation")
}
```
### Keyframe Animation
```swift
func animateAlongPath() {
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addCurve(
to: CGPoint(x: 250, y: 250),
controlPoint1: CGPoint(x: 150, y: 50),
controlPoint2: CGPoint(x: 50, y: 250)
)
let animation = CAKeyframeAnimation(keyPath: "position")
animation.path = path.cgPath
animation.duration = 2.0
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
layer.add(animation, forKey: "pathAnimation")
}
```
### Animation Group
```swift
func animateMultiple() {
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = 1.0
scaleAnimation.toValue = 1.5
let opacityAnimation = CABasicAnimation(keyPath: "opacity")
opacityAnimation.fromValue = 1.0
opacityAnimation.toValue = 0.0
let group = CAAnimationGroup()
group.animations = [scaleAnimation, opacityAnimation]
group.duration = 0.5
group.fillMode = .forwards
group.isRemovedOnCompletion = false
layer.add(group, forKey: "multipleAnimations")
}
```
### Spring Animation
```swift
func springAnimation() {
let spring = CASpringAnimation(keyPath: "transform.scale")
spring.fromValue = 0.8
spring.toValue = 1.0
spring.damping = 10
spring.stiffness = 100
spring.mass = 1
spring.initialVelocity = 5
spring.duration = spring.settlingDuration
layer.add(spring, forKey: "springAnimation")
}
```
## UIView Animation
### Basic UIView Animation
```swift
UIView.animate(withDuration: 0.3) {
self.view.alpha = 1.0
self.view.transform = .identity
}
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseInOut]) {
self.cardView.frame.origin.y = 100
} completion: { _ in
self.didFinishAnimation()
}
```
### Spring Animation
```swift
UIView.animate(
withDuration: 0.6,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: []
) {
self.popupView.transform = .identity
}
```
### Keyframe Animation
```swift
UIView.animateKeyframes(withDuration: 1.0, delay: 0) {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25) {
self.view.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
}
UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25) {
self.view.transform = CGAffineTransform(rotationAngle: .pi / 4)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.5) {
self.view.transform = .identity
}
}
```
## Timing Functions
| Name | Description |
|------|-------------|
| `.linear` | Constant speed |
| `.easeIn` | Slow start |
| `.easeOut` | Slow end |
| `.easeInEaseOut` | Slow start and end |
| `.default` | System default |
---
*UIKit, Core Animation, and Apple are trademarks of Apple Inc.*

View File

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

View File

@@ -0,0 +1,295 @@
# HTML Slide Template
Complete HTML structure with navigation, tokens, and Chart.js integration.
## Base Structure
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Presentation Title</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
/* Paste embed-tokens.cjs output here */
:root {
--color-primary: #FF6B6B;
--color-background: #0D0D0D;
/* ... more tokens */
}
/* Base slide styles */
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--color-background);
color: #fff;
font-family: var(--typography-font-body, 'Inter', sans-serif);
overflow: hidden;
}
/* 16:9 Aspect Ratio Container (desktop) */
.slide-deck {
position: relative;
width: 100vw;
height: 100vh;
overflow: hidden;
}
@media (min-width: 769px) {
.slide-deck {
/* Lock to 16:9 — letterbox if viewport ratio differs */
max-width: calc(100vh * 16 / 9);
max-height: calc(100vw * 9 / 16);
margin: auto;
position: absolute;
inset: 0;
}
}
.slide {
position: absolute;
width: 100%; height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
padding: 60px;
opacity: 0;
visibility: hidden;
transition: opacity 0.4s;
background: var(--color-background);
overflow: hidden; /* Prevent content overflow */
}
.slide.active { opacity: 1; visibility: visible; }
/* Slide inner wrapper — constrains content within safe area */
.slide-content {
width: 100%;
max-width: 100%;
max-height: 100%;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 16px;
}
/* Typography */
h1, h2 { font-family: var(--typography-font-heading, 'Space Grotesk', sans-serif); }
.slide-title {
font-size: clamp(32px, 6vw, 80px);
background: var(--primitive-gradient-primary, linear-gradient(135deg, #FF6B6B, #FF8E53));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
line-height: 1.1;
}
/* ===== RESPONSIVE BREAKPOINTS ===== */
/* Tablet (portrait) */
@media (max-width: 768px) {
.slide { padding: 32px 24px; }
.slide-title { font-size: clamp(28px, 5vw, 48px); }
h2 { font-size: clamp(20px, 4vw, 32px); }
p, li { font-size: clamp(14px, 2.5vw, 18px); }
}
/* Mobile */
@media (max-width: 480px) {
.slide { padding: 24px 16px; }
.slide-title { font-size: clamp(22px, 6vw, 36px); }
h2 { font-size: clamp(18px, 4.5vw, 28px); }
p, li { font-size: clamp(12px, 3vw, 16px); }
.nav-controls { bottom: 16px; gap: 12px; }
.nav-btn { width: 32px; height: 32px; font-size: 14px; }
}
/* Navigation */
.progress-bar {
position: fixed;
top: 0; left: 0;
height: 3px;
background: var(--color-primary);
transition: width 0.3s;
z-index: 1000;
}
.nav-controls {
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 20px;
z-index: 1000;
}
.nav-btn {
background: rgba(255,255,255,0.1);
border: none;
color: #fff;
width: 40px; height: 40px;
border-radius: 50%;
cursor: pointer;
font-size: 18px;
}
.nav-btn:hover { background: rgba(255,255,255,0.2); }
.slide-counter { color: rgba(255,255,255,0.6); font-size: 14px; }
</style>
</head>
<body>
<!-- Progress Bar -->
<div class="progress-bar" id="progressBar"></div>
<!-- Slide Deck Container (16:9 on desktop) -->
<div class="slide-deck">
<!-- Slides -->
<div class="slide active">
<div class="slide-content">
<h1 class="slide-title">Title Slide</h1>
<p>Subtitle or tagline</p>
</div>
</div>
<!-- More slides... (always wrap content in .slide-content) -->
</div><!-- /.slide-deck -->
<!-- Navigation -->
<div class="nav-controls">
<button class="nav-btn" onclick="prevSlide()"></button>
<span class="slide-counter"><span id="current">1</span> / <span id="total">9</span></span>
<button class="nav-btn" onclick="nextSlide()"></button>
</div>
<script>
let current = 1;
const total = document.querySelectorAll('.slide').length;
document.getElementById('total').textContent = total;
function showSlide(n) {
if (n < 1) n = 1;
if (n > total) n = total;
current = n;
document.querySelectorAll('.slide').forEach((s, i) => {
s.classList.toggle('active', i === n - 1);
});
document.getElementById('current').textContent = n;
document.getElementById('progressBar').style.width = (n / total * 100) + '%';
}
function nextSlide() { showSlide(current + 1); }
function prevSlide() { showSlide(current - 1); }
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); nextSlide(); }
if (e.key === 'ArrowLeft') { e.preventDefault(); prevSlide(); }
});
document.addEventListener('click', (e) => {
if (!e.target.closest('.nav-controls')) nextSlide();
});
showSlide(1);
</script>
</body>
</html>
```
## Chart.js Integration
```html
<div class="chart-container" style="width: min(80%, 600px); height: clamp(200px, 40vh, 350px);">
<canvas id="revenueChart"></canvas>
</div>
<script>
new Chart(document.getElementById('revenueChart'), {
type: 'line', // or 'bar', 'doughnut', 'radar'
data: {
labels: ['Sep', 'Oct', 'Nov', 'Dec'],
datasets: [{
label: 'MRR ($K)',
data: [5, 12, 28, 45],
borderColor: '#FF6B6B',
backgroundColor: 'rgba(255, 107, 107, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false } },
scales: {
x: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#B8B8D0' } },
y: { grid: { color: 'rgba(255,255,255,0.05)' }, ticks: { color: '#B8B8D0' } }
}
}
});
</script>
```
## Animation Classes
```css
/* Fade Up */
.animate-fade-up {
animation: fadeUp 0.6s ease-out forwards;
opacity: 0;
}
@keyframes fadeUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
/* Count Animation */
.animate-count { animation: countUp 1s ease-out forwards; }
/* Scale */
.animate-scale {
animation: scaleIn 0.5s ease-out forwards;
}
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
/* Stagger Children */
.animate-stagger > * {
opacity: 0;
animation: fadeUp 0.5s ease-out forwards;
}
.animate-stagger > *:nth-child(1) { animation-delay: 0.1s; }
.animate-stagger > *:nth-child(2) { animation-delay: 0.2s; }
.animate-stagger > *:nth-child(3) { animation-delay: 0.3s; }
.animate-stagger > *:nth-child(4) { animation-delay: 0.4s; }
```
## Background Images
```html
<div class="slide slide-with-bg" style="background-image: url('https://images.pexels.com/...')">
<div class="overlay" style="background: linear-gradient(135deg, rgba(13,13,13,0.9), rgba(13,13,13,0.7))"></div>
<div class="content" style="position: relative; z-index: 1;">
<!-- Slide content -->
</div>
</div>
```
## CSS Variables Reference
| Variable | Usage |
|----------|-------|
| `--color-primary` | Brand primary (CTA, highlights) |
| `--color-background` | Slide background |
| `--color-secondary` | Secondary elements |
| `--primitive-gradient-primary` | Title gradients |
| `--typography-font-heading` | Headlines |
| `--typography-font-body` | Body text |

View File

@@ -0,0 +1,122 @@
# Icon Design Reference
AI-powered SVG icon generation using Gemini 3.1 Pro Preview. 15 styles, 12 categories, multi-size export.
## Scripts
| Script | Purpose |
|--------|---------|
| `scripts/icon/generate.py` | Generate SVG icons with Gemini 3.1 Pro Preview |
## Commands
### Generate Single Icon
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "settings gear" --style outlined
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "shopping cart" --style filled --color "#6366F1"
python3 ~/.claude/skills/design/scripts/icon/generate.py --name "dashboard" --category navigation --style duotone
```
### Generate Batch Variations
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "cloud upload" --batch 4 --output-dir ./icons
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "notification bell" --batch 6 --style outlined --output-dir ./icons
```
### Generate Multiple Sizes
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --prompt "user profile" --sizes "16,24,32,48" --output-dir ./icons
```
### List Styles/Categories
```bash
python3 ~/.claude/skills/design/scripts/icon/generate.py --list-styles
python3 ~/.claude/skills/design/scripts/icon/generate.py --list-categories
```
## CLI Options
| Option | Description | Default |
|--------|-------------|---------|
| `--prompt, -p` | Icon description | required |
| `--name, -n` | Icon name (for filename) | - |
| `--style, -s` | Icon style (15 options) | - |
| `--category, -c` | Icon category for context | - |
| `--color` | Primary hex color | currentColor |
| `--size` | Display size in px | 24 |
| `--viewbox` | SVG viewBox size | 24 |
| `--output, -o` | Output file path | auto |
| `--output-dir` | Output directory (batch) | ./icons |
| `--batch` | Number of variations | - |
| `--sizes` | Comma-separated sizes | - |
## Available Styles
| Style | Stroke | Fill | Best For |
|-------|--------|------|----------|
| outlined | 2px | none | UI interfaces, web apps |
| filled | 0 | solid | Mobile apps, nav bars |
| duotone | 0 | dual | Marketing, landing pages |
| thin | 1-1.5px | none | Luxury brands, editorial |
| bold | 3px | none | Headers, hero sections |
| rounded | 2px | none | Friendly apps, health |
| sharp | 2px | none | Tech, fintech, enterprise |
| flat | 0 | solid | Material design, Google-style |
| gradient | 0 | gradient | Modern brands, SaaS |
| glassmorphism | 1px | semi | Modern UI, overlays |
| pixel | 0 | solid | Gaming, retro |
| hand-drawn | varies | none | Artisan, creative |
| isometric | 1-2px | partial | Tech docs, infographics |
| glyph | 0 | solid | System UI, compact |
| animated-ready | 2px | varies | Interactive UI, onboarding |
## Icon Categories
| Category | Icons |
|----------|-------|
| navigation | arrows, menus, home, chevrons |
| action | edit, delete, save, download, upload |
| communication | email, chat, phone, notification |
| media | play, pause, volume, camera |
| file | document, folder, archive, cloud |
| user | person, group, profile, settings |
| commerce | cart, bag, wallet, credit card |
| data | chart, graph, analytics, dashboard |
| development | code, terminal, bug, git, API |
| social | heart, star, bookmark, trophy |
| weather | sun, moon, cloud, rain |
| map | pin, location, compass, globe |
## SVG Best Practices
- **ViewBox**: Use `0 0 24 24` (standard) or `0 0 16 16` (compact)
- **Colors**: Use `currentColor` for CSS inheritance, avoid hardcoded colors
- **Accessibility**: Always include `<title>` element
- **Optimization**: Minimal path nodes, no embedded fonts or raster images
- **Sizing**: Design at 24px, test at 16px and 48px for clarity
- **Stroke**: Use `stroke-linecap="round"` and `stroke-linejoin="round"` for outlined styles
## Model
- **gemini-3.1-pro-preview**: Best thinking, token efficiency, factual consistency
- Text-only output (SVG is XML text) — no image generation API needed
- Supports structured output for consistent SVG formatting
## Workflow
1. Describe icon → `--prompt "settings gear"`
2. Choose style → `--style outlined`
3. Generate → script outputs .svg file
4. Optionally batch → `--batch 4` for variations
5. Multi-size export → `--sizes "16,24,32,48"`
## Setup
```bash
export GEMINI_API_KEY="your-key"
pip install google-genai
```

View File

@@ -0,0 +1,115 @@
# MiniMax Image Generation API (image-01)
Source: https://platform.minimaxi.com/docs/api-reference/image-generation-t2i and https://platform.minimaxi.com/docs/api-reference/image-generation-i2i
## Endpoint
`POST https://api.minimaxi.com/v1/image_generation`
## Auth
`Authorization: Bearer <MINIMAX_API_KEY>`
## Request (JSON)
Required:
- `model`: string — `image-01`
- `prompt`: string (max 1500 chars) — text description of the desired image
Optional:
- `aspect_ratio`: string — image aspect ratio, default `1:1`. Options:
- `1:1` (1024×1024)
- `16:9` (1280×720)
- `4:3` (1152×864)
- `3:2` (1248×832)
- `2:3` (832×1248)
- `3:4` (864×1152)
- `9:16` (720×1280)
- `21:9` (1344×576)
- `width`: integer — custom width in pixels. Range [512, 2048], must be multiple of 8. Overridden by `aspect_ratio` if both set.
- `height`: integer — custom height in pixels. Same rules as `width`. Both `width` and `height` must be set together.
- `response_format`: string — `url` (default, valid 24h) or `base64`
- `n`: integer (19, default 1) — number of images to generate
- `seed`: integer — random seed for reproducibility
- `prompt_optimizer`: boolean (default `false`) — enable automatic prompt optimization
- `aigc_watermark`: boolean (default `false`) — add AIGC watermark
### Subject Reference (image-to-image)
- `subject_reference`: array — character reference for image-to-image generation
- `type`: string — currently only `character` (portrait)
- `image_file`: string — reference image as public URL or Base64 Data URL (`data:image/jpeg;base64,...`). For best results, use a single person front-facing photo. Formats: JPG, JPEG, PNG. Max size: 10MB.
## Example — Text-to-Image
```json
{
"model": "image-01",
"prompt": "A man in a white t-shirt, full-body, standing front view, outdoors, with the Venice Beach sign in the background, Los Angeles. Fashion photography in 90s documentary style, film grain, photorealistic.",
"aspect_ratio": "16:9",
"response_format": "url",
"n": 3,
"prompt_optimizer": true
}
```
## Example — Image-to-Image (Character Reference)
```json
{
"model": "image-01",
"prompt": "A girl looking into the distance from a library window",
"aspect_ratio": "16:9",
"subject_reference": [
{
"type": "character",
"image_file": "https://example.com/face.jpg"
}
],
"n": 2
}
```
## Response
```json
{
"id": "03ff3cd0820949eb8a410056b5f21d38",
"data": {
"image_urls": ["https://...", "https://...", "https://..."],
"image_base64": null
},
"metadata": {
"success_count": 3,
"failed_count": 0
},
"base_resp": {
"status_code": 0,
"status_msg": "success"
}
}
```
- `data.image_urls`: array of image URLs (when `response_format` is `url`, valid 24h)
- `data.image_base64`: array of Base64 strings (when `response_format` is `base64`)
- `metadata.success_count`: number of successfully generated images
- `metadata.failed_count`: number of images blocked by content safety
## Status Codes
| Code | Meaning |
|------|---------|
| 0 | Success |
| 1002 | Rate limited, retry later |
| 1004 | Auth failed, check API key |
| 1008 | Insufficient balance |
| 1026 | Prompt contains sensitive content |
| 2013 | Invalid parameters |
| 2049 | Invalid API key |
## Notes
- The API is synchronous — images are returned directly in the response (no polling needed).
- URL format image links expire after 24 hours.
- For image-to-image: upload a single front-facing portrait for best character reference results.
- `width`/`height` are overridden by `aspect_ratio` if both provided.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,137 @@
# Layout Patterns
25 slide layouts with CSS structures and animation classes.
## Layout Selection by Use Case
| Layout | Use Case | Animation |
|--------|----------|-----------|
| Title Slide | Opening/first impression | `animate-fade-up` |
| Problem Statement | Establish pain point | `animate-stagger` |
| Solution Overview | Introduce solution | `animate-scale` |
| Feature Grid | Show capabilities (3-6 cards) | `animate-stagger` |
| Metrics Dashboard | Display KPIs (3-4 metrics) | `animate-stagger-scale` |
| Comparison Table | Compare options | `animate-fade-up` |
| Timeline Flow | Show progression | `animate-stagger` |
| Team Grid | Introduce people | `animate-stagger` |
| Quote Testimonial | Customer endorsement | `animate-fade-up` |
| Two Column Split | Compare/contrast | `animate-fade-up` |
| Big Number Hero | Single powerful metric | `animate-count` |
| Product Screenshot | Show product UI | `animate-scale` |
| Pricing Cards | Present tiers | `animate-stagger` |
| CTA Closing | Drive action | `animate-pulse` |
## CSS Structures
### Title Slide
```css
.slide-title {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
}
```
### Two Column Split
```css
.slide-split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: center;
}
@media (max-width: 768px) {
.slide-split { grid-template-columns: 1fr; gap: 24px; }
}
```
### Feature Grid (3 columns)
```css
.slide-features {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
@media (max-width: 768px) {
.slide-features { grid-template-columns: repeat(2, 1fr); gap: 16px; }
}
@media (max-width: 480px) {
.slide-features { grid-template-columns: 1fr; }
}
```
### Metrics Dashboard (4 columns)
```css
.slide-metrics {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
}
@media (max-width: 768px) {
.slide-metrics { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.slide-metrics { grid-template-columns: 1fr; }
}
```
## Component Variants
### Card Styles
| Style | CSS Class | Use For |
|-------|-----------|---------|
| Icon Left | `.card-icon-left` | Features with icons |
| Accent Bar | `.card-accent-bar` | Highlighted features |
| Metric Card | `.card-metric` | Numbers/stats |
| Avatar Card | `.card-avatar` | Team members |
| Pricing Card | `.card-pricing` | Price tiers |
### Metric Styles
| Style | Effect |
|-------|--------|
| `gradient-number` | Gradient text on numbers |
| `oversized` | Extra large (120px+) |
| `sparkline` | Small inline chart |
| `funnel-numbers` | Conversion stages |
## Visual Treatments
| Treatment | When to Use |
|-----------|-------------|
| `gradient-glow` | Title slides, CTAs |
| `subtle-border` | Problem statements |
| `icon-top` | Feature grids |
| `screenshot-shadow` | Product screenshots |
| `popular-highlight` | Pricing (scale 1.05) |
| `bg-overlay` | Background images |
| `contrast-pair` | Before/after |
| `logo-grayscale` | Client logos |
## Search Commands
```bash
# Find layout for specific use
python .claude/skills/design-system/scripts/search-slides.py "metrics dashboard" -d layout
# Contextual recommendation
python .claude/skills/design-system/scripts/search-slides.py "traction slide" \
--context --position 4 --total 10
```
## Layout Decision Flow
```
1. What's the slide goal?
└─> Search layout-logic.csv
2. What emotion should it trigger?
└─> Search color-logic.csv
3. What's the content type?
└─> Search typography.csv
4. Should it break pattern?
└─> Check position (1/3, 2/3) → Use full-bleed
```

View File

@@ -0,0 +1,199 @@
# Layout System
iOS layout system guide covering touch targets, safe areas, UICollectionView, and Compositional Layout.
## Touch Targets
Interactive elements need adequate tap areas. The recommended minimum is 44x44 points.
```swift
let actionButton = UIButton(type: .system)
actionButton.setTitle("Submit", for: .normal)
view.addSubview(actionButton)
actionButton.snp.makeConstraints { make in
make.height.greaterThanOrEqualTo(44)
make.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalTo(view.safeAreaLayoutGuide).offset(-16)
}
```
Use 8-point increments for spacing (8, 16, 24, 32, 40, 48) to maintain visual consistency.
## Safe Area
Always constrain content to the safe area to avoid the notch, Dynamic Island, and home indicator.
```swift
class MainViewController: UIViewController {
private let contentStack = UIStackView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
contentStack.axis = .vertical
contentStack.spacing = 16
view.addSubview(contentStack)
contentStack.snp.makeConstraints { make in
make.top.bottom.equalTo(view.safeAreaLayoutGuide)
make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16)
}
}
}
```
## UICollectionView with Diffable Data Source
```swift
class ItemsViewController: UIViewController {
enum Section { case main }
private var collectionView: UICollectionView!
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
override func viewDidLoad() {
super.viewDidLoad()
setupCollectionView()
configureDataSource()
}
private func setupCollectionView() {
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.trailingSwipeActionsConfigurationProvider = { [weak self] indexPath in
self?.makeSwipeActions(for: indexPath)
}
let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
cell, indexPath, item in
var content = cell.defaultContentConfiguration()
content.text = item.title
content.secondaryText = item.subtitle
cell.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, item in
collectionView.dequeueConfiguredReusableCell(
using: cellRegistration, for: indexPath, item: item
)
}
}
func updateItems(_ items: [Item]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items)
dataSource.apply(snapshot)
}
}
```
## Grid Layout
```swift
private func createGridLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1/3),
heightDimension: .fractionalHeight(1.0)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = NSDirectionalEdgeInsets(top: 2, leading: 2, bottom: 2, trailing: 2)
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(1/3)
)
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
let section = NSCollectionLayoutSection(group: group)
return UICollectionViewCompositionalLayout(section: section)
}
```
## Sectioned List with Headers
```swift
class CategorizedListVC: UIViewController {
enum Section: Hashable {
case favorites, recent, all
}
private var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
private func setupCollectionView() {
var config = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
config.headerMode = .supplementary
let layout = UICollectionViewCompositionalLayout.list(using: config)
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
}
private func configureDataSource() {
let cellRegistration = UICollectionView.CellRegistration<UICollectionViewListCell, Item> {
cell, indexPath, item in
var content = cell.defaultContentConfiguration()
content.text = item.title
cell.contentConfiguration = content
}
let headerRegistration = UICollectionView.SupplementaryRegistration<UICollectionViewListCell>(
elementKind: UICollectionView.elementKindSectionHeader
) { [weak self] header, elementKind, indexPath in
guard let section = self?.dataSource.sectionIdentifier(for: indexPath.section) else { return }
var content = header.defaultContentConfiguration()
content.text = self?.title(for: section)
header.contentConfiguration = content
}
dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
collectionView, indexPath, item in
collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: item)
}
dataSource.supplementaryViewProvider = { collectionView, kind, indexPath in
collectionView.dequeueConfiguredReusableSupplementary(using: headerRegistration, for: indexPath)
}
}
func applySnapshot(favorites: [Item], recent: [Item], all: [Item]) {
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
if !favorites.isEmpty {
snapshot.appendSections([.favorites])
snapshot.appendItems(favorites, toSection: .favorites)
}
if !recent.isEmpty {
snapshot.appendSections([.recent])
snapshot.appendItems(recent, toSection: .recent)
}
snapshot.appendSections([.all])
snapshot.appendItems(all, toSection: .all)
dataSource.apply(snapshot)
}
}
```
## Spacing Guidelines
| Spacing | Usage |
|---------|-------|
| 8pt | Compact element spacing |
| 16pt | Standard padding |
| 24pt | Section spacing |
| 32pt | Large section separation |
| 48pt | Screen margins (large screens) |
---
*UIKit and Apple are trademarks of Apple Inc. SnapKit is a trademark of its respective owners.*

View File

@@ -0,0 +1,103 @@
# Layouts catalog
Every layout lives in `templates/single-page/<name>.html` as a fully
functional standalone page with realistic demo data. Open any file directly
in Chrome to see it working.
To compose a new deck: open the file, copy the `<section class="slide">…</section>`
block (or multiple blocks) into your deck HTML, and replace the demo data.
Shared CSS (base, theme, animations) is already wired by `deck.html`.
## Openers & transitions
| file | purpose |
|---|---|
| `cover.html` | Deck cover. Kicker + huge title + lede + pill row. |
| `toc.html` | Table of contents. 2×3 grid of numbered cards. |
| `section-divider.html` | Big numbered section break (02 · Theme). |
## Text-centric
| file | purpose |
|---|---|
| `bullets.html` | Classic bullet list with card-wrapped items. |
| `two-column.html` | Concept + example side by side. |
| `three-column.html` | Three equal pillars with icons. |
| `big-quote.html` | Full-bleed pull quote in editorial-serif style. |
## Numbers & data
| file | purpose |
|---|---|
| `stat-highlight.html` | One giant number + subtitle (uses `.counter` animation). |
| `kpi-grid.html` | 4 KPIs in a row with up/down deltas. |
| `table.html` | Data table with hover rows, right-aligned numerics. |
| `chart-bar.html` | Chart.js bar chart, theme-aware colors. |
| `chart-line.html` | Chart.js dual-line chart with filled area. |
| `chart-pie.html` | Chart.js doughnut + takeaways card. |
| `chart-radar.html` | Chart.js radar comparing 2 products on 6 axes. |
## Code & terminal
| file | purpose |
|---|---|
| `code.html` | Syntax-highlighted code via highlight.js (JS example). |
| `diff.html` | Hand-rolled +/- diff view. |
| `terminal.html` | Terminal window mock with traffic-light header. |
## Diagrams & flows
| file | purpose |
|---|---|
| `flow-diagram.html` | 5-node pipeline with arrows and one highlighted node. |
| `arch-diagram.html` | 3-tier architecture grid. |
| `process-steps.html` | 4 numbered steps in cards. |
| `mindmap.html` | Radial mindmap with SVG path-draw animation. |
## Plans & comparisons
| file | purpose |
|---|---|
| `timeline.html` | 5-point horizontal timeline with dots. |
| `roadmap.html` | 4-column NOW / NEXT / LATER / VISION. |
| `gantt.html` | 12-week gantt chart with 5 parallel tracks. |
| `comparison.html` | Before vs After two-panel card. |
| `pros-cons.html` | Pros and cons two-card layout. |
| `todo-checklist.html` | Checklist with checked/unchecked states. |
## Visuals
| file | purpose |
|---|---|
| `image-hero.html` | Full-bleed hero with Ken Burns gradient background. |
| `image-grid.html` | 7-cell bento grid with gradient placeholders. |
## Closers
| file | purpose |
|---|---|
| `cta.html` | Call-to-action with big gradient headline + buttons. |
| `thanks.html` | Final "Thanks" page with confetti burst. |
## Picking a layout
- **Opener**: `cover.html`, often followed by `toc.html`.
- **Section break**: `section-divider.html` before every major section.
- **Core content**: `bullets.html`, `two-column.html`, `three-column.html`.
- **Show numbers**: `stat-highlight.html` (single) or `kpi-grid.html` (4-up).
- **Show plot**: `chart-bar.html` / `chart-line.html` / `chart-pie.html` / `chart-radar.html`.
- **Show a diff or change**: `comparison.html`, `diff.html`, `pros-cons.html`.
- **Show a plan**: `timeline.html`, `roadmap.html`, `gantt.html`, `process-steps.html`.
- **Show architecture**: `arch-diagram.html`, `flow-diagram.html`, `mindmap.html`.
- **Code / demo**: `code.html`, `terminal.html`.
- **Closer**: `cta.html``thanks.html`.
## Naming / structure conventions
- Each slide is `<section class="slide" data-title="...">`.
- Header pills: `<p class="kicker">…</p>`, eyebrow: `<p class="eyebrow">…</p>`.
- Titles: `<h1 class="h1">…</h1>` / `<h2 class="h2">…</h2>`.
- Lede: `<p class="lede">…</p>`.
- Cards: `<div class="card">…</div>` (variants: `card-soft`, `card-outline`, `card-accent`).
- Grids: `.grid.g2`, `.grid.g3`, `.grid.g4`.
- Notes: `<div class="notes">…</div>` per slide.

View File

@@ -0,0 +1,101 @@
# Logo Color Psychology
## Primary Color Meanings
### Blue
- **Psychology:** Trust, stability, professionalism, calm
- **Industries:** Finance, healthcare, tech, corporate
- **Hex Examples:** Navy #003366, Royal #0055A4, Sky #0EA5E9
- **Pairings:** White, gold, light gray
### Red
- **Psychology:** Energy, passion, urgency, excitement
- **Industries:** Food, sports, entertainment, sales
- **Hex Examples:** Crimson #DC2626, Scarlet #EF4444, Burgundy #9F1239
- **Pairings:** White, black, gold
- **Caution:** Avoid for healthcare (blood connotation)
### Green
- **Psychology:** Growth, nature, health, sustainability
- **Industries:** Eco, wellness, organic, finance (growth)
- **Hex Examples:** Forest #228B22, Sage #2E8B57, Mint #10B981
- **Pairings:** White, brown, blue
### Yellow/Gold
- **Psychology:** Optimism, warmth, luxury, attention
- **Industries:** Food, children, luxury (gold), energy
- **Hex Examples:** Gold #D4AF37, Amber #F59E0B, Lemon #FACC15
- **Pairings:** Black, navy, dark brown
### Purple
- **Psychology:** Creativity, wisdom, luxury, mystery
- **Industries:** Beauty, creative, spiritual, premium
- **Hex Examples:** Royal #7C3AED, Lavender #A78BFA, Deep #581C87
- **Pairings:** Gold, white, pink
### Orange
- **Psychology:** Friendly, energetic, confident, youthful
- **Industries:** Food, sports, entertainment, retail
- **Hex Examples:** Tangerine #F97316, Coral #FB923C, Burnt #EA580C
- **Pairings:** White, navy, dark gray
### Black
- **Psychology:** Sophistication, power, elegance, authority
- **Industries:** Luxury, fashion, tech, premium
- **Pairings:** White, gold, silver
- **Note:** Use for high-end positioning
### White
- **Psychology:** Purity, simplicity, cleanliness, modern
- **Use:** Backgrounds, negative space, contrast
- **Pairings:** Any color (universal neutral)
## Color Combinations by Industry
| Industry | Primary | Secondary | Accent | Avoid |
|----------|---------|-----------|--------|-------|
| Tech | Blue, Purple | Gray, White | Teal, Green | Brown, Beige |
| Healthcare | Blue, Green | Teal, White | Light Purple | Red, Black |
| Finance | Navy, Blue | Gold, Gray | Green | Bright colors |
| Food | Red, Orange | Yellow, Brown | Green | Blue (appetite suppressant) |
| Fashion | Black, White | Gold, Blush | Navy | Neon (unless intentional) |
| Eco | Green, Brown | Beige, Blue | Yellow | Neon, Black |
| Children | Multi-color | Pastels | Bright accents | Dark, muted |
## Color Harmony Types
### Monochromatic
Single color with tints/shades. Safe, cohesive.
### Complementary
Opposite colors (blue-orange). High contrast, vibrant.
### Analogous
Adjacent colors (blue-teal-green). Harmonious, natural.
### Triadic
Three evenly spaced colors. Balanced, dynamic.
## Accessibility Considerations
- Minimum contrast ratio: 4.5:1 (WCAG AA)
- Avoid red-green only indicators
- Test in grayscale for clarity
- Consider colorblind users (~8% of males)
## Quick Reference Palettes
**Tech Professional:**
Primary: #6366F1 | Secondary: #8B5CF6 | Accent: #06B6D4
**Eco Sustainable:**
Primary: #228B22 | Secondary: #2E8B57 | Accent: #DEB887
**Luxury Premium:**
Primary: #1C1917 | Secondary: #D4AF37 | Accent: #FFFFFF
**Healthcare Trust:**
Primary: #0077B6 | Secondary: #00A896 | Accent: #FFFFFF
**Food Warm:**
Primary: #DC2626 | Secondary: #F97316 | Accent: #CA8A04

View File

@@ -0,0 +1,92 @@
# Logo Design Reference
AI-powered logo design with 55+ styles, 30 color palettes, 25 industry guides. Uses Gemini Nano Banana models.
## Scripts
| Script | Purpose |
|--------|---------|
| `scripts/logo/search.py` | Search styles, colors, industries; generate design briefs |
| `scripts/logo/generate.py` | Generate logos with Gemini Nano Banana |
| `scripts/logo/core.py` | BM25 search engine for logo data |
## Commands
### Design Brief (Start Here)
```bash
python3 ~/.claude/skills/design/scripts/logo/search.py "tech startup modern" --design-brief -p "BrandName"
```
### Search Domains
```bash
# Styles
python3 ~/.claude/skills/design/scripts/logo/search.py "minimalist clean" --domain style
# Color palettes
python3 ~/.claude/skills/design/scripts/logo/search.py "tech professional" --domain color
# Industry guidelines
python3 ~/.claude/skills/design/scripts/logo/search.py "healthcare medical" --domain industry
```
### Generate Logo
**ALWAYS** use white background for output logos.
```bash
python3 ~/.claude/skills/design/scripts/logo/generate.py --brand "TechFlow" --style minimalist --industry tech
python3 ~/.claude/skills/design/scripts/logo/generate.py --prompt "coffee shop vintage badge" --style vintage
```
Options: `--style`, `--industry`, `--prompt`
## Available Styles
| Category | Styles |
|----------|--------|
| General | Minimalist, Wordmark, Lettermark, Pictorial Mark, Abstract Mark, Mascot, Emblem, Combination Mark |
| Aesthetic | Vintage/Retro, Art Deco, Luxury, Playful, Corporate, Organic, Neon, Grunge, Watercolor |
| Modern | Gradient, Flat Design, 3D/Isometric, Geometric, Line Art, Duotone, Motion-Ready |
| Clever | Negative Space, Monoline, Split/Fragmented, Responsive/Adaptive |
## Color Psychology
| Color | Psychology | Best For |
|-------|------------|----------|
| Blue | Trust, stability | Finance, tech, healthcare |
| Green | Growth, natural | Eco, wellness, organic |
| Red | Energy, passion | Food, sports, entertainment |
| Gold | Luxury, premium | Fashion, jewelry, hotels |
| Purple | Creative, innovative | Beauty, creative, tech |
## Industry Defaults
| Industry | Style | Colors | Typography |
|----------|-------|--------|------------|
| Tech | Minimalist, Abstract | Blues, purples, gradients | Geometric sans |
| Healthcare | Professional, Line Art | Blues, greens, teals | Clean sans |
| Finance | Corporate, Emblem | Navy, gold | Serif or clean sans |
| Food | Vintage Badge, Mascot | Warm reds, oranges | Friendly, script |
| Fashion | Wordmark, Luxury | Black, gold, white | Elegant serif |
## Workflow
1. Generate design brief → `scripts/logo/search.py --design-brief`
2. Generate logo variations → `scripts/logo/generate.py --brand --style --industry`
3. Ask user about HTML preview → `AskUserQuestion` tool
4. If yes, invoke `/ui-ux-pro-max` for HTML gallery
## Detailed References
- `references/logo-style-guide.md` - Detailed style descriptions
- `references/logo-color-psychology.md` - Color meanings and combinations
- `references/logo-prompt-engineering.md` - AI generation prompts
## Setup
```bash
export GEMINI_API_KEY="your-key"
pip install google-genai
```

View File

@@ -0,0 +1,158 @@
# Logo AI Prompt Engineering
## Core Prompt Structure
```
Professional logo design for [brand/industry]:
[Visual description]
Style: [style keywords]
Colors: [color palette]
Requirements: [technical specs]
```
## Effective Keywords by Style
### Minimalist
```
minimalist, clean lines, simple geometric shapes, essential elements only,
high white space, flat design, single color, modern, uncluttered,
negative space, subtle, refined
```
### Vintage/Retro
```
vintage, retro, heritage, established, classic, nostalgic, weathered,
distressed texture, badge style, hand-lettered, craft, artisan,
sepia tones, muted colors, aged paper effect
```
### Luxury/Premium
```
luxury, elegant, sophisticated, premium, refined, exclusive, high-end,
gold accents, metallic, minimal, tasteful, upscale, prestige,
thin lines, serif typography, foil effect
```
### Modern/Tech
```
modern, innovative, digital, tech-forward, sleek, futuristic,
gradient colors, geometric, abstract, dynamic, cutting-edge,
clean sans-serif, circuit-like, data visualization
```
### Playful/Fun
```
playful, fun, colorful, friendly, approachable, cheerful, whimsical,
bouncy, rounded shapes, bright colors, cartoon-like, energetic,
bubbly, hand-drawn elements
```
### Organic/Natural
```
organic, natural, flowing, botanical, eco-friendly, sustainable,
earth tones, leaf elements, hand-drawn, imperfect lines, growth,
green, nature-inspired, biophilic
```
## Negative Prompts (What to Avoid)
Always include to prevent unwanted results:
```
NOT: photorealistic, 3D render with realistic textures, photograph,
stock image, clip art, multiple logos, busy background, text watermarks,
low quality, blurry, distorted, complex detailed patterns
```
## Industry-Specific Prompts
### Tech Startup
```
Modern tech company logo, abstract geometric mark, gradient blue to purple,
clean minimal design, innovative feel, scalable vector style,
professional quality, silicon valley aesthetic
```
### Healthcare
```
Healthcare medical logo, clean professional design, cross or heart symbol,
calming blue and teal colors, trustworthy appearance, caring feel,
simple scalable mark, HIPAA-appropriate conservative style
```
### Restaurant/Food
```
Restaurant logo, warm inviting colors, appetizing feel, vintage badge style,
chef or utensil iconography, friendly welcoming design, rustic charm,
established look, readable at small sizes
```
### Fashion Brand
```
Fashion brand logo, elegant sophisticated wordmark, luxury aesthetic,
black and gold color scheme, thin refined typography, haute couture feel,
minimal exclusive design, high-end positioning
```
### Eco/Sustainable
```
Eco-friendly sustainable brand logo, organic natural elements, leaf motif,
earth green and brown colors, growth symbolism, environmental awareness,
clean modern yet natural feel, recyclable-look design
```
## Technical Requirements to Include
### Scalability
```
vector-style, scalable at any size, clear silhouette,
works as favicon, recognizable small scale, simple shapes
```
### Versatility
```
works on light and dark backgrounds, single color version possible,
horizontal and stacked layouts, brand mark can stand alone
```
### Quality
```
professional quality, print-ready, high resolution,
crisp edges, balanced composition, centered design
```
## Prompt Templates
### Quick Generation
```
Professional [industry] logo, [style] design, [color] colors,
clean modern aesthetic, scalable vector style
```
### Detailed Brief
```
Professional logo design for [brand name], a [industry] company.
Visual style: [style keywords]
Primary colors: [hex codes]
Mood: [emotional keywords]
Symbols: [iconography hints]
Technical: Vector-style illustration, scalable, works in single color,
centered on plain background, no text unless specified.
```
### Variation Request
```
Alternative version of [brand] logo:
Keep: [elements to preserve]
Change: [elements to modify]
Style direction: [new style keywords]
```
## Common Pitfalls
1. **Too detailed** - AI generates complexity; request "simple"
2. **Unclear background** - Specify "plain white background"
3. **Text issues** - AI struggles with text; generate mark separately
4. **Wrong aspect** - Specify "1:1 square" or "horizontal"
5. **Realistic style** - Add "illustration, vector-style, not photorealistic"

View File

@@ -0,0 +1,109 @@
# Logo Style Guide
## Core Logo Types
### 1. Wordmark (Logotype)
Text-only logo using custom typography.
- **Best for:** Established brands, distinctive names
- **Examples:** Google, Coca-Cola, FedEx
- **Typography:** Custom letterforms, unique kerning
- **Tip:** Name must be memorable and pronounceable
### 2. Lettermark (Monogram)
Initials or abbreviated letters.
- **Best for:** Long company names, professional firms
- **Examples:** IBM, HBO, NASA
- **Typography:** Bold geometric sans-serif
- **Tip:** Works well for brands with 2-4 letter abbreviations
### 3. Pictorial Mark (Brand Mark)
Standalone icon or symbol.
- **Best for:** Global brands with recognition
- **Examples:** Apple, Twitter, Target
- **Design:** Simple, scalable, memorable shape
- **Tip:** Requires brand equity to work alone
### 4. Abstract Mark
Non-representational geometric shapes.
- **Best for:** Tech companies, differentiating brands
- **Examples:** Pepsi, Airbnb, Spotify
- **Design:** Unique shape conveying brand values
- **Tip:** Can represent complex ideas simply
### 5. Mascot
Character representing the brand.
- **Best for:** Family brands, sports teams, food
- **Examples:** KFC, Pringles, Michelin
- **Design:** Friendly, expressive, versatile
- **Tip:** Can evolve with brand while maintaining recognition
### 6. Emblem
Symbol enclosed within a shape.
- **Best for:** Traditional brands, organizations
- **Examples:** Starbucks, Harley-Davidson, NFL
- **Design:** Badge, seal, or crest style
- **Tip:** May have scalability challenges
### 7. Combination Mark
Icon + text in unified design.
- **Best for:** New brands, versatile applications
- **Examples:** Burger King, Lacoste, Doritos
- **Design:** Lockup with flexible arrangements
- **Tip:** Most versatile, can separate elements later
## Aesthetic Styles
### Minimalist
- Clean lines, essential elements only
- High white space, simple geometry
- Limited color palette (1-2 colors)
- **Use:** Tech, professional services, modern brands
### Vintage/Retro
- Nostalgic, heritage feel
- Distressed textures, muted colors
- Script or slab serif typography
- **Use:** Craft brands, artisan products
### Luxury/Premium
- Elegant, refined aesthetic
- Gold, black, white color scheme
- Thin serifs or sophisticated sans
- **Use:** Fashion, jewelry, high-end services
### Geometric
- Mathematical precision
- Circles, triangles, squares
- Perfect symmetry
- **Use:** Architecture, tech, modern brands
### Organic/Natural
- Flowing, imperfect lines
- Earth tones, natural colors
- Hand-drawn feel
- **Use:** Eco brands, wellness, organic products
### Gradient/Modern
- Color transitions, vibrant palettes
- Dimensional depth
- Contemporary feel
- **Use:** Apps, tech startups, digital products
## Style Selection Matrix
| Brand Type | Primary Style | Secondary Options |
|------------|---------------|-------------------|
| Tech Startup | Minimalist, Abstract | Geometric, Gradient |
| Law Firm | Wordmark, Emblem | Lettermark |
| Restaurant | Mascot, Badge | Vintage, Combination |
| Fashion | Wordmark, Luxury | Monogram, Line Art |
| Healthcare | Professional, Line Art | Abstract, Combination |
| Non-Profit | Combination, Emblem | Organic, Hand-Drawn |
## Scalability Checklist
- [ ] Recognizable at 16x16 pixels (favicon)
- [ ] Clear at business card size
- [ ] Works in single color
- [ ] Maintains clarity in black/white
- [ ] No tiny details that disappear when scaled

View File

@@ -0,0 +1,185 @@
# Logo Usage Rules
Guidelines for proper logo implementation across all marketing materials.
## Logo Variants
### Primary Variants
| Variant | File Name | Use Case |
|---------|-----------|----------|
| Full Horizontal | logo-full-horizontal.{ext} | Website headers, documents |
| Stacked | logo-stacked.{ext} | Square spaces, social avatars |
| Icon Only | logo-icon.{ext} | Favicons, app icons, small spaces |
| Wordmark Only | logo-wordmark.{ext} | When icon already present |
### Color Variants
| Variant | Use Case |
|---------|----------|
| Full Color | Default on white/light backgrounds |
| Reversed | On dark backgrounds |
| Monochrome Dark | On light backgrounds when color not possible |
| Monochrome Light | On dark backgrounds when color not possible |
## Clear Space
### Minimum Clear Space
The clear space around the logo should equal the height of the logo mark (icon portion).
```
┌─────────────────────────────┐
│ [x] │
│ ┌───────────────────┐ │
│ │ │ │
[x] │ │ [LOGO] │ [x] │
│ │ │ │
│ └───────────────────┘ │
│ [x] │
└─────────────────────────────┘
```
Where [x] = height of logo mark
## Minimum Size
### Digital
| Format | Minimum Width | Notes |
|--------|---------------|-------|
| Full Logo | 120px | All elements legible |
| Icon Only | 24px | Favicon/small icons |
| Icon Only | 32px | UI elements |
### Print
| Format | Minimum Width | Notes |
|--------|---------------|-------|
| Full Logo | 35mm | Business cards, letterhead |
| Icon Only | 10mm | Small print items |
## Color Usage
### Approved Backgrounds
| Background | Logo Version |
|------------|--------------|
| White | Full color or dark mono |
| Light gray (#F5F5F5+) | Full color or dark mono |
| Brand primary | Reversed (white) |
| Dark (#333 or darker) | Reversed (white) |
| Photography | Ensure sufficient contrast |
### Color Rules
1. Never change logo colors outside approved palette
2. Don't use gradients on the logo
3. Don't apply transparency to logo elements
4. Don't add shadows or effects
## Incorrect Usage
### Absolute Don'ts
- ❌ Stretch or compress logo
- ❌ Rotate at angles
- ❌ Add drop shadows
- ❌ Apply gradient fills
- ❌ Use unapproved colors
- ❌ Add strokes or outlines
- ❌ Place on busy backgrounds
- ❌ Crop any portion
- ❌ Rearrange elements
- ❌ Add additional elements
### Visual Examples
```
WRONG: Stretched WRONG: Rotated WRONG: Wrong color
┌──────────────┐ ┌────────┐ ┌────────┐
│ L O G O │ │ / │ │ LOGO │ <- wrong color
└──────────────┘ │ /LOGO │ └────────┘
└───────/
```
## Co-branding
### Partner Logo Guidelines
1. Equal visual weight (same height)
2. Adequate separation between logos
3. Use divider line if needed
4. Both logos in their approved colors
5. Clear space applies to both
### Layout Options
```
Option A: Side by side with divider
[OUR LOGO] | [PARTNER LOGO]
Option B: Stacked
[OUR LOGO]
+
[PARTNER LOGO]
```
## File Formats
### Recommended Formats
| Usage | Format | Notes |
|-------|--------|-------|
| Web | SVG | Preferred, scalable |
| Web fallback | PNG | With transparency |
| Print | PDF | Vector, high quality |
| Print alt | EPS | Legacy systems |
| Documents | PNG | High res (300dpi) |
### File Organization
```
assets/logos/
├── full-horizontal/
│ ├── logo-full-color.svg
│ ├── logo-full-color.png
│ ├── logo-reversed.svg
│ ├── logo-mono-dark.svg
│ └── logo-mono-light.svg
├── icon-only/
│ ├── icon-full-color.svg
│ ├── icon-reversed.svg
│ └── favicon.ico
└── monochrome/
├── logo-black.svg
└── logo-white.svg
```
## Platform-Specific Guidelines
### Social Media
| Platform | Format | Size | Notes |
|----------|--------|------|-------|
| LinkedIn | PNG | 300x300px | Icon only |
| Twitter/X | PNG | 400x400px | Icon only |
| Facebook | PNG | 180x180px | Icon only |
| Instagram | PNG | 320x320px | Icon only |
### Website
| Location | Variant | Size |
|----------|---------|------|
| Header | Full horizontal | 120-200px width |
| Footer | Full horizontal | 100-150px width |
| Favicon | Icon only | 32x32px |
| Apple Touch | Icon only | 180x180px |
### Documents
| Document | Variant | Placement |
|----------|---------|-----------|
| Letterhead | Full horizontal | Top left |
| Presentation | Icon + wordmark | Title slide |
| Report | Full horizontal | Cover + footer |
## Logo Approval Process
### Before Using Logo
1. Verify you have the correct version
2. Check background compatibility
3. Ensure minimum size requirements
4. Confirm clear space allocation
5. Review against these guidelines
### Requesting Approval
For non-standard uses:
1. Submit mockup showing proposed usage
2. Include context (medium, audience)
3. Wait for brand team approval
4. Document approved exceptions

View File

@@ -0,0 +1,85 @@
# Messaging Framework
## Framework Structure
```
Mission (Why we exist)
Vision (Where we're going)
Value Proposition (What we offer)
Positioning Statement (How we're different)
Key Messages (What we say)
Proof Points (Why to believe)
```
## Core Statements
### Mission Statement
```
We [action] for [audience] by [method] so they can [outcome].
```
### Vision Statement
```
A world where [aspiration/change we want to see].
```
### Value Proposition
```
For [target customer] who [need/problem],
[Product/Brand] is a [category]
that [key benefit].
Unlike [competitors],
we [unique differentiator].
```
### Positioning Statement
```
[Brand] is the [category] for [audience]
who want [desired outcome]
because [reason to believe].
```
## Message Architecture
### Primary Message
One sentence that captures your core value.
### Supporting Messages (3-5)
Each addresses a different benefit or audience need.
| Message | Audience Need | Proof Point |
|---------|---------------|-------------|
| [Message 1] | [Need] | [Evidence] |
| [Message 2] | [Need] | [Evidence] |
| [Message 3] | [Need] | [Evidence] |
### Elevator Pitches
**10-second:**
[One sentence that sparks interest]
**30-second:**
[Problem + solution + differentiation]
**60-second:**
[Full pitch with proof points]
## Message by Audience
| Audience | Pain Point | Key Message | CTA |
|----------|------------|-------------|-----|
| [Segment 1] | [Pain] | [Message] | [Action] |
| [Segment 2] | [Pain] | [Message] | [Action] |
## Message Testing
1. Is it clear? (No jargon)
2. Is it differentiated? (Competitors can't say it)
3. Is it credible? (Can we prove it)
4. Is it compelling? (Does audience care)
5. Is it consistent? (Aligns with brand)

View File

@@ -0,0 +1,178 @@
# Metal Shader Reference
Expert reference for Metal shaders, real-time rendering, and Apple's Tile-Based Deferred Rendering (TBDR) architecture.
## Core Principles
**Half precision first → Leverage TBDR → Function constant specialization → Use Intersector API**
### When to Use
- Metal Shading Language (MSL) development
- Apple GPU optimization (TBDR architecture)
- PBR rendering pipelines
- Compute shaders and parallel processing
- Apple Silicon ray tracing
- GPU profiling and debugging
### When NOT to Use
- WebGL/GLSL (different architecture)
- CUDA (NVIDIA only)
- OpenGL (deprecated on Apple)
- CPU-side optimization
## Expert vs Novice
| Topic | Novice | Expert |
|-------|--------|--------|
| Data types | `float` everywhere | Default `half`, `float` only for position/depth |
| Branching | Runtime conditionals | Function constants for compile-time elimination |
| Memory | Everything in device | Know constant/device/threadgroup tradeoffs |
| Architecture | Treat as desktop GPU | Understand TBDR: tile memory is free, bandwidth is expensive |
| Ray tracing | intersection queries | intersector API (hardware-aligned) |
| Debugging | print debugging | GPU capture, shader profiler, occupancy analysis |
## Common Anti-Patterns
| Anti-Pattern | Problem | Solution |
|--------------|---------|----------|
| 32-bit floats | Wastes registers, reduces occupancy, doubles bandwidth | Default `half`, `float` only for position/depth |
| Ignoring TBDR | Not using free tile memory | Use `[[color(n)]]`, memoryless targets |
| Runtime constant branches | Warp divergence, wastes ALU | Function constants + pipeline specialization |
| intersection queries | Not hardware-aligned | Use intersector API |
## Metal Evolution
| Era | Key Development |
|-----|-----------------|
| Metal 2.x | OpenGL migration, basic compute |
| Apple Silicon | Unified memory, tile shaders critical |
| Metal 3 | Mesh shaders, hardware-accelerated ray tracing |
| Latest | Neural Engine + GPU cooperation, Vision Pro foveated rendering |
**Apple Family 9 Note**: Threadgroup memory less advantageous vs direct device access.
## Shader Types
| Type | Purpose | Key Attributes |
|------|---------|----------------|
| Vertex | Vertex transformation | `[[stage_in]]`, `[[buffer(n)]]` |
| Fragment | Pixel shading | `[[color(n)]]`, `[[texture(n)]]` |
| Compute/Kernel | General computation | `[[thread_position_in_grid]]` |
| Tile | TBDR-specific | `[[imageblock]]` |
| Mesh | Metal 3 geometry | `[[mesh_id]]` |
## Rendering Techniques
| Technique | Description |
|-----------|-------------|
| Fullscreen quad | 4 vertex triangle strip, no MVP, post-processing basis |
| PBR Cook-Torrance | Fresnel Schlick + GGX Distribution + Smith Geometry |
| Blinn-Phong | Simple specular, half-vector calculation |
## Procedural Generation
| Technique | Use Case |
|-----------|----------|
| Hash functions | Pseudo-random basis for noise, random sampling |
| Voronoi | Cell textures, stones, cracks |
| Value/Perlin Noise | Continuous random fields |
| FBM | Multi-octave layering, fractal terrain, clouds |
| Domain Warping | Coordinate distortion, organic shapes |
## Numerical Techniques
| Technique | Formula |
|-----------|---------|
| Central difference gradient | `(f(x+h) - f(x-h)) / (2h)` |
| Smoothstep | `x * x * (3 - 2 * x)` |
| SDF operations | `min/max/smooth_min` boolean ops |
## SwiftUI + MTKView Integration
### Architecture Pattern
```
MetalView (UIViewRepresentable)
└── Coordinator = Renderer (MTKViewDelegate)
├── MTLDevice
├── MTLCommandQueue
├── MTLRenderPipelineState
└── MTLBuffer (vertices, uniforms)
```
### Uniform Alignment Rules
| Swift Type | Metal Type | Alignment |
|------------|------------|-----------|
| `Float` | `float` | 4 bytes |
| `SIMD2<Float>` | `float2` | 8 bytes |
| `SIMD3<Float>` | `float3` | **16 bytes** |
| `SIMD4<Float>` | `float4` | 16 bytes |
**Key**: `float3` aligns to 16 bytes. Use `MemoryLayout<T>.size` to verify.
## Command Line Tools
| Command | Purpose |
|---------|---------|
| `xcrun metal -c shader.metal -o shader.air` | Compile to AIR |
| `xcrun metallib shader.air -o shader.metallib` | Link to metallib |
| `xcrun metal shader.metal -o shader.metallib` | One-step compile & link |
| `xcrun metal -Weverything -c shader.metal` | Syntax check |
| `xcrun metal-objdump --disassemble shader.metallib` | Disassemble |
## GPU Debugging
### Xcode Workflow
1. **GPU Capture**: ⌘⇧⌥G
2. **Shader Profiler**: Select draw call → View Shader
3. **Memory Viewer**: Inspect buffer/texture
4. **Performance HUD**: Enable in device options
### Key Metrics
| Metric | Healthy Value | Low Value Cause |
|--------|---------------|-----------------|
| GPU Occupancy | > 80% | Memory bandwidth bottleneck |
| ALU Utilization | > 60% | Waiting on memory |
| Bandwidth | As low as possible | TBDR should minimize store |
### Debug Utility Functions
| Function | Purpose |
|----------|---------|
| heatmap | Value visualization (blue→green→red) |
| debugNaN | NaN/Inf detection (magenta marker) |
| visualizeDepth | Linearized depth visualization |
## Performance Optimization Checklist
### Data Types
- [ ] Default `half`, `float` only for position/depth
### Memory Management
- [ ] Constants in constant address space
- [ ] Use `.storageModeShared`
- [ ] Leverage tile memory (TBDR free reads)
- [ ] Avoid unnecessary render target stores
### Branch Optimization
- [ ] Function constants to eliminate branches
- [ ] Fixed loop bounds (GPU unrolling)
### Rendering Tips
- [ ] Fullscreen quad with 4 vertex triangle strip
- [ ] Procedural textures to avoid sampling bandwidth
- [ ] `[[early_fragment_tests]]` for early depth test
- [ ] `setFragmentBytes` for small data
### Compute Optimization
- [ ] Vectorize (SIMD)
- [ ] Reduce register pressure
---
*Metal, Apple Silicon, and Xcode are trademarks of Apple Inc.*

View File

@@ -0,0 +1,133 @@
# Provider Reference — MiniMax
All asset generation uses MiniMax API. Env: `MINIMAX_API_KEY` (required).
## Audio (Sync TTS)
**Script:** `scripts/minimax_tts.py`
```bash
python scripts/minimax_tts.py "Hello world" -o output.mp3
python scripts/minimax_tts.py "你好" -o hi.mp3 -v female-shaonv
python scripts/minimax_tts.py "Welcome" -o out.wav -v male-qn-jingying --speed 0.8 --format wav
```
**Model:** `speech-2.8-hd` (default).
| Flag | Default | Range / Options |
|------|---------|-----------------|
| `-o` | (required) | Output file path |
| `-v` | `male-qn-qingse` | Voice ID |
| `--model` | `speech-2.8-hd` | speech-2.8-hd / speech-2.8-turbo / speech-2.6-hd / speech-2.6-turbo |
| `--speed` | 1.0 | 0.52.0 |
| `--volume` | 1.0 | 0.110 |
| `--pitch` | 0 | -12 to 12 |
| `--emotion` | (auto) | happy / sad / angry / fearful / disgusted / surprised / calm / fluent / whisper |
| `--format` | mp3 | mp3 / wav / flac |
| `--lang` | auto | Language boost |
**Programmatic:**
```python
from minimax_tts import tts
audio_bytes = tts("Hello", voice_id="female-shaonv")
```
## Video (Text-to-Video)
**Script:** `scripts/minimax_video.py`
```bash
python scripts/minimax_video.py "A cat playing piano" -o cat.mp4
python scripts/minimax_video.py "Ocean waves [Truck left]" -o waves.mp4 --duration 10
python scripts/minimax_video.py "City skyline [Push in]" -o city.mp4 --resolution 1080P
```
**Model:** `MiniMax-Hailuo-2.3` (default). Async: script handles create → poll → download automatically.
| Flag | Default | Options |
|------|---------|---------|
| `-o` | (required) | Output file path (.mp4) |
| `--model` | `MiniMax-Hailuo-2.3` | MiniMax-Hailuo-2.3 / MiniMax-Hailuo-02 / T2V-01-Director / T2V-01 |
| `--duration` | 6 | 6 / 10 (10s only at 768P with Hailuo models) |
| `--resolution` | 768P | 720P / 768P / 1080P (1080P only 6s) |
| `--no-optimize` | false | Disable prompt auto-optimization |
| `--poll-interval` | 10 | Seconds between status checks |
| `--max-wait` | 600 | Max wait time in seconds |
**Camera commands** — insert `[Command]` in prompt: `[Push in]`, `[Truck left]`, `[Pan right]`, `[Zoom out]`, `[Static shot]`, `[Tracking shot]`, etc.
**Programmatic:**
```python
from minimax_video import generate
generate("A cat playing piano", "cat.mp4", model="MiniMax-Hailuo-2.3", duration=6)
```
See [minimax-video-guide.md](minimax-video-guide.md) for full camera command list and model compatibility.
## Image (Text-to-Image)
**Script:** `scripts/minimax_image.py`
```bash
python scripts/minimax_image.py "A cat astronaut in space" -o cat.png
python scripts/minimax_image.py "Mountain landscape" -o hero.png --ratio 16:9
python scripts/minimax_image.py "Product icons, flat style" -o icons.png -n 4 --seed 42
```
**Model:** `image-01`. Sync: returns image URL (or base64) immediately.
| Flag | Default | Options |
|------|---------|---------|
| `-o` | (required) | Output file path (.png/.jpg) |
| `--ratio` | 1:1 | 1:1 / 16:9 / 4:3 / 3:2 / 2:3 / 3:4 / 9:16 / 21:9 |
| `-n` | 1 | Number of images (19) |
| `--seed` | (random) | Seed for reproducibility |
| `--optimize` | false | Enable prompt auto-optimization |
| `--base64` | false | Return base64 instead of URL |
**Batch output:** with `-n > 1`, files are named `out-0.png`, `out-1.png`, etc.
**Programmatic:**
```python
from minimax_image import generate_image, download_and_save
result = generate_image("A cat in space", aspect_ratio="16:9")
download_and_save(result["data"]["image_urls"][0], "cat.png")
```
See [minimax-image-guide.md](minimax-image-guide.md) for ratio dimensions and details.
## Music (Text-to-Music)
**Script:** `scripts/minimax_music.py`
```bash
python scripts/minimax_music.py --prompt "Indie folk, melancholic" --lyrics "[verse]\nStreetlights flicker" -o song.mp3
python scripts/minimax_music.py --prompt "Upbeat pop, energetic" --auto-lyrics -o pop.mp3
python scripts/minimax_music.py --prompt "Jazz piano, smooth, relaxing" --instrumental -o jazz.mp3
```
**Model:** `music-2.5+` (default). Sync: returns audio hex or URL.
| Flag | Default | Options |
|------|---------|---------|
| `-o` | (required) | Output file path (.mp3/.wav) |
| `--prompt` | (empty) | Music description: style, mood, scenario (max 2000 chars) |
| `--lyrics` | (empty) | Song lyrics with structure tags (max 3500 chars) |
| `--lyrics-file` | (empty) | Read lyrics from file |
| `--model` | `music-2.5+` | music-2.5+ / music-2.5 |
| `--instrumental` | false | Generate instrumental only (no vocals, music-2.5+ only) |
| `--auto-lyrics` | false | Auto-generate lyrics from prompt |
| `--format` | mp3 | mp3 / wav / pcm |
| `--sample-rate` | 44100 | 16000 / 24000 / 32000 / 44100 |
| `--bitrate` | 256000 | 32000 / 64000 / 128000 / 256000 |
**Lyrics structure tags:** `[Intro]`, `[Verse]`, `[Pre Chorus]`, `[Chorus]`, `[Interlude]`, `[Bridge]`, `[Outro]`, `[Post Chorus]`, `[Transition]`, `[Break]`, `[Hook]`, `[Build Up]`, `[Inst]`, `[Solo]`
**Programmatic:**
```python
from minimax_music import generate_music
result = generate_music(prompt="Jazz piano", is_instrumental=True)
with open("jazz.mp3", "wb") as f:
f.write(result["audio_bytes"])
```

View File

@@ -0,0 +1,65 @@
# Image Generation Guide
## CLI usage
```bash
# Basic (1:1, 1024x1024)
python scripts/minimax_image.py "A cat astronaut floating in space" -o cat.png
# 16:9 for hero banner
python scripts/minimax_image.py "Mountain landscape at golden hour" -o hero.png --ratio 16:9
# Batch: 4 images at once
python scripts/minimax_image.py "Minimalist product icon" -o icons.png -n 4
# With seed for reproducibility
python scripts/minimax_image.py "Abstract gradient background" -o bg.png --seed 42
# Enable prompt optimization
python scripts/minimax_image.py "a dog" -o dog.png --optimize
# Base64 mode (no URL download, save directly)
python scripts/minimax_image.py "Logo concept" -o logo.png --base64
```
## Programmatic usage
```python
from minimax_image import generate_image, download_and_save
# Generate and get URL
result = generate_image("A cat in space", aspect_ratio="16:9")
url = result["data"]["image_urls"][0]
download_and_save(url, "cat.png")
# Generate multiple
result = generate_image("Icon design", n=4, aspect_ratio="1:1")
for i, url in enumerate(result["data"]["image_urls"]):
download_and_save(url, f"icon-{i}.png")
```
## Model
Currently only `image-01`.
## Aspect ratios & dimensions
| Ratio | Pixels | Use case |
|-------|--------|----------|
| `1:1` | 1024x1024 | Avatar, icon, square thumbnail |
| `16:9` | 1280x720 | Hero banner, video thumbnail |
| `4:3` | 1152x864 | Standard landscape |
| `3:2` | 1248x832 | Photo-style |
| `2:3` | 832x1248 | Portrait, mobile |
| `3:4` | 864x1152 | Portrait card |
| `9:16` | 720x1280 | Mobile fullscreen, story |
| `21:9` | 1344x576 | Ultra-wide banner |
Custom dimensions also supported: width/height in [512, 2048], must be divisible by 8.
## Limits
- Prompt: max 1,500 characters
- Batch: 19 images per request
- URL expires after 24 hours (use `--base64` to avoid expiry)
- Seed: set for reproducible results across identical prompts

View File

@@ -0,0 +1,216 @@
# Music Generation Guide
## CLI Usage
```bash
# Instrumental (no vocals)
python scripts/minimax_music.py --prompt "Jazz piano, smooth, relaxing" --instrumental -o jazz.mp3
# With custom lyrics
python scripts/minimax_music.py --prompt "Indie folk, melancholic" --lyrics "[verse]\nStreetlights flicker\nOn empty roads" -o song.mp3
# Auto-generate lyrics from prompt
python scripts/minimax_music.py --prompt "Upbeat pop, energetic, summer vibes" --auto-lyrics -o pop.mp3
# From lyrics file
python scripts/minimax_music.py --prompt "Soulful blues, rainy night" --lyrics-file lyrics.txt -o blues.mp3
# Custom audio settings
python scripts/minimax_music.py --prompt "Lo-fi beats" --instrumental -o lofi.wav --format wav --sample-rate 44100 --bitrate 256000
```
## Programmatic Usage
```python
from minimax_music import generate_music
# Instrumental
result = generate_music(prompt="Jazz piano, smooth", is_instrumental=True)
with open("jazz.mp3", "wb") as f:
f.write(result["audio_bytes"])
# With lyrics
result = generate_music(
prompt="Indie folk, acoustic guitar",
lyrics="[verse]\nWalking through the rain\n[chorus]\nI'll find my way home",
)
# Auto-generate lyrics
result = generate_music(
prompt="Upbeat pop, summer anthem",
lyrics_optimizer=True,
)
# Access metadata
print(f"Duration: {result['duration']}ms")
print(f"Sample rate: {result['sample_rate']}")
print(f"Size: {result['size']} bytes")
```
## Models
| Model | Features |
|-------|----------|
| `music-2.5+` | Recommended. Supports instrumental mode, complete song structures, hi-fi audio |
| `music-2.5` | Standard model. No instrumental mode |
## Prompt Writing
The `prompt` parameter describes music style using comma-separated descriptors:
| Category | Examples |
|----------|----------|
| Genre | Blues, Pop, Rock, Jazz, Electronic, Hip-hop, Folk, Classical |
| Mood | Soulful, Melancholy, Upbeat, Energetic, Peaceful, Dark, Nostalgic |
| Scenario | Rainy night, Summer day, Road trip, Late night, Sunrise |
| Instrumentation | Electric guitar, Piano, Acoustic, Synthesizer, Strings |
| Vocal type | Male vocals, Female vocals, Soft vocals, Powerful vocals |
| Tempo | Slow tempo, Fast tempo, Mid-tempo, Relaxed |
**Example prompts:**
```
"Soulful Blues, Rainy Night, Melancholy, Male Vocals, Slow Tempo"
"Upbeat Pop, Summer Vibes, Female Vocals, Energetic, Synth-heavy"
"Lo-fi Hip-hop, Chill, Relaxed, Instrumental, Piano samples"
"Cinematic Orchestral, Epic, Building tension, Strings and Brass"
```
## Lyrics Format
Use structure tags in brackets to organize song sections:
### Structure Tags
| Tag | Purpose |
|-----|---------|
| `[Intro]` | Opening section (can be instrumental) |
| `[Verse]` / `[Verse 1]` | Story/narrative sections |
| `[Pre-Chorus]` | Build-up before chorus |
| `[Chorus]` | Main hook, typically repeated |
| `[Post Chorus]` | Extension after chorus |
| `[Bridge]` | Contrasting section near end |
| `[Interlude]` | Instrumental break |
| `[Solo]` | Instrumental solo (add direction: "slow, bluesy") |
| `[Outro]` | Closing section |
| `[Break]` | Short pause or transition |
| `[Hook]` | Catchy repeated phrase |
| `[Build Up]` | Tension building section |
| `[Inst]` | Instrumental section |
| `[Transition]` | Section change |
### Backing Vocals & Directions
Use parentheses for backing vocals or performance notes:
```
(Ooh, yeah)
(Harmonize)
(Whispered)
(Fade out...)
```
### Example Lyrics
```
[Intro]
(Soft piano)
[Verse 1]
Streetlights flicker on empty roads
The rain keeps falling, the wind still blows
I'm walking home with nowhere to go
Just memories of what I used to know
[Pre-Chorus]
And I can feel it coming back to me
(Coming back to me)
[Chorus]
Under the neon lights tonight
I'm searching for what feels right
(Oh, feels right)
These city streets will guide me home
I'm tired of feeling so alone
[Verse 2]
Coffee shops and midnight trains
The faces change but the feeling remains
...
[Bridge]
Maybe tomorrow will be different
Maybe I'll finally understand
(Understand...)
[Solo]
(Slow, mournful, bluesy guitar)
[Outro]
(Fade out...)
Under the neon lights...
```
## Audio Settings
| Parameter | Options | Default | Notes |
|-----------|---------|---------|-------|
| `format` | mp3, wav, pcm | mp3 | WAV for highest quality |
| `sample_rate` | 16000, 24000, 32000, 44100 | 44100 | 44100 recommended |
| `bitrate` | 32000, 64000, 128000, 256000 | 256000 | Higher = better quality |
## Generation Modes
### 1. Instrumental Only
```bash
python scripts/minimax_music.py --prompt "Ambient electronic, space theme" --instrumental -o ambient.mp3
```
- Requires `music-2.5+` model
- Only `prompt` needed, no lyrics
### 2. With Custom Lyrics
```bash
python scripts/minimax_music.py --prompt "Pop ballad, emotional" --lyrics "[verse]\nYour lyrics here" -o ballad.mp3
```
- Provide both `prompt` (style) and `lyrics` (words + structure)
### 3. Auto-Generated Lyrics
```bash
python scripts/minimax_music.py --prompt "Rock anthem about freedom" --auto-lyrics -o rock.mp3
```
- System generates lyrics from prompt
- Good for quick generation when lyrics aren't critical
## Limits
- **Prompt:** max 2,000 characters
- **Lyrics:** 13,500 characters
- **Duration:** ~25-30 seconds per generation (varies)
- **URL expiration:** 24 hours (when using URL output mode)
## Best Practices
1. **Layer style descriptors** — Combine genre + mood + instrumentation for precise results
2. **Use structure tags** — Even simple `[verse]` `[chorus]` improves arrangement
3. **Include backing vocal cues**`(Ooh)`, `(Yeah)` add production polish
4. **Match prompt to lyrics mood** — Conflicting prompt/lyrics produce inconsistent results
5. **Instrumental for backgrounds** — Use `--instrumental` for BGM, avoiding vocal distractions
6. **High bitrate for production** — Use 256000 for final assets, lower for drafts
## Common Use Cases
| Use Case | Command |
|----------|---------|
| Background music | `--prompt "Lo-fi, calm, ambient" --instrumental` |
| Landing page hero | `--prompt "Cinematic, inspiring, building" --instrumental` |
| Podcast intro | `--prompt "Upbeat, energetic, short" --instrumental` |
| Demo song | `--prompt "Pop, catchy" --auto-lyrics` |
| Custom jingle | `--prompt "Happy, bright, corporate" --lyrics "[hook]\nYour brand name"` |
## Error Handling
| Error Code | Meaning | Solution |
|------------|---------|----------|
| 1002 | Rate limit | Wait and retry |
| 1004 | Auth failed | Check API key |
| 1008 | Insufficient balance | Top up account |
| 1026 | Content flagged | Rephrase prompt/lyrics |
| 2013 | Invalid parameters | Check prompt/lyrics length |

View File

@@ -0,0 +1,78 @@
# TTS Guide
## CLI usage (recommended)
```bash
# Basic
python scripts/minimax_tts.py "Hello world" -o output.mp3
# Custom voice and speed
python scripts/minimax_tts.py "你好世界" -o hi.mp3 -v female-shaonv --speed 0.9
# WAV format, high quality
python scripts/minimax_tts.py "Welcome" -o out.wav -v male-qn-jingying --format wav --sample-rate 32000
# With emotion (for speech-2.6 models)
python scripts/minimax_tts.py "Great news!" -o happy.mp3 -v female-shaonv --emotion happy --model speech-2.6-hd
```
## Programmatic usage
```python
from minimax_tts import tts
# Basic
audio_bytes = tts("Hello world")
# With options
audio_bytes = tts(
text="Welcome to our product.",
voice_id="female-shaonv",
model="speech-2.8-hd",
speed=0.9,
fmt="mp3",
)
# Save to file
with open("output.mp3", "wb") as f:
f.write(audio_bytes)
```
## Limits
- **Sync TTS:** max 10,000 characters per request
- **Pause markers:** insert `<#1.5#>` for a 1.5s pause (range: 0.0199.99s)
## Model selection
| Model | Best for |
|-------|----------|
| `speech-2.8-hd` | Highest quality, auto emotion (recommended) |
| `speech-2.8-turbo` | Fast, good quality |
| `speech-2.6-hd` | Manual emotion control needed |
| `speech-2.6-turbo` | Fast + manual emotion |
## Voice selection
See [minimax-voice-catalog.md](minimax-voice-catalog.md) for the full list.
Common voices:
| Voice ID | Gender | Style |
|----------|--------|-------|
| `male-qn-qingse` | Male | Young, gentle |
| `male-qn-jingying` | Male | Elite, authoritative |
| `male-qn-badao` | Male | Dominant, powerful |
| `female-shaonv` | Female | Young, bright |
| `female-yujie` | Female | Mature, elegant |
| `female-chengshu` | Female | Sophisticated |
| `presenter_male` | Male | News presenter |
| `presenter_female` | Female | News presenter |
| `audiobook_male_1` | Male | Audiobook narrator |
| `audiobook_female_1` | Female | Audiobook narrator |
## Best practices
- Use `speech-2.8-hd` and let emotion auto-match — don't manually set emotion unless needed
- Use 32000 sample rate for web audio (good balance of quality and file size)
- For long text (>10,000 chars), split into chunks and merge with FFmpeg

View File

@@ -0,0 +1,82 @@
# Video Generation Guide
## CLI usage
```bash
# Basic
python scripts/minimax_video.py "A cat playing piano in a cozy room" -o cat.mp4
# With camera control
python scripts/minimax_video.py "Ocean waves crashing on rocks [Truck left]" -o waves.mp4
# 10 seconds, 1080P
python scripts/minimax_video.py "City skyline at sunset [Push in]" -o city.mp4 --duration 10 --resolution 1080P
# Disable prompt auto-optimization
python scripts/minimax_video.py "Exact prompt I want used" -o out.mp4 --no-optimize
```
## Programmatic usage
```python
from minimax_video import generate, create_task, poll_task, download_video
# Full pipeline (blocking)
generate("A cat playing piano", "cat.mp4", model="MiniMax-Hailuo-2.3", duration=6)
# Step by step
task_id = create_task("A cat playing piano")
file_id = poll_task(task_id, interval=10, max_wait=600)
download_video(file_id, "cat.mp4")
```
## Models
| Model | Resolution | Duration | Notes |
|-------|-----------|----------|-------|
| `MiniMax-Hailuo-2.3` | 768P, 1080P | 6s, 10s (768P only) | Latest, recommended |
| `MiniMax-Hailuo-02` | 768P, 1080P | 6s, 10s (768P only) | Previous gen |
| `T2V-01-Director` | 720P | 6s | Camera control optimized |
| `T2V-01` | 720P | 6s | Base model |
## Camera commands
Insert `[Command]` in prompt text to control camera movement:
| Command | Effect |
|---------|--------|
| `[Truck left]` | Camera moves left |
| `[Truck right]` | Camera moves right |
| `[Push in]` | Camera moves toward subject |
| `[Pull out]` | Camera moves away from subject |
| `[Pan left]` | Camera rotates left (fixed position) |
| `[Pan right]` | Camera rotates right (fixed position) |
| `[Tilt up]` | Camera tilts upward |
| `[Tilt down]` | Camera tilts downward |
| `[Pedestal up]` | Camera rises vertically |
| `[Pedestal down]` | Camera lowers vertically |
| `[Zoom in]` | Lens zooms in |
| `[Zoom out]` | Lens zooms out |
| `[Static shot]` | No camera movement |
| `[Tracking shot]` | Camera follows subject |
| `[Shake]` | Handheld shake effect |
Example: `"A runner sprints through a forest trail [Tracking shot]"`
## Pipeline
The script handles the full async flow:
1. **Create task**`POST /v1/video_generation` → returns `task_id`
2. **Poll status**`GET /v1/query/video_generation?task_id=xxx` → poll until `Success`
- Status values: `Preparing``Queueing``Processing``Success` / `Fail`
3. **Download**`GET /v1/files/retrieve?file_id=xxx` → get `download_url` (valid 1 hour) → save file
Typical generation time: 15 minutes depending on duration and resolution.
## Limits
- Prompt: max 2,000 characters
- 1080P: only supports 6s duration
- 10s duration: only available at 768P with Hailuo-2.3/02
- Download URL expires after 1 hour

View File

@@ -0,0 +1,686 @@
# MiniMax Voice Catalog
Complete reference for all available voices in the MiniMax Voice API.
## Contents
- [Voice Recommendation](#voice-recommendation) - Find voices by content type and characteristics
- [System Voices List (categorized by language)](#system-voices-list-categorized-by-language) - Complete voice database by language
- [Voice Parameters](#voice-parameters) - Configure voice settings (speed, volume, pitch, emotion)
- [Custom Voices](#custom-voices) - Voice cloning and voice design options
- [Voice Comparison Table](#voice-comparison-table) - Quick reference comparison
- [Voice IDs for Quick Reference](#voice-ids-for-quick-reference) - Most popular voices at a glance
---
## 1. How to Choose a Voice
When selecting a voice, follow this two-step decision process to ensure the voice matches the scenario, gender, age, and language of the character.
### Step 1: Identify the Usage Scenario
First, determine whether your content falls into one of the **three professional domains** listed in **Section 2.1**:
| Professional Domain | Examples |
|---|---|
| **Narration & Narrator in Storytelling** | suitable for the narrator in Audiobooks, fiction narration, storytelling |
| **News & Announcements** | suitable for news broadcasts, formal announcements, press releases |
| **Documentary** | suitable for documentary narration, commentary, educational films |
**If your content matches one of these professional domains:**
→ Prioritize selecting from the recommended voices in **Section 2.1**, filtering by scenario and the speaker's **gender**.
These voices are specifically optimized for their respective professional use cases (pacing, clarity, tone).
**If your content does NOT fall into these three professional domains:**
→ Proceed to Step 2 below.
### Step 2: Select by Character Traits (Gender + Age + Language)
For non-professional scenarios, select a voice from **Section 2.2** based on the following three character traits, in strict priority order:
1. **Gender** (highest priority, non-negotiable)
- Male characters → **must** use male voices
- Female characters → **must** use female voices
- Never mismatch gender, even if other traits seem to fit
2. **Age** (determines which subsection to look in)
- **Children** → Section 2.2 "Children's Voices"
- **Youth** (teens, young adults) → Section 2.2 "Youthful Voices"
- **Adult** → Section 2.2 "Adult Voices"
- **Elderly** → Section 2.2 "Elderly Voices"
3. **Language** (must match the content language)
- The voice **must** match the language of the content being generated
- Chinese content → select Chinese voices; Korean content → select Korean voices; English content → select English voices, etc.
- If no exact language match exists in Section 2.2, fall back to the full **System Voices List** (Section 3) for the target language
After narrowing down candidates by these three traits, choose the best match based on the voice's **personality**, **tone**, and **use case** as described in each voice entry.
### Quick Reference Decision Flow
```
Content Type?
├── Story/Narration/News/Documentary → Section 2.1 (filter by scenario + gender)
└── Other scenarios → Section 2.2:
├── 1. Match Gender (mandatory)
├── 2. Match Age Group (Children/Youth/Adult/Elderly/Professional)
├── 3. Match Language (must match content language)
└── 4. Choose best fit by personality/tone
```
---
## 2. Voice Recommendation
### 2.1 By Content Type
**Narration & Narrator in Storytelling**
- Recommended: `audiobook_female_1`, `audiobook_male_1`
- Characteristics: suitable for narrating stories, sustained performance, clear articulation, good pacing
**News & Announcements**
- Recommended: `Chinese (Mandarin)_News_Anchor`, `Chinese (Mandarin)_Male_Announcer`
- Characteristics: Authoritative, clear, professional pacing
**Documentary**
- Recommended: `doc_commentary`
- Characteristics: Professional, clear, consistent pacing
### 2.2 By Characteristics
#### Children's Voices
| voice_id | Name | Description | Best For | Language |
|----------|------|-------------|----------|----------|
| `clever_boy` | 聪明男童 | Smart, witty boy voice | Children's content, educational | Chinese (Mandarin) |
| `cute_boy` | 可爱男童 | Adorable young boy voice | Kids' content, animations | Chinese (Mandarin) |
| `lovely_girl` | 萌萌女童 | Cute, sweet girl voice | Children's stories, games | Chinese (Mandarin) |
| `cartoon_pig` | 卡通猪小琪 | Cartoon character voice | Animations, comedy, entertainment | Chinese (Mandarin) |
| `Korean_SweetGirl` | Sweet Girl | Sweet, adorable young girl voice | Children's content, romance | Korean |
| `Indonesian_SweetGirl` | Sweet Girl | Sweet, adorable girl voice | Children's content, friendly | Indonesian |
| `English_Sweet_Girl` | Sweet Girl | Sweet, innocent young girl voice | Children's content, friendly | English |
| `Spanish_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm | Spanish |
| `Portuguese_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm | Portuguese |
#### Youthful Voices
| voice_id | Name | Description | Best For | Language |
|----------|------|-------------|----------|----------|
| `male-qn-qingse` | 青涩青年 | Youthful, inexperienced young man voice | Campus stories, coming-of-age content | Chinese (Mandarin) |
| `male-qn-daxuesheng` | 青年大学生 | Young university student voice | Campus content, educational | Chinese (Mandarin) |
| `female-shaonv` | 少女 | Young maiden voice | Romance, youth content | Chinese (Mandarin) |
| `bingjiao_didi` | 病娇弟弟 | Tsundere young brother voice | Romance, character-driven content | Chinese (Mandarin) |
| `junlang_nanyou` | 俊朗男友 | Handsome boyfriend voice | Romance, dating content | Chinese (Mandarin) |
| `chunzhen_xuedi` | 纯真学弟 | Innocent junior student voice | Campus stories, youth content | Chinese (Mandarin) |
| `lengdan_xiongzhang` | 冷淡学长 | Cool senior student voice | Campus stories, romance | Chinese (Mandarin) |
| `diadia_xuemei` | 嗲嗲学妹 | Flirty junior girl voice | Romance, dating content | Chinese (Mandarin) |
| `danya_xuejie` | 淡雅学姐 | Elegant senior girl voice | Campus stories, romance | Chinese (Mandarin) |
| `Chinese (Mandarin)_Straightforward_Boy` | 率真弟弟 | Frank, straightforward boy voice | Casual, direct content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Sincere_Adult` | 真诚青年 | Sincere young adult voice | Honest, genuine content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Pure-hearted_Boy` | 清澈邻家弟弟 | Pure-hearted neighbor boy voice | Innocent, wholesome content | Chinese (Mandarin) |
| `Korean_CheerfulBoyfriend` | Cheerful Boyfriend | Energetic, loving boyfriend voice | Romance, dating content | Korean |
| `Korean_ShyGirl` | Shy Girl | Timid, reserved girl voice | Comedy, romance | Korean |
| `Japanese_SportyStudent` | Sporty Student | Energetic athletic student voice | Sports, youth content | Japanese |
| `Japanese_InnocentBoy` | Innocent Boy | Pure, naive young boy voice | Children's content | Japanese |
| `Spanish_SincereTeen` | SincereTeen | Honest, genuine teenager voice | Youth, authentic | Spanish |
| `Spanish_Strong-WilledBoy` | Strong-willed Boy | Determined, persistent boy voice | Youth, motivation | Spanish |
#### Adult Voices
| voice_id | Name | Description | Best For | Language |
|----------|------|-------------|----------|----------|
| `female-chengshu` | 成熟女性 | Mature woman voice | Sophisticated, adult content | Chinese (Mandarin) |
| `female-yujie` | 御姐 | Mature, elegant woman voice | Romance, professional content | Chinese (Mandarin) |
| `female-tianmei` | 甜美女性 | Sweet, pleasant woman voice | Soft, gentle content | Chinese (Mandarin) |
| `badao_shaoye` | 霸道少爷 | Arrogant young master voice | Drama, character roles | Chinese (Mandarin) |
| `wumei_yujie` | 妩媚御姐 | Charming mature woman voice | Romance, mature content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Gentleman` | 温润男声 | Gentle, refined male voice | Narration, storytelling | Chinese (Mandarin) |
| `Chinese (Mandarin)_Unrestrained_Young_Man` | 不羁青年 | Unrestrained young man voice | Casual, entertainment content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Southern_Young_Man` | 南方小哥 | Southern young man voice | Regional character, casual content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Gentle_Youth` | 温润青年 | Gentle young man voice | Narration, calm content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Warm_Girl` | 温暖少女 | Warm young girl voice | Friendly, supportive content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Soft_Girl` | 柔和少女 | Soft, gentle girl voice | Calm, soothing content | Chinese (Mandarin) |
| `Korean_PlayboyCharmer` | Playboy Charmer | Smooth, flirtatious male voice | Romance, entertainment | Korean |
| `Korean_CalmLady` | Calm Lady | Composed, serene female voice | Meditation, relaxation | Korean |
| `Spanish_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | Spanish |
| `Portuguese_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment | Portuguese |
#### Elderly Voices
| voice_id | Name | Description | Best For | Language |
|----------|------|-------------|----------|----------|
| `Chinese (Mandarin)_Humorous_Elder` | 搞笑大爷 | Humorous old man voice | Comedy, entertainment | Chinese (Mandarin) |
| `Chinese (Mandarin)_Kind-hearted_Elder` | 花甲奶奶 | Kind elderly lady voice | Stories, warm content | Chinese (Mandarin) |
| `Chinese (Mandarin)_Kind-hearted_Antie` | 热心大婶 | Kind-hearted auntie voice | Warm, friendly content | Chinese (Mandarin) |
| `Japanese_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Narration, educational | Japanese |
| `Korean_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Educational, narration | Korean |
| `Spanish_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice | Spanish |
| `Portuguese_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice | Portuguese |
| `Spanish_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom | Spanish |
| `Portuguese_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom | Portuguese |
| `English_Gentle-voiced_man` | Gentle-voiced Man | Soft-spoken, kind male voice | Calm, supportive content | English |
---
## System Voices List (categorized by language)
### Chinese Mandarin Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `male-qn-qingse` | 青涩青年 | Youthful, inexperienced young man voice | Campus stories, coming-of-age content |
| `male-qn-badao` | 霸道青年 | Arrogant, dominant young man voice | Drama, romance, character roles |
| `male-qn-daxuesheng` | 青年大学生 | Young university student voice | Campus content, educational |
| `female-shaonv` | 少女 | Young maiden voice | Romance, youth content |
| `female-yujie` | 御姐 | Mature, elegant woman voice | Romance, professional content |
| `female-chengshu` | 成熟女性 | Mature woman voice | Sophisticated, adult content |
| `female-tianmei` | 甜美女性 | Sweet, pleasant woman voice | Soft, gentle content |
| `clever_boy` | 聪明男童 | Smart, witty boy voice | Children's content, educational |
| `cute_boy` | 可爱男童 | Adorable young boy voice | Kids' content, animations |
| `lovely_girl` | 萌萌女童 | Cute, sweet girl voice | Children's stories, games |
| `cartoon_pig` | 卡通猪小琪 | Cartoon character voice | Animations, comedy, entertainment |
| `bingjiao_didi` | 病娇弟弟 | Tsundere young brother voice | Romance, character-driven content |
| `junlang_nanyou` | 俊朗男友 | Handsome boyfriend voice | Romance, dating content |
| `chunzhen_xuedi` | 纯真学弟 | Innocent junior student voice | Campus stories, youth content |
| `lengdan_xiongzhang` | 冷淡学长 | Cool senior student voice | Campus stories, romance |
| `badao_shaoye` | 霸道少爷 | Arrogant young master voice | Drama, character roles |
| `tianxin_xiaoling` | 甜心小玲 | Sweet Xiao Ling voice | Character roles, animations |
| `qiaopi_mengmei` | 俏皮萌妹 | Playful cute girl voice | Comedy, light-hearted content |
| `wumei_yujie` | 妩媚御姐 | Charming mature woman voice | Romance, mature content |
| `diadia_xuemei` | 嗲嗲学妹 | Flirty junior girl voice | Romance, dating content |
| `danya_xuejie` | 淡雅学姐 | Elegant senior girl voice | Campus stories, romance |
| `Arrogant_Miss` | 嚣张小姐 | Arrogant young lady voice | Drama, character roles |
| `Robot_Armor` | 机械战甲 | Robotic armor voice | Sci-fi, game characters |
| `Chinese (Mandarin)_Reliable_Executive` | 沉稳高管 | Reliable executive voice | Corporate, business content |
| `Chinese (Mandarin)_News_Anchor` | 新闻女声 | News anchor female voice | News broadcasts, current affairs |
| `Chinese (Mandarin)_Mature_Woman` | 傲娇御姐 | Tsundere mature woman voice | Romance, character-driven content |
| `Chinese (Mandarin)_Unrestrained_Young_Man` | 不羁青年 | Unrestrained young man voice | Casual, entertainment content |
| `male-qn-jingying` | 精英青年 | Elite, ambitious young man voice | Business, professional content |
| `Chinese (Mandarin)_Kind-hearted_Antie` | 热心大婶 | Kind-hearted auntie voice | Warm, friendly content |
| `Chinese (Mandarin)_HK_Flight_Attendant` | 港普空姐 | HK accent flight attendant voice | Regional character, entertainment |
| `Chinese (Mandarin)_Humorous_Elder` | 搞笑大爷 | Humorous old man voice | Comedy, entertainment |
| `Chinese (Mandarin)_Gentleman` | 温润男声 | Gentle, refined male voice | Narration, storytelling |
| `Chinese (Mandarin)_Warm_Bestie` | 温暖闺蜜 | Warm bestie female voice | Friendly, supportive content |
| `Chinese (Mandarin)_Male_Announcer` | 播报男声 | Male announcer voice | Announcements, broadcasts |
| `Chinese (Mandarin)_Sweet_Lady` | 甜美女声 | Sweet lady voice | Soft, gentle content |
| `Chinese (Mandarin)_Southern_Young_Man` | 南方小哥 | Southern young man voice | Regional character, casual content |
| `Chinese (Mandarin)_Wise_Women` | 阅历姐姐 | Experienced wise woman voice | Advice, guidance content |
| `Chinese (Mandarin)_Gentle_Youth` | 温润青年 | Gentle young man voice | Narration, calm content |
| `Chinese (Mandarin)_Warm_Girl` | 温暖少女 | Warm young girl voice | Friendly, supportive content |
| `Chinese (Mandarin)_Kind-hearted_Elder` | 花甲奶奶 | Kind elderly lady voice | Stories, warm content |
| `Chinese (Mandarin)_Cute_Spirit` | 憨憨萌兽 | Cute cartoon spirit voice | Animations, children's content |
| `Chinese (Mandarin)_Radio_Host` | 电台男主播 | Radio host male voice | Podcasts, radio shows |
| `Chinese (Mandarin)_Lyrical_Voice` | 抒情男声 | Lyrical male singing voice | Music, singing content |
| `Chinese (Mandarin)_Straightforward_Boy` | 率真弟弟 | Frank, straightforward boy voice | Casual, direct content |
| `Chinese (Mandarin)_Sincere_Adult` | 真诚青年 | Sincere young adult voice | Honest, genuine content |
| `Chinese (Mandarin)_Gentle_Senior` | 温柔学姐 | Gentle senior girl voice | Campus stories, supportive content |
| `Chinese (Mandarin)_Stubborn_Friend` | 嘴硬竹马 | Stubborn childhood friend voice | Drama, character-driven content |
| `Chinese (Mandarin)_Crisp_Girl` | 清脆少女 | Crisp, clear young girl voice | Clear, bright content |
| `Chinese (Mandarin)_Pure-hearted_Boy` | 清澈邻家弟弟 | Pure-hearted neighbor boy voice | Innocent, wholesome content |
| `Chinese (Mandarin)_Soft_Girl` | 柔和少女 | Soft, gentle girl voice | Calm, soothing content |
### Chinese Cantonese Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Cantonese_ProfessionalHostF)` | 专业女主持 | Professional female host voice | Cantonese broadcasts, hosting |
| `Cantonese_GentleLady` | 温柔女声 | Gentle Cantonese female voice | Soft, warm Cantonese content |
| `Cantonese_ProfessionalHostM)` | 专业男主持 | Professional male host voice | Cantonese broadcasts, hosting |
| `Cantonese_PlayfulMan` | 活泼男声 | Playful Cantonese male voice | Entertainment, casual content |
| `Cantonese_CuteGirl` | 可爱女孩 | Cute Cantonese girl voice | Children's content, animations |
| `Cantonese_KindWoman` | 善良女声 | Kind Cantonese female voice | Warm, friendly content |
### English Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Santa_Claus` | Santa Claus | Festive, jolly male voice | Holiday content, children's stories |
| `Grinch` | Grinch | Whiny, mischievous voice | Comedy, entertainment, holiday |
| `Rudolph` | Rudolph | Cute, nasal reindeer voice | Children's content, holiday |
| `Arnold` | Arnold | Deep, robotic terminator voice | Sci-fi, action, character roles |
| `Charming_Santa` | Charming Santa | Smooth, charismatic Santa voice | Holiday, entertainment |
| `Charming_Lady` | Charming Lady | Elegant, sophisticated female voice | Professional, romance |
| `Sweet_Girl` | Sweet Girl | Sweet, innocent young girl voice | Children's content, friendly |
| `Cute_Elf` | Cute Elf | Playful, tiny elf voice | Fantasy, children's content |
| `Attractive_Girl` | Attractive Girl | Attractive, engaging female voice | Entertainment, marketing |
| `Serene_Woman` | Serene Woman | Calm, peaceful female voice | Meditation, relaxation |
| `English_Trustworthy_Man` | Trustworthy Man | Reliable, sincere male voice | Business, narration |
| `English_Graceful_Lady` | Graceful Lady | Elegant, refined female voice | Formal, professional |
| `English_Aussie_Bloke` | Aussie Bloke | Casual, friendly Australian male voice | Casual, entertainment |
| `English_Whispering_girl` | Whispering Girl | Soft, whisper voice | Romance, intimate content |
| `English_Diligent_Man` | Diligent Man | Hardworking, earnest male voice | Motivational, educational |
| `English_Gentle-voiced_man` | Gentle-voiced Man | Soft-spoken, kind male voice | Calm, supportive content |
### Japanese Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Japanese_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Narration, educational |
| `Japanese_DecisivePrincess` | Decisive Princess | Confident, royal princess voice | Animation, games, drama |
| `Japanese_LoyalKnight` | Loyal Knight | Brave, faithful knight voice | Fantasy, games, stories |
| `Japanese_DominantMan` | Dominant Man | Powerful, commanding male voice | Action, leadership |
| `Japanese_SeriousCommander` | Serious Commander | Stern, authoritative commander voice | Military, games |
| `Japanese_ColdQueen` | Cold Queen | Distant, majestic queen voice | Drama, fantasy |
| `Japanese_DependableWoman` | Dependable Woman | Reliable, supportive female voice | Supportive, guidance |
| `Japanese_GentleButler` | Gentle Butler | Polite, refined servant voice | Comedy, animation |
| `Japanese_KindLady` | Kind Lady | Warm, gentle noblewoman voice | Warm, comforting |
| `Japanese_CalmLady` | Calm Lady | Composed, serene female voice | Meditation, relaxation |
| `Japanese_OptimisticYouth` | Optimistic Youth | Cheerful, positive young person voice | Youth content, motivation |
| `Japanese_GenerousIzakayaOwner` | Generous Izakaya Owner | Friendly, welcoming tavern owner voice | Casual, comedy |
| `Japanese_SportyStudent` | Sporty Student | Energetic athletic student voice | Sports, youth content |
| `Japanese_InnocentBoy` | Innocent Boy | Pure, naive young boy voice | Children's content |
| `Japanese_GracefulMaiden` | Graceful Maiden | Elegant, gentle young woman voice | Romance, drama |
### Korean Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Korean_SweetGirl` | Sweet Girl | Sweet, adorable young girl voice | Children's content, romance |
| `Korean_CheerfulBoyfriend` | Cheerful Boyfriend | Energetic, loving boyfriend voice | Romance, dating content |
| `Korean_EnchantingSister` | Enchanting Sister | Charming, captivating sister voice | Family, drama |
| `Korean_ShyGirl` | Shy Girl | Timid, reserved girl voice | Comedy, romance |
| `Korean_ReliableSister` | Reliable Sister | Trustworthy, dependable sister voice | Supportive, guidance |
| `Korean_StrictBoss` | Strict Boss | Authoritative, demanding boss voice | Business, drama |
| `Korean_SassyGirl` | Sassy Girl | Bold, witty girl voice | Comedy, entertainment |
| `Korean_ChildhoodFriendGirl` | Childhood Friend Girl | Familiar, friendly childhood friend voice | Romance, nostalgia |
| `Korean_PlayboyCharmer` | Playboy Charmer | Smooth, flirtatious male voice | Romance, entertainment |
| `Korean_ElegantPrincess` | Elegant Princess | Graceful, royal princess voice | Animation, fantasy |
| `Korean_BraveFemaleWarrior` | Brave Female Warrior | Courageous female warrior voice | Action, fantasy |
| `Korean_BraveYouth` | Brave Youth | Heroic young person voice | Action, youth |
| `Korean_CalmLady` | Calm Lady | Composed, serene female voice | Meditation, relaxation |
| `Korean_EnthusiasticTeen` | EnthusiasticTeen | Excited, energetic teenager voice | Youth content |
| `Korean_SoothingLady` | Soothing Lady | Calming, comforting female voice | Relaxation, support |
| `Korean_IntellectualSenior` | Intellectual Senior | Wise, knowledgeable elder voice | Educational, narration |
| `Korean_LonelyWarrior` | Lonely Warrior | Solitary, melancholic warrior voice | Drama, fantasy |
| `Korean_MatureLady` | MatureLady | Sophisticated, adult female voice | Professional, drama |
| `Korean_InnocentBoy` | Innocent Boy | Pure, naive young boy voice | Children's content |
| `Korean_CharmingSister` | Charming Sister | Attractive, delightful sister voice | Family, romance |
| `Korean_AthleticStudent` | Athletic Student | Sporty, energetic student voice | Sports, youth |
| `Korean_BraveAdventurer` | Brave Adventurer | Courageous explorer voice | Adventure, fantasy |
| `Korean_CalmGentleman` | Calm Gentleman | Composed, refined gentleman voice | Formal, professional |
| `Korean_WiseElf` | Wise Elf | Ancient, mystical elf voice | Fantasy, narration |
| `Korean_CheerfulCoolJunior` | Cheerful Cool Junior | Popular, friendly junior voice | Youth, entertainment |
| `Korean_DecisiveQueen` | Decisive Queen | Authoritative, commanding queen voice | Drama, fantasy |
| `Korean_ColdYoungMan` | Cold Young Man | Distant, aloof young man voice | Drama, romance |
| `Korean_MysteriousGirl` | Mysterious Girl | Enigmatic, secretive girl voice | Mystery, drama |
| `Korean_QuirkyGirl` | Quirky Girl | Eccentric, unique girl voice | Comedy, entertainment |
| `Korean_ConsiderateSenior` | Considerate Senior | Thoughtful, caring elder voice | Warm, supportive |
| `Korean_CheerfulLittleSister` | Cheerful Little Sister | Playful, adorable younger sister voice | Family, comedy |
| `Korean_DominantMan` | Dominant Man | Powerful, commanding male voice | Leadership, action |
| `Korean_AirheadedGirl` | Airheaded Girl | Bubbly, spacey girl voice | Comedy, entertainment |
| `Korean_ReliableYouth` | Reliable Youth | Trustworthy, dependable young person voice | Supportive, youth |
| `Korean_FriendlyBigSister` | Friendly Big Sister | Warm, protective elder sister voice | Family, support |
| `Korean_GentleBoss` | Gentle Boss | Kind, understanding boss voice | Business, supportive |
| `Korean_ColdGirl` | Cold Girl | Aloof, distant girl voice | Drama, romance |
| `Korean_HaughtyLady` | Haughty Lady | Arrogant, proud woman voice | Drama, comedy |
| `Korean_CharmingElderSister` | Charming Elder Sister | Attractive, graceful elder sister voice | Romance, family |
| `Korean_IntellectualMan` | Intellectual Man | Smart, knowledgeable male voice | Educational, professional |
| `Korean_CaringWoman` | Caring Woman | Nurturing, supportive woman voice | Supportive, warm |
| `Korean_WiseTeacher` | Wise Teacher | Experienced, knowledgeable teacher voice | Educational |
| `Korean_ConfidentBoss` | Confident Boss | Self-assured, capable boss voice | Business, leadership |
| `Korean_AthleticGirl` | Athletic Girl | Sporty, energetic girl voice | Sports, fitness |
| `Korean_PossessiveMan` | PossessiveMan | Intense, protective male voice | Romance, drama |
| `Korean_GentleWoman` | Gentle Woman | Soft-spoken, kind woman voice | Calm, supportive |
| `Korean_CockyGuy` | Cocky Guy | Confident, slightly arrogant male voice | Comedy, entertainment |
| `Korean_ThoughtfulWoman` | ThoughtfulWoman | Reflective, caring woman voice | Drama, support |
| `Korean_OptimisticYouth` | Optimistic Youth | Positive, hopeful young person voice | Motivation, youth |
### Spanish Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Spanish_SereneWoman` | Serene Woman | Calm, peaceful female voice | Relaxation, meditation |
| `Spanish_MaturePartner` | Mature Partner | Sophisticated, adult partner voice | Romance, drama |
| `Spanish_CaptivatingStoryteller` | Captivating Storyteller | Engaging, magnetic narrator voice | Audiobooks, storytelling |
| `Spanish_Narrator` | Narrator | Professional narrative voice | Documentaries, narration |
| `Spanish_WiseScholar` | Wise Scholar | Knowledgeable, wise scholar voice | Educational, historical |
| `Spanish_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm |
| `Spanish_DeterminedManager` | Determined Manager | Ambitious, driven manager voice | Business, motivation |
| `Spanish_BossyLeader` | Bossy Leader | Commanding, authoritative leader voice | Leadership, drama |
| `Spanish_ReservedYoungMan` | Reserved Young Man | Quiet, introverted young man voice | Drama, realistic characters |
| `Spanish_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment |
| `Spanish_ThoughtfulMan` | ThoughtfulMan | Reflective, intelligent man voice | Educational, drama |
| `Spanish_Strong-WilledBoy` | Strong-willed Boy | Determined, persistent boy voice | Youth, motivation |
| `Spanish_SophisticatedLady` | SophisticatedLady | Elegant, refined woman voice | Formal, romance |
| `Spanish_RationalMan` | Rational Man | Logical, analytical man voice | Educational, business |
| `Spanish_AnimeCharacter` | Anime Character | Exaggerated anime-style voice | Animation, entertainment |
| `Spanish_Deep-tonedMan` | Deep-toned Man | Deep, resonant male voice | Attractive, commanding |
| `Spanish_Fussyhostess` | Fussy Hostess | Particular, demanding hostess voice | Comedy, drama |
| `Spanish_SincereTeen` | SincereTeen | Honest, genuine teenager voice | Youth, authentic |
| `Spanish_FrankLady` | Frank Lady | Direct, honest woman voice | Comedy, drama |
| `Spanish_Comedian` | Comedian | Humorous, entertaining voice | Comedy, entertainment |
| `Spanish_Debator` | Debator | Argumentative, persuasive voice | Debate, discussion |
| `Spanish_ToughBoss` | Tough Boss | Harsh, demanding boss voice | Business, drama |
| `Spanish_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice |
| `Spanish_Steadymentor` | Steady Mentor | Reliable, supportive mentor voice | Educational, guidance |
| `Spanish_Jovialman` | Jovial Man | Cheerful, friendly man voice | Entertainment, casual |
| `Spanish_SantaClaus` | Santa Claus | Festive Santa voice | Holiday, children |
| `Spanish_Rudolph` | Rudolph | Reindeer voice | Holiday, children |
| `Spanish_Intonategirl` | Intonate Girl | Musical, melodic girl voice | Music, singing |
| `Spanish_Arnold` | Arnold | Robotic, mechanical voice | Sci-fi, action |
| `Spanish_Ghost` | Ghost | Spooky, ethereal voice | Horror, mystery |
| `Spanish_HumorousElder` | Humorous Elder | Funny, elderly person voice | Comedy, entertainment |
| `Spanish_EnergeticBoy` | Energetic Boy | Active, lively boy voice | Youth, sports |
| `Spanish_WhimsicalGirl` | Whimsical Girl | Playful, imaginative girl voice | Children's, fantasy |
| `Spanish_StrictBoss` | Strict Boss | Strict, demanding boss voice | Business, education |
| `Spanish_ReliableMan` | Reliable Man | Trustworthy, dependable man voice | Professional, support |
| `Spanish_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom |
| `Spanish_AngryMan` | Angry Man | Frustrated, irritated male voice | Drama, comedy |
| `Spanish_AssertiveQueen` | Assertive Queen | Confident, commanding queen voice | Drama, fantasy |
| `Spanish_CaringGirlfriend` | Caring Girlfriend | Nurturing, loving girlfriend voice | Romance, relationship |
| `Spanish_PowerfulSoldier` | Powerful Soldier | Strong, brave soldier voice | Action, military |
| `Spanish_PassionateWarrior` | Passionate Warrior | Fierce, dedicated warrior voice | Action, fantasy |
| `Spanish_ChattyGirl` | Chatty Girl | Talkative, sociable girl voice | Comedy, social |
| `Spanish_RomanticHusband` | Romantic Husband | Loving, romantic husband voice | Romance, family |
| `Spanish_CompellingGirl` | CompellingGirl | Persuasive, magnetic girl voice | Marketing, entertainment |
| `Spanish_PowerfulVeteran` | Powerful Veteran | Experienced, strong veteran voice | Military, drama |
| `Spanish_SensibleManager` | Sensible Manager | Practical, reasonable manager voice | Business, guidance |
| `Spanish_ThoughtfulLady` | Thoughtful Lady | Considerate, kind lady voice | Supportive, advice |
### Portuguese Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Portuguese_SentimentalLady` | Sentimental Lady | Emotional, sensitive lady voice | Drama, romance |
| `Portuguese_BossyLeader` | Bossy Leader | Commanding, authoritative leader voice | Leadership, drama |
| `Portuguese_Wiselady` | Wise Lady | Experienced, wise woman voice | Guidance, advice |
| `Portuguese_Strong-WilledBoy` | Strong-willed Boy | Determined, persistent boy voice | Youth, motivation |
| `Portuguese_Deep-VoicedGentleman` | Deep-voiced Gentleman | Deep, rich male voice | Attractive, commanding |
| `Portuguese_UpsetGirl` | Upset Girl | Distressed, emotional girl voice | Drama, realistic |
| `Portuguese_PassionateWarrior` | Passionate Warrior | Fierce, dedicated warrior voice | Action, fantasy |
| `Portuguese_AnimeCharacter` | Anime Character | Exaggerated anime-style voice | Animation, entertainment |
| `Portuguese_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment |
| `Portuguese_AngryMan` | Angry Man | Frustrated, irritated male voice | Drama, comedy |
| `Portuguese_CaptivatingStoryteller` | Captivating Storyteller | Engaging, magnetic narrator voice | Audiobooks, storytelling |
| `Portuguese_Godfather` | Godfather | Authoritative, powerful father figure voice | Drama, powerful |
| `Portuguese_ReservedYoungMan` | Reserved Young Man | Quiet, introverted young man voice | Drama, realistic |
| `Portuguese_SmartYoungGirl` | Smart Young Girl | Intelligent, clever girl voice | Educational, youth |
| `Portuguese_Kind-heartedGirl` | Kind-hearted Girl | Warm, compassionate girl voice | Children's content, warm |
| `Portuguese_Pompouslady` | Pompous Lady | Self-important, arrogant lady voice | Comedy, drama |
| `Portuguese_Grinch` | Grinch | Whiny, mischievous voice | Comedy, entertainment |
| `Portuguese_Debator` | Debator | Argumentative, persuasive voice | Debate, discussion |
| `Portuguese_SweetGirl` | Sweet Girl | Sweet, adorable girl voice | Children's content, romance |
| `Portuguese_AttractiveGirl` | Attractive Girl | Charming, appealing girl voice | Entertainment, romance |
| `Portuguese_ThoughtfulMan` | Thoughtful Man | Reflective, intelligent man voice | Educational, drama |
| `Portuguese_PlayfulGirl` | Playful Girl | Playful, fun-loving girl voice | Comedy, children's content |
| `Portuguese_GorgeousLady` | Gorgeous Lady | Beautiful, stunning lady voice | Romance, entertainment |
| `Portuguese_LovelyLady` | Lovely Lady | Sweet, endearing lady voice | Warm, friendly |
| `Portuguese_SereneWoman` | Serene Woman | Calm, peaceful female voice | Relaxation, meditation |
| `Portuguese_SadTeen` | Sad Teen | Melancholic, teenage voice | Drama, emotional |
| `Portuguese_MaturePartner` | Mature Partner | Sophisticated, adult partner voice | Romance, drama |
| `Portuguese_Comedian` | Comedian | Humorous, entertaining voice | Comedy, entertainment |
| `Portuguese_NaughtySchoolgirl` | Naughty Schoolgirl | Mischievous, playful student voice | Comedy, school |
| `Portuguese_Narrator` | Narrator | Professional narrative voice | Documentaries, narration |
| `Portuguese_ToughBoss` | Tough Boss | Harsh, demanding boss voice | Business, drama |
| `Portuguese_Fussyhostess` | Fussy Hostess | Particular, demanding hostess voice | Comedy, drama |
| `Portuguese_Dramatist` | Dramatist | Theatrical, expressive voice | Drama, storytelling |
| `Portuguese_Steadymentor` | Steady Mentor | Reliable, supportive mentor voice | Educational, guidance |
| `Portuguese_Jovialman` | Jovial Man | Cheerful, friendly man voice | Entertainment, casual |
| `Portuguese_CharmingQueen` | Charming Queen | Elegant, captivating queen voice | Drama, fantasy |
| `Portuguese_SantaClaus` | Santa Claus | Festive Santa voice | Holiday, children |
| `Portuguese_Rudolph` | Rudolph | Reindeer voice | Holiday, children |
| `Portuguese_Arnold` | Arnold | Robotic, mechanical voice | Sci-fi, action |
| `Portuguese_CharmingSanta` | Charming Santa | Smooth, charismatic Santa voice | Holiday, entertainment |
| `Portuguese_CharmingLady` | Charming Lady | Elegant, sophisticated lady voice | Professional, romance |
| `Portuguese_Ghost` | Ghost | Spooky, ethereal voice | Horror, mystery |
| `Portuguese_HumorousElder` | Humorous Elder | Funny, elderly person voice | Comedy, entertainment |
| `Portuguese_CalmLeader` | Calm Leader | Composed, steady leader voice | Leadership, guidance |
| `Portuguese_GentleTeacher` | Gentle Teacher | Kind, patient teacher voice | Educational, supportive |
| `Portuguese_EnergeticBoy` | Energetic Boy | Active, lively boy voice | Youth, sports |
| `Portuguese_ReliableMan` | Reliable Man | Trustworthy, dependable man voice | Professional, support |
| `Portuguese_SereneElder` | Serene Elder | Calm, peaceful elderly voice | Meditation, wisdom |
| `Portuguese_GrimReaper` | Grim Reaper | Dark, ominous voice | Horror, fantasy |
| `Portuguese_AssertiveQueen` | Assertive Queen | Confident, commanding queen voice | Drama, fantasy |
| `Portuguese_WhimsicalGirl` | Whimsical Girl | Playful, imaginative girl voice | Children's, fantasy |
| `Portuguese_StressedLady` | Stressed Lady | Anxious, overwhelmed lady voice | Comedy, realistic |
| `Portuguese_FriendlyNeighbor` | Friendly Neighbor | Warm, helpful neighbor voice | Community, family |
| `Portuguese_CaringGirlfriend` | Caring Girlfriend | Nurturing, loving girlfriend voice | Romance, relationship |
| `Portuguese_PowerfulSoldier` | Powerful Soldier | Strong, brave soldier voice | Action, military |
| `Portuguese_FascinatingBoy` | Fascinating Boy | Charming, intriguing boy voice | Romance, youth |
| `Portuguese_RomanticHusband` | Romantic Husband | Loving, romantic husband voice | Romance, family |
| `Portuguese_StrictBoss` | Strict Boss | Strict, demanding boss voice | Business, education |
| `Portuguese_InspiringLady` | Inspiring Lady | Motivating, encouraging lady voice | Motivation, leadership |
| `Portuguese_PlayfulSpirit` | Playful Spirit | Cheerful, mischievous spirit voice | Fantasy, children's |
| `Portuguese_ElegantGirl` | Elegant Girl | Graceful, refined girl voice | Formal, romance |
| `Portuguese_CompellingGirl` | Compelling Girl | Persuasive, magnetic girl voice | Marketing, entertainment |
| `Portuguese_PowerfulVeteran` | Powerful Veteran | Experienced, strong veteran voice | Military, drama |
| `Portuguese_SensibleManager` | Sensible Manager | Practical, reasonable manager voice | Business, guidance |
| `Portuguese_ThoughtfulLady` | Thoughtful Lady | Considerate, kind lady voice | Supportive, advice |
| `Portuguese_TheatricalActor` | Theatrical Actor | Dramatic, expressive actor voice | Drama, entertainment |
| `Portuguese_FragileBoy` | Fragile Boy | Sensitive, vulnerable boy voice | Drama, emotional |
| `Portuguese_ChattyGirl` | Chatty Girl | Talkative, sociable girl voice | Comedy, social |
| `Portuguese_Conscientiousinstructor` | Conscientious Instructor | Careful, diligent instructor voice | Educational, training |
| `Portuguese_RationalMan` | Rational Man | Logical, analytical man voice | Educational, business |
| `Portuguese_WiseScholar` | Wise Scholar | Knowledgeable, wise scholar voice | Educational, historical |
| `Portuguese_FrankLady` | Frank Lady | Direct, honest woman voice | Comedy, drama |
| `Portuguese_DeterminedManager` | Determined Manager | Ambitious, driven manager voice | Business, motivation |
### French Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `French_Male_Speech_New` | Level-Headed Man | Calm, reasonable male voice | Professional, narration |
| `French_Female_News Anchor` | Patient Female Presenter | Clear, patient news presenter voice | News, broadcasts |
| `French_CasualMan` | Casual Man | Relaxed, informal male voice | Casual, entertainment |
| `French_MovieLeadFemale` | Movie Lead Female | Dramatic, expressive female voice | Drama, entertainment |
| `French_FemaleAnchor` | Female Anchor | Professional female anchor voice | News, broadcasts |
### Indonesian Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Indonesian_SweetGirl` | Sweet Girl | Sweet, adorable girl voice | Children's content, friendly |
| `Indonesian_ReservedYoungMan` | Reserved Young Man | Quiet, introverted young man voice | Drama, realistic |
| `Indonesian_CharmingGirl` | Charming Girl | Attractive, appealing girl voice | Entertainment, romance |
| `Indonesian_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation |
| `Indonesian_ConfidentWoman` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment |
| `Indonesian_CaringMan` | Caring Man | Nurturing, supportive man voice | Supportive, family |
| `Indonesian_BossyLeader` | Bossy Leader | Commanding, authoritative leader voice | Leadership, drama |
| `Indonesian_DeterminedBoy` | Determined Boy | Ambitious, persistent boy voice | Youth, motivation |
| `Indonesian_GentleGirl` | Gentle Girl | Soft-spoken, kind girl voice | Calm, supportive |
### German Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `German_FriendlyMan` | Friendly Man | Warm, approachable male voice | Casual, friendly |
| `German_SweetLady` | Sweet Lady | Pleasant, kind lady voice | Warm, supportive |
| `German_PlayfulMan` | Playful Man | Fun-loving, humorous male voice | Comedy, entertainment |
### Russian Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Russian_HandsomeChildhoodFriend` | Handsome Childhood Friend | Charming childhood friend voice | Romance, nostalgia |
| `Russian_BrightHeroine` | Bright Queen | Lively, strong female lead voice | Drama, action |
| `Russian_AmbitiousWoman` | Ambitious Woman | Driven, determined woman voice | Professional, motivation |
| `Russian_ReliableMan` | Reliable Man | Trustworthy, dependable man voice | Professional, support |
| `Russian_CrazyQueen` | Crazy Girl | Wild, unpredictable female voice | Comedy, drama |
| `Russian_PessimisticGirl` | Pessimistic Girl | Gloomy, negative girl voice | Comedy, drama |
| `Russian_AttractiveGuy` | Attractive Guy | Charming, appealing male voice | Romance, entertainment |
| `Russian_Bad-temperedBoy` | Bad-tempered Boy | Irritable, grumpy boy voice | Comedy, drama |
### Italian Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Italian_BraveHeroine` | Brave Heroine | Courageous, heroic female voice | Action, fantasy |
| `Italian_Narrator` | Narrator | Professional narrative voice | Documentaries, storytelling |
| `Italian_WanderingSorcerer` | Wandering Sorcerer | Mysterious, traveling magician voice | Fantasy, adventure |
| `Italian_DiligentLeader` | Diligent Leader | Hardworking, dedicated leader voice | Leadership, business |
### Arabic Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Arabic_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation |
| `Arabic_FriendlyGuy` | Friendly Guy | Warm, approachable male voice | Casual, friendly |
### Turkish Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Turkish_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation |
| `Turkish_Trustworthyman` | Trustworthy Man | Reliable, sincere male voice | Professional, business |
### Ukrainian Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Ukrainian_CalmWoman` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation |
| `Ukrainian_WiseScholar` | Wise Scholar | Knowledgeable, wise scholar voice | Educational, historical |
### Dutch Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Dutch_kindhearted_girl` | Kind-hearted girl | Warm, compassionate girl voice | Children's content, warm |
| `Dutch_bossy_leader` | Bossy leader | Commanding, authoritative leader voice | Leadership, drama |
### Vietnamese Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Vietnamese_kindhearted_girl` | Kind-hearted girl | Warm, compassionate girl voice | Children's content, warm |
### Thai Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Thai_male_1_sample8` | Serene Man | Calm, peaceful male voice | Relaxation, meditation |
| `Thai_male_2_sample2` | Friendly Man | Warm, approachable male voice | Casual, friendly |
| `Thai_female_1_sample1` | Confident Woman | Self-assured, capable woman voice | Professional, empowerment |
| `Thai_female_2_sample2` | Energetic Woman | Active, lively female voice | Motivation, energy |
### Polish Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Polish_male_1_sample4` | Male Narrator | Professional narrative voice | Documentaries, narration |
| `Polish_male_2_sample3` | Male Anchor | Professional male anchor voice | News, broadcasts |
| `Polish_female_1_sample1` | Calm Woman | Composed, peaceful female voice | Relaxation, meditation |
| `Polish_female_2_sample3` | Casual Woman | Relaxed, informal female voice | Casual, entertainment |
### Romanian Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `Romanian_male_1_sample2` | Reliable Man | Trustworthy, dependable man voice | Professional, support |
| `Romanian_male_2_sample1` | Energetic Youth | Active, lively young person voice | Youth, motivation |
| `Romanian_female_1_sample4` | Optimistic Youth | Positive, hopeful young person voice | Motivation, youth |
| `Romanian_female_2_sample1` | Gentle Woman | Soft-spoken, kind woman voice | Calm, supportive |
### Greek Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `greek_male_1a_v1` | Thoughtful Mentor | Reflective, wise mentor voice | Educational, guidance |
| `Greek_female_1_sample1` | Gentle Lady | Soft-spoken, kind lady voice | Calm, supportive |
| `Greek_female_2_sample3` | Girl Next Door | Friendly, approachable girl voice | Casual, friendly |
### Czech Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `czech_male_1_v1` | Assured Presenter | Confident, professional presenter voice | Presentations, broadcasts |
| `czech_female_5_v7` | Steadfast Narrator | Reliable, consistent narrator voice | Documentaries, storytelling |
| `czech_female_2_v2` | Elegant Lady | Graceful, refined lady voice | Formal, professional |
### Finnish Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `finnish_male_3_v1` | Upbeat Man | Cheerful, energetic male voice | Motivation, entertainment |
| `finnish_male_1_v2` | Friendly Boy | Warm, approachable boy voice | Children's content, friendly |
| `finnish_female_4_v1` | Assertive Woman | Confident, strong female voice | Professional, empowerment |
### Hindi Voices
| voice_id | Name | Description | Best For |
|----------|------|-------------|----------|
| `hindi_male_1_v2` | Trustworthy Advisor | Reliable, wise advisor voice | Guidance, advice |
| `hindi_female_2_v1` | Tranquil Woman | Calm, peaceful female voice | Relaxation, meditation |
| `hindi_female_1_v2` | News Anchor | Professional news anchor voice | News, broadcasts |
---
## Voice Parameters
### VoiceSetting Dataclass
```python
from utils import VoiceSetting
voice = VoiceSetting(
voice_id="male-qn-qingse", # Required: Voice ID
speed=1.0, # Optional: 0.5 (slower) to 2.0 (faster), default 1.0
volume=1.0, # Optional: 0.1 (quieter) to 10.0 (louder), default 1.0
pitch=0, # Optional: -12 (deeper) to 12 (higher), default 0
emotion="calm", # Optional: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper
)
```
### Parameter Guidelines
**Speed**
- 0.75: Slower, deliberate speech (news, tutorials)
- 1.0: Normal pace (most content)
- 1.25: Slightly faster (energetic content)
- 1.5+: Fast pace (time-sensitive content)
**Volume**
- 0.8-1.0: Normal listening levels
- 1.0-1.5: Louder for attention-grabbing content
- < 0.8: Softer, intimate feeling
**Pitch**
- -6 to -3: Deeper, more authoritative
- 0: Natural pitch
- +3 to +6: Higher, more energetic
**Emotion**
- `calm`: Calm, neutral tone
- `fluent`: Fluent, natural tone
- `whisper`: Whisper, soft, gentle tone
- `happy`: Cheerful, upbeat tone
- `sad`: Melancholic, somber tone
- `angry`: Frustrated, intense tone
- `fearful`: Anxious, nervous tone
- `disgusted`: Repulsed, revolted tone
- `surprised`: Astonished, amazed tone
## Custom Voices
### Voice Cloning
Create custom voices from audio samples for unique brand voices.
**Requirements:**
- Source audio: 10 seconds to 5 minutes
- Format: mp3, wav, m4a
- Size: Max 20MB
- Quality: Clear, no background noise, single speaker
**Best Practices:**
- Use 30-60 seconds of clean speech
- Include varied intonation and emotion
- Record in quiet environment
- Consistent volume throughout
### Voice Design
Generate new voices through text descriptions for creative projects.
**When to Use:**
- No existing voice matches your needs
- Need unique character voices
- Prototype before full voice cloning
**Prompt Guidelines:**
- Include: gender, age, vocal characteristics, emotional tone, use case
- Be specific about pacing, tone, and intended audience
- Example: "A warm, grandmotherly voice with gentle pacing, perfect for bedtime stories"

View File

@@ -0,0 +1,407 @@
# Motion Recipes
Production-ready animation code snippets. Copy and adapt as needed.
## 1. Scroll-Triggered Reveal (Framer Motion)
Elements fade and slide up when entering viewport.
```tsx
"use client";
import { motion } from "framer-motion";
const fadeSlideUp = {
hidden: { opacity: 0, y: 40 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 100, damping: 20 },
},
};
export function RevealSection({ children }: { children: React.ReactNode }) {
return (
<motion.div
variants={fadeSlideUp}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-80px" }}
>
{children}
</motion.div>
);
}
```
## 2. Staggered List Orchestration (Framer Motion)
Children animate sequentially with blur effect.
```tsx
"use client";
import { motion } from "framer-motion";
const container = {
hidden: {},
visible: { transition: { staggerChildren: 0.08, delayChildren: 0.1 } },
};
const item = {
hidden: { opacity: 0, y: 24, filter: "blur(4px)" },
visible: {
opacity: 1,
y: 0,
filter: "blur(0px)",
transition: { type: "spring", stiffness: 120, damping: 20 },
},
};
export function StaggerGrid({ items }: { items: React.ReactNode[] }) {
return (
<motion.div
className="grid gap-6"
variants={container}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
>
{items.map((child, i) => (
<motion.div key={i} variants={item}>
{child}
</motion.div>
))}
</motion.div>
);
}
```
## 3. GSAP ScrollTrigger Pinned Section
Horizontal scroll panels with pinning.
```tsx
"use client";
import { useRef, useEffect } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
export function PinnedTimeline() {
const containerRef = useRef<HTMLDivElement>(null);
const panelsRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const ctx = gsap.context(() => {
const panels = gsap.utils.toArray<HTMLElement>(".panel");
gsap.to(panels, {
xPercent: -100 * (panels.length - 1),
ease: "none",
scrollTrigger: {
trigger: containerRef.current,
pin: true,
scrub: 1,
end: () => "+=" + (panelsRef.current?.scrollWidth ?? 0),
},
});
}, containerRef);
return () => ctx.revert(); // CRITICAL: full cleanup
}, []);
return (
<div ref={containerRef} className="overflow-hidden">
<div ref={panelsRef} className="flex">
{/* .panel elements */}
</div>
</div>
);
}
```
## 4. Parallax Tilt Card (Framer Motion)
Mouse-tracking 3D perspective. Uses `useMotionValue` — never `useState`.
```tsx
"use client";
import { motion, useMotionValue, useTransform } from "framer-motion";
export function TiltCard({ children }: { children: React.ReactNode }) {
const x = useMotionValue(0.5);
const y = useMotionValue(0.5);
const rotateX = useTransform(y, [0, 1], [8, -8]);
const rotateY = useTransform(x, [0, 1], [-8, 8]);
return (
<motion.div
style={{ rotateX, rotateY, transformPerspective: 800 }}
onMouseMove={(e) => {
const rect = e.currentTarget.getBoundingClientRect();
x.set((e.clientX - rect.left) / rect.width);
y.set((e.clientY - rect.top) / rect.height);
}}
onMouseLeave={() => {
x.set(0.5);
y.set(0.5);
}}
className="rounded-2xl bg-white shadow-lg"
>
{children}
</motion.div>
);
}
```
## 5. Magnetic Button (Framer Motion)
Cursor-attracted button. Pure `useMotionValue` — zero re-renders.
```tsx
"use client";
import { motion, useMotionValue, useSpring } from "framer-motion";
import { useRef } from "react";
export function MagneticButton({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLButtonElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springX = useSpring(x, { stiffness: 200, damping: 15 });
const springY = useSpring(y, { stiffness: 200, damping: 15 });
return (
<motion.button
ref={ref}
style={{ x: springX, y: springY }}
onMouseMove={(e) => {
const rect = ref.current!.getBoundingClientRect();
const dx = e.clientX - (rect.left + rect.width / 2);
const dy = e.clientY - (rect.top + rect.height / 2);
x.set(dx * 0.3);
y.set(dy * 0.3);
}}
onMouseLeave={() => {
x.set(0);
y.set(0);
}}
>
{children}
</motion.button>
);
}
```
## 6. Text Scramble / Decode Effect
Matrix-style character reveal — pure JS, no library needed.
```tsx
"use client";
import { useEffect, useRef, useState } from "react";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
export function TextScramble({ text, className }: { text: string; className?: string }) {
const [display, setDisplay] = useState(text);
const iteration = useRef(0);
useEffect(() => {
iteration.current = 0;
const id = setInterval(() => {
setDisplay(
text
.split("")
.map((char, i) =>
i < iteration.current ? char : chars[Math.floor(Math.random() * chars.length)]
)
.join("")
);
iteration.current += 1 / 3;
if (iteration.current >= text.length) clearInterval(id);
}, 30);
return () => clearInterval(id);
}, [text]);
return <span className={className}>{display}</span>;
}
```
## 7. SVG Path Draw on Scroll (CSS Scroll-Driven)
Zero-JS scroll-linked path drawing using native CSS.
```css
@supports (animation-timeline: scroll()) {
.draw-path {
stroke-dasharray: 1;
stroke-dashoffset: 1;
animation: draw linear;
animation-timeline: scroll();
animation-range: entry 0% cover 60%;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
}
```
## 8. Horizontal Scroll Hijack (GSAP)
Vertical scroll drives horizontal panning.
```tsx
"use client";
import { useRef, useEffect } from "react";
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
export function HorizontalScroll({ children }: { children: React.ReactNode }) {
const sectionRef = useRef<HTMLDivElement>(null);
const trackRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const ctx = gsap.context(() => {
const track = trackRef.current!;
const scrollWidth = track.scrollWidth - window.innerWidth;
gsap.to(track, {
x: -scrollWidth,
ease: "none",
scrollTrigger: {
trigger: sectionRef.current,
pin: true,
scrub: 0.8,
end: () => `+=${scrollWidth}`,
},
});
}, sectionRef);
return () => ctx.revert();
}, []);
return (
<section ref={sectionRef} className="overflow-hidden">
<div ref={trackRef} className="flex gap-8 w-max">
{children}
</div>
</section>
);
}
```
## 9. Particle Background (React Three Fiber)
Isolated canvas layer. Purely decorative, pointer-events-none.
```tsx
"use client";
import { Canvas, useFrame } from "@react-three/fiber";
import { useRef, useMemo } from "react";
import * as THREE from "three";
function Particles({ count = 800 }) {
const mesh = useRef<THREE.Points>(null);
const positions = useMemo(() => {
const arr = new Float32Array(count * 3);
for (let i = 0; i < count * 3; i++) arr[i] = (Math.random() - 0.5) * 10;
return arr;
}, [count]);
useFrame(({ clock }) => {
if (mesh.current) mesh.current.rotation.y = clock.getElapsedTime() * 0.05;
});
return (
<points ref={mesh}>
<bufferGeometry>
<bufferAttribute attach="attributes-position" args={[positions, 3]} />
</bufferGeometry>
<pointsMaterial size={0.015} color="#94a3b8" transparent opacity={0.6} />
</points>
);
}
export function ParticleCanvas() {
return (
<div className="fixed inset-0 -z-10 pointer-events-none">
<Canvas camera={{ position: [0, 0, 5], fov: 60 }}>
<Particles />
</Canvas>
</div>
);
}
```
## 10. Shared Layout Morph (Framer Motion)
Card-to-modal expansion using `layoutId`.
```tsx
"use client";
import { motion, AnimatePresence } from "framer-motion";
import { useState } from "react";
export function MorphCard({ id, preview, detail }: {
id: string;
preview: React.ReactNode;
detail: React.ReactNode;
}) {
const [open, setOpen] = useState(false);
return (
<>
<motion.div layoutId={`card-${id}`} onClick={() => setOpen(true)}
className="cursor-pointer rounded-2xl bg-white p-6 shadow-md">
{preview}
</motion.div>
<AnimatePresence>
{open && (
<>
<motion.div
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/40 z-40"
onClick={() => setOpen(false)}
/>
<motion.div layoutId={`card-${id}`}
className="fixed inset-4 md:inset-20 z-50 rounded-2xl bg-white p-8 shadow-2xl overflow-auto">
{detail}
</motion.div>
</>
)}
</AnimatePresence>
</>
);
}
```
## Scroll Animation Patterns
### Sticky Scroll Stack
Cards pin to top and stack over each other.
- Each card: `position: sticky; top: calc(var(--index) * 2rem)`
- Depth illusion: `scale(calc(1 - var(--index) * 0.03))`
### Split-Screen Parallax
Two viewport halves scroll at different speeds.
- Left: `translateY` at 0.5x scroll speed (GSAP `scrub`)
- Mobile: collapse to single column, disable parallax
### Zoom Parallax
Hero image scales 1 to 1.5 on scroll.
```tsx
scrollTrigger: { trigger: heroRef, start: "top top", end: "bottom top", scrub: true }
gsap.to(imageRef, { scale: 1.5, ease: "none" });
```
### Text Mask Reveal
Large typography as window into video/image background.
- `background-clip: text` + `color: transparent`
- Animate `background-position` on scroll
### Curtain Reveal
Hero splits in half, each side slides away on scroll.
- Two halves clipped with `clip-path: inset(0 50% 0 0)` and `inset(0 0 0 50%)`
- GSAP animates `xPercent: -100` and `xPercent: 100`

View File

@@ -0,0 +1,203 @@
# Motion System Guidelines
Animation and transition specifications for Material Design 3.
## Motion Principles
### Four Core Characteristics
| Principle | Description |
|-----------|-------------|
| **Responsive** | Quickly responds to user input at the point of interaction |
| **Natural** | Follows real-world physics (gravity, friction, momentum) |
| **Aware** | Elements are aware of surroundings and other elements |
| **Intentional** | Guides focus to the right place at the right time |
## Duration Guidelines
### By Interaction Type
| Type | Duration | Usage |
|------|----------|-------|
| Micro | 50-100ms | Ripples, state changes, hover |
| Short | 100-200ms | Simple transitions, toggles |
| Medium | 200-300ms | Expanding, collapsing, revealing |
| Long | 300-500ms | Complex choreography, page transitions |
### By Device Type
| Device | Typical Duration | Adjustment |
|--------|------------------|------------|
| Mobile | 300ms | Baseline |
| Tablet | 390ms | +30% slower |
| Desktop | 150-200ms | Faster, more responsive |
| Wearable | 210ms | -30% faster |
### Duration Rules
- **Maximum**: Keep under 400ms for most transitions
- **User-initiated**: Faster (closer to instant feedback)
- **System-initiated**: Can be slightly longer
- **Loading states**: Use indeterminate indicators for unknown duration
## Easing Curves
### Standard Curves
| Curve | Usage | Characteristics |
|-------|-------|-----------------|
| **Standard** | Most common transitions | Quick acceleration, slow deceleration |
| **Emphasized** | Important/significant transitions | More dramatic curve |
| **Decelerate** | Elements entering screen | Starts fast, ends slow |
| **Accelerate** | Elements leaving screen permanently | Starts slow, ends fast |
| **Sharp** | Elements temporarily leaving | Quick, snappy motion |
### Curve Values (Cubic Bezier)
| Curve | Value |
|-------|-------|
| Standard | cubic-bezier(0.2, 0.0, 0.0, 1.0) |
| Emphasized | cubic-bezier(0.2, 0.0, 0.0, 1.0) |
| Decelerate | cubic-bezier(0.0, 0.0, 0.0, 1.0) |
| Accelerate | cubic-bezier(0.3, 0.0, 1.0, 1.0) |
## Movement Patterns
### Arc Motion
- Use natural, concave arcs for diagonal movement
- Single-axis movement (horizontal/vertical only) stays straight
- Elements entering/exiting screen move on single axis
### Choreography
- **Stagger**: Offset timing for related elements (20-40ms between)
- **Cascade**: Sequential reveal from a focal point
- **Shared motion**: Elements that move together maintain relationship
## Transition Patterns
### Container Transform
Best for: Navigation from card/list item to detail screen
- Origin container morphs into destination
- Maintains visual continuity
- Content fades during transformation
### Shared Axis
Best for: Same-level navigation (tabs, stepper)
| Axis | Direction | Usage |
|------|-----------|-------|
| X-axis | Horizontal | Tabs, horizontal paging |
| Y-axis | Vertical | Vertical lists, feeds |
| Z-axis | Depth | Parent-child relationships |
### Fade Through
Best for: Unrelated screen transitions
- Outgoing content fades out
- Incoming content fades in
- Brief overlap period
- No shared elements
### Fade
Best for: Show/hide single elements
- Simple opacity change
- Optionally combine with scale
- Quick duration (100-200ms)
## Component-Specific Motion
### FAB
| State | Animation |
|-------|-----------|
| Appear | Scale up + fade in |
| Disappear | Scale down + fade out |
| Transform | Morph to extended FAB |
| Press | Elevation change (3dp → 8dp) |
### Bottom Sheet
| State | Animation |
|-------|-----------|
| Expand | Slide up with decelerate curve |
| Collapse | Slide down with accelerate curve |
| Dismiss | Swipe down with velocity-based duration |
### Navigation
| Pattern | Animation |
|---------|-----------|
| Push | Incoming slides from right, outgoing shifts left |
| Pop | Incoming slides from left, outgoing shifts right |
| Modal | Slide up from bottom |
### Cards
| State | Animation |
|-------|-----------|
| Expand | Container transform to detail |
| Press | Subtle elevation increase |
| Reorder | Follow finger with physics |
## Loading & Progress
### Indeterminate Indicators
- Use for unknown duration
- Continuous, looping animation
- M3 Expressive: Customizable waveform and thickness
### Determinate Indicators
- Use when progress is measurable
- Smooth, linear progression
- Update frequently for responsiveness
### Skeleton Screens
- Show layout structure immediately
- Subtle shimmer animation
- Replace with content as it loads
## Accessibility Considerations
### Reduced Motion
- Respect prefers-reduced-motion setting
- Provide alternatives:
- Instant transitions (no animation)
- Simple fade instead of complex motion
- Static loading indicators
### Motion Duration
- Keep essential feedback < 100ms
- Avoid motion that could trigger vestibular issues
- Test with motion sensitivity settings enabled
## Implementation Notes
### Android Animation APIs
| API | Usage |
|-----|-------|
| MotionLayout | Complex, coordinated animations |
| Transition | Activity/Fragment transitions |
| Animator | Property animations |
| AnimatedContent | Compose content transitions |
| animateContentSize | Compose size changes |
### Performance Tips
- Use hardware layers for complex animations
- Avoid animating layout properties (use transform)
- Profile with GPU rendering tools
- Target 60 FPS (16ms per frame)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
# MiniMax Music Generation API (music-2.5)
Source: https://platform.minimaxi.com/docs/api-reference/music-generation
## Endpoint
`POST https://api.minimaxi.com/v1/music_generation`
## Auth
`Authorization: Bearer <MINIMAX_API_KEY>`
## Request (JSON)
Required:
- `model`: string — `music-2.5`
- `lyrics`: string (13500 chars) — required. Use `\n` for line breaks. Structure tags: `[Verse]`, `[Chorus]`, `[Bridge]`, `[Intro]`, `[Outro]`, etc.
Optional:
- `prompt`: string (02000 chars) — style description, optional but recommended.
- `lyrics_optimizer`: boolean — auto-generate lyrics from prompt when lyrics is empty.
- `stream`: boolean (default `false`)
- `output_format`: `hex` (default) or `url`. URL valid for 24 hours.
- `aigc_watermark`: boolean — top-level field, non-streaming only.
- `audio_setting`:
- `sample_rate`: 16000, 24000, 32000, 44100
- `bitrate`: 32000, 64000, 128000, 256000
- `format`: mp3, wav, pcm
## Example
```json
{
"model": "music-2.5",
"prompt": "indie folk, melancholic, introspective",
"lyrics": "[verse]\n...\n[chorus]\n...",
"aigc_watermark": false,
"audio_setting": {
"sample_rate": 44100,
"bitrate": 256000,
"format": "mp3"
}
}
```
## Response
- `data.audio`: hex string or URL depending on `output_format`
- `data.status`: 1 (generating), 2 (complete)
- `extra_info`: duration, sample_rate, channels, bitrate, size
- `base_resp.status_code`: 0 on success
## Notes
- `music-2.5` does not support `is_instrumental`. For instrumental music, use lyrics `[intro] [outro]` and add `pure music, no lyrics` to the prompt.
- `prompt` is optional but recommended for better style control.
- `stream=true` only supports `hex` output.

View File

@@ -0,0 +1,175 @@
# Navigation Patterns
iOS navigation patterns guide covering Tab navigation, Navigation Controller, and modal presentation.
## Tab-Based Navigation
For apps with 3-5 main sections:
```swift
class AppTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
let homeNav = UINavigationController(rootViewController: HomeVC())
homeNav.tabBarItem = UITabBarItem(
title: "Home",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
let searchNav = UINavigationController(rootViewController: SearchVC())
searchNav.tabBarItem = UITabBarItem(
title: "Search",
image: UIImage(systemName: "magnifyingglass"),
tag: 1
)
let profileNav = UINavigationController(rootViewController: ProfileVC())
profileNav.tabBarItem = UITabBarItem(
title: "Profile",
image: UIImage(systemName: "person"),
selectedImage: UIImage(systemName: "person.fill")
)
viewControllers = [homeNav, searchNav, profileNav]
}
}
```
### Tab Bar Best Practices
| Principle | Description |
|-----------|-------------|
| Limit count | Maximum 5 tabs, use More for additional |
| Always visible | Tab bar stays visible at all navigation levels |
| State preservation | Preserve navigation state when switching tabs |
| Icon choice | Use SF Symbols, provide selected/unselected states |
## Navigation Controller
Use large titles for root views:
```swift
class ListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Items"
navigationController?.navigationBar.prefersLargeTitles = true
navigationItem.largeTitleDisplayMode = .always
}
func pushDetail(_ item: Item) {
let detail = DetailViewController(item: item)
detail.navigationItem.largeTitleDisplayMode = .never
navigationController?.pushViewController(detail, animated: true)
}
}
```
### Navigation Bar Configuration
```swift
class CustomNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
let appearance = UINavigationBarAppearance()
appearance.configureWithDefaultBackground()
navigationBar.standardAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
navigationBar.compactAppearance = appearance
}
}
```
### Navigation Bar Buttons
```swift
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.rightBarButtonItem = UIBarButtonItem(
image: UIImage(systemName: "plus"),
style: .plain,
target: self,
action: #selector(addItem)
)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(systemItem: .add, primaryAction: UIAction { _ in }),
UIBarButtonItem(systemItem: .edit, primaryAction: UIAction { _ in })
]
}
```
## Modal Presentation
### Sheet Presentation
```swift
func presentEditor() {
let editorVC = EditorViewController()
let nav = UINavigationController(rootViewController: editorVC)
editorVC.navigationItem.leftBarButtonItem = UIBarButtonItem(
systemItem: .cancel, target: self, action: #selector(dismissEditor)
)
editorVC.navigationItem.rightBarButtonItem = UIBarButtonItem(
systemItem: .done, target: self, action: #selector(saveAndDismiss)
)
if let sheet = nav.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.prefersGrabberVisible = true
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
}
present(nav, animated: true)
}
```
### Custom Detent (iOS 16+)
```swift
if let sheet = nav.sheetPresentationController {
let customDetent = UISheetPresentationController.Detent.custom { context in
return context.maximumDetentValue * 0.4
}
sheet.detents = [customDetent, .large()]
}
```
### Full Screen Presentation
```swift
func presentFullScreen() {
let vc = FullScreenViewController()
vc.modalPresentationStyle = .fullScreen
vc.modalTransitionStyle = .coverVertical
present(vc, animated: true)
}
```
## Presentation Styles
| Style | Usage |
|-------|-------|
| `.automatic` | System default (usually sheet) |
| `.pageSheet` | Card-style, parent view visible |
| `.fullScreen` | Full screen cover |
| `.overFullScreen` | Full screen with transparent background |
| `.popover` | iPad popover |
## Navigation Best Practices
1. **Back gesture** - Ensure edge swipe back always works
2. **State restoration** - Use `UIStateRestoring` to save navigation stack
3. **Depth limit** - Avoid more than 4-5 navigation levels
4. **Cancel button** - Modal views must provide a cancel option
5. **Save confirmation** - Show confirmation dialog for unsaved changes
---
*UIKit, SF Symbols, and Apple are trademarks of Apple Inc.*

View File

@@ -0,0 +1,139 @@
# Onboarding — generate your skin from a website
**Goal:** point the skill at a site you like (your own, a competitor, a reference), and have it extract the palette + typography, then rewrite `style-guide.md` so every future diagram inherits that skin.
Takes about 60 seconds.
---
## The flow
```
URL you provide
[1] fetch homepage (headless browser or HTTP)
[2] extract dominant colors + fonts
[3] map to semantic roles (paper, ink, muted, accent, …)
[4] propose a style-guide.md diff
[5] write the diff (with your approval)
future diagrams use your tokens
```
---
## Invocation
Ask the skill:
> *"Onboard diagram-design to my site — `https://example.com`"*
Or run it with an explicit URL and the skill reads this file and executes the steps below.
---
## Step 1 — fetch the page
Use `agent-browser` (preferred) or a plain `fetch`. If the site has multiple pages worth sampling (landing + blog + product), fetch 23 and merge the palette signals.
```bash
agent-browser navigate https://example.com --screenshot out.png --html out.html
```
---
## Step 2 — extract colors and fonts
### Colors
Parse the rendered CSS and screenshot:
- **Background color** of `<body>` or the dominant large region → `paper`
- **Primary text color** (body text) → `ink`
- **Secondary text color** (captions, meta) → `muted`
- **Most-used brand color** (CTA button, link, heading accent) → `accent`
- **Container / card background** slightly darker than paper → `paper-2`
- **Border / hairline color** → `rule` (convert to rgba of ink at ~0.12 opacity)
Prefer CSS custom properties when the site exposes them (`:root { --accent: …; }`). Otherwise pull via rendered `getComputedStyle` samples or a color-histogram pass over the screenshot.
### Fonts
Read the rendered `font-family` stack of:
- `<h1>``title` family
- `<body>``node-name` family
- `<code>`, `<pre>`, or any mono-styled element → `sublabel` family
If the site has only one family, keep the diagram-design defaults for the missing roles (Instrument Serif for title, Geist Mono for mono). Don't force-pick a mono font that isn't on the site.
---
## Step 3 — map to semantic roles
Propose a diff by filling this table:
| Role | Detected | Confidence |
|---|---|---|
| paper | `#f8f6f0` | high |
| ink | `#111111` | high |
| muted | `#6b6b68` | medium |
| accent | `#c73a2b` | high |
| … | … | … |
Flag low-confidence guesses so the user can correct before applying.
### Constraint checks
Before writing, validate:
- **AA contrast**: `ink` on `paper` ≥ 4.5:1. `muted` on `paper` ≥ 4.5:1 for body text.
- **Accent is the most saturated color**: not muted-ish, not near-grey.
- **paper ≠ pure white**: if the site uses `#ffffff`, fall back to `#fafaf7` to preserve diagram-design's warm-neutral feel — or ask the user to confirm pure-white is intentional.
If any check fails, propose an adjusted value and explain why.
---
## Step 4 — preview the diff
Show the user what will change in `style-guide.md`. Only the tokens table — everything else stays the same.
```diff
-| `paper` | `#f5f4ed` | `#1c1a17` |
-| `ink` | `#0b0d0b` | `#f1efe7` |
-| `accent` | `#f7591f` | `#ff6a30` |
+| `paper` | `#f8f6f0` | `#1a1815` |
+| `ink` | `#111111` | `#efeee7` |
+| `accent` | `#c73a2b` | `#e05440` |
```
Also regenerate the dark variant via the inversion rule (`rgba(11,13,11, X)``rgba(ink-rgb, X)`).
---
## Step 5 — apply
Write the new tokens to `style-guide.md`. Suggest running the `/regenerate-examples` flow (if it exists) or rebuilding one example to verify the new skin reads cleanly.
After onboarding, the user should:
1. Open `assets/index.html` (gallery) and confirm the new palette feels coherent across all 13 types.
2. If any type looks off, they usually need to tune `muted` (often too dark or too light against the new `paper`).
---
## When onboarding fails
- **Site uses webfonts you can't replicate** (custom-hosted, paid): keep the diagram-design defaults for typography and skin only the colors.
- **Brand has 6+ colors** and you can't identify a clear hierarchy: pick one as `accent`, demote the rest to `muted` variants or ignore them. The diagram grammar only uses 57 roles.
- **Site is dark-mode first**: flip the inversion — treat their dark paper as the default `paper`, and generate a light variant via inversion.
- **Homepage is all imagery, no text**: ask for a blog or docs URL instead — text-heavy pages expose the type hierarchy.
---
## Future: per-project skins
If the user wants multiple skins (one per project), duplicate `style-guide.md` as `style-guides/<project>.md` and add a header comment pointing the build to the active one. That's a v1.1 feature — for now, one skin per skill install.

View File

@@ -0,0 +1,231 @@
# OOXML SpreadsheetML Cheat Sheet
Quick reference for XML manipulation of xlsx files.
---
## Package Structure
```
my_file.xlsx (ZIP archive)
├── [Content_Types].xml ← declares MIME types for all files
├── _rels/
│ └── .rels ← root relationship: points to xl/workbook.xml
└── xl/
├── workbook.xml ← sheet list, calc settings
├── styles.xml ← ALL style definitions
├── sharedStrings.xml ← ALL text strings (referenced by index)
├── _rels/
│ └── workbook.xml.rels ← maps r:id → worksheet/styles/sharedStrings files
├── worksheets/
│ ├── sheet1.xml ← Sheet 1 data
│ ├── sheet2.xml ← Sheet 2 data
│ └── ...
├── charts/ ← chart XML (if any)
├── pivotTables/ ← pivot table XML (if any)
└── theme/
└── theme1.xml ← color/font theme
```
---
## Cell Reference Format
```
A1 → column A (1), row 1
B5 → column B (2), row 5
AA1 → column 27, row 1
```
Column letter ↔ number conversion:
```python
def col_letter(n): # 1-based → letter
r = ""
while n > 0:
n, rem = divmod(n - 1, 26)
r = chr(65 + rem) + r
return r
def col_number(s): # letter → 1-based
n = 0
for c in s.upper():
n = n * 26 + (ord(c) - 64)
return n
```
---
## Cell XML Reference
### Data Types
| Type | `t` attr | XML Example | Value |
|------|---------|-------------|-------|
| Number | omit | `<c r="B2"><v>1000</v></c>` | 1000 |
| String (shared) | `s` | `<c r="A1" t="s"><v>0</v></c>` | sharedStrings[0] |
| String (inline) | `inlineStr` | `<c r="A1" t="inlineStr"><is><t>Hi</t></is></c>` | "Hi" |
| Boolean | `b` | `<c r="D1" t="b"><v>1</v></c>` | TRUE |
| Error | `e` | `<c r="E1" t="e"><v>#REF!</v></c>` | #REF! |
| Formula | omit | `<c r="B4"><f>SUM(B2:B3)</f><v></v></c>` | computed |
### Formula Types
```xml
<!-- Basic formula (no leading = in XML!) -->
<c r="B4"><f>SUM(B2:B3)</f><v></v></c>
<!-- Cross-sheet -->
<c r="C1"><f>Assumptions!B5</f><v></v></c>
<c r="C1"><f>'Sheet With Spaces'!B5</f><v></v></c>
<!-- Shared formula: D2:D100 all use B*C with relative row offset -->
<c r="D2"><f t="shared" ref="D2:D100" si="0">B2*C2</f><v></v></c>
<c r="D3"><f t="shared" si="0"/><v></v></c>
<!-- Array formula -->
<c r="E1"><f t="array" ref="E1:E5">SORT(A1:A5)</f><v></v></c>
```
---
## styles.xml Reference
### Indirect Reference Chain
```
Cell s="3"
cellXfs[3] → fontId="2", fillId="0", borderId="0", numFmtId="165"
↓ ↓ ↓ ↓ ↓
fonts[2] fills[0] borders[0] numFmts: id=165
blue color no fill no border "0.0%"
```
### Adding a New Style (step-by-step)
1. In `<numFmts>`: add `<numFmt numFmtId="168" formatCode="0.00%"/>`, update `count`
2. In `<fonts>`: add font entry, note its index
3. In `<cellXfs>`: append `<xf numFmtId="168" fontId="N" .../>`, update `count`
4. New style index = old `cellXfs count` value (before incrementing)
5. Apply to cells: `<c r="B5" s="NEW_INDEX">...</c>`
### Color Format
`AARRGGBB` — Alpha (always `00` for opaque) + Red + Green + Blue
```
000000FF → Blue
00000000 → Black
00008000 → Green (dark)
00FF0000 → Red
00FFFF00 → Yellow (for fills)
00FFFFFF → White
```
### Built-in numFmtIds (no declaration needed)
| ID | Format | Display |
|----|--------|---------|
| 0 | General | as-is |
| 1 | 0 | 2024 (use for years!) |
| 2 | 0.00 | 1000.00 |
| 3 | #,##0 | 1,000 |
| 4 | #,##0.00 | 1,000.00 |
| 9 | 0% | 15% |
| 10 | 0.00% | 15.25% |
| 14 | m/d/yyyy | 3/21/2026 |
---
## sharedStrings.xml Reference
```xml
<sst count="3" uniqueCount="3">
<si><t>Revenue</t></si> <!-- index 0 -->
<si><t>Cost</t></si> <!-- index 1 -->
<si><t>Margin</t></si> <!-- index 2 -->
</sst>
```
Text with leading/trailing spaces:
```xml
<si><t xml:space="preserve"> indented </t></si>
```
Special characters:
```xml
<si><t>R&amp;D Expenses</t></si> <!-- & must be &amp; -->
```
---
## workbook.xml / .rels Sync
Every `<sheet>` in workbook.xml needs a matching `<Relationship>` in workbook.xml.rels:
```xml
<!-- workbook.xml -->
<!-- NOTE: rId numbering depends on what rIds are already in workbook.xml.rels.
The minimal template reserves rId1=sheet1, rId2=styles, rId3=sharedStrings.
When ADDING sheets to the template, start from rId4 to avoid conflicts.
The rId3 here is just a generic illustration — use the next available rId. -->
<sheet name="Summary" sheetId="3" r:id="rId3"/>
<!-- workbook.xml.rels -->
<Relationship Id="rId3"
Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
Target="worksheets/sheet3.xml"/>
```
And a matching `<Override>` in `[Content_Types].xml`:
```xml
<Override PartName="/xl/worksheets/sheet3.xml"
ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>
```
---
## Column / Row Dimensions
```xml
<!-- Before <sheetData> -->
<cols>
<col min="1" max="1" width="28" customWidth="1"/> <!-- A: 28 chars -->
<col min="2" max="6" width="14" customWidth="1"/> <!-- B-F: 14 chars -->
</cols>
<!-- Row height on individual rows -->
<row r="1" ht="20" customHeight="1">
...
</row>
```
---
## Freeze Panes
Inside `<sheetView>`:
```xml
<!-- Freeze row 1 (header row stays visible) -->
<pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/>
<!-- Freeze column A -->
<pane xSplit="1" topLeftCell="B1" activePane="topRight" state="frozen"/>
<!-- Freeze both row 1 and column A -->
<pane xSplit="1" ySplit="1" topLeftCell="B2" activePane="bottomRight" state="frozen"/>
```
---
## 7 Excel Error Types (All Must Be Absent at Delivery)
| Error | Meaning | Detect in XML |
|-------|---------|---------------|
| `#REF!` | Invalid cell reference | `<c t="e"><v>#REF!</v></c>` |
| `#DIV/0!` | Divide by zero | `<c t="e"><v>#DIV/0!</v></c>` |
| `#VALUE!` | Wrong data type | `<c t="e"><v>#VALUE!</v></c>` |
| `#NAME?` | Unknown function/name | `<c t="e"><v>#NAME?</v></c>` |
| `#NULL!` | Empty intersection | `<c t="e"><v>#NULL!</v></c>` |
| `#NUM!` | Number out of range | `<c t="e"><v>#NUM!</v></c>` |
| `#N/A` | Value not found | `<c t="e"><v>#N/A</v></c>` |

View File

@@ -0,0 +1,308 @@
# OpenXML Child Element Ordering Rules
Element ordering in OpenXML is defined by the XSD schema. Incorrect ordering produces invalid documents that Word may refuse to open or silently repair (potentially losing data).
> **Key rule**: Properties elements (`*Pr`) must always be the **first child** of their parent.
---
## w:document
```
Children in order:
1. w:background [0..1] — page background color/fill
2. w:body [0..1] — document content container
```
---
## w:body
```
Children in order (repeating group):
1. w:p [0..*] — paragraph
2. w:tbl [0..*] — table
3. w:sdt [0..*] — structured document tag (content control)
4. w:sectPr [0..1] — LAST child: final section properties
```
Note: `w:p`, `w:tbl`, and `w:sdt` are interleaved in document order. The only strict rule is that `w:sectPr` must be the **last child** of `w:body`.
---
## w:p (Paragraph)
```
Children in order:
1. w:pPr [0..1] — paragraph properties (MUST be first)
Then any mix of (interleaved in document order):
- w:r [0..*] — run
- w:hyperlink [0..*] — hyperlink wrapper
- w:ins [0..*] — tracked insertion
- w:del [0..*] — tracked deletion
- w:bookmarkStart [0..*] — bookmark anchor start
- w:bookmarkEnd [0..*] — bookmark anchor end
- w:commentRangeStart [0..*] — comment range start
- w:commentRangeEnd [0..*] — comment range end
- w:proofErr [0..*] — proofing error marker
- w:fldSimple [0..*] — simple field
- w:sdt [0..*] — inline content control
- w:smartTag [0..*] — smart tag
```
**Practical note**: After `w:pPr`, the remaining children appear in document reading order. Runs, hyperlinks, bookmarks, and comment ranges intermix freely based on their position in the text.
---
## w:pPr (Paragraph Properties)
```
Children in order:
1. w:pStyle [0..1] — paragraph style reference
2. w:keepNext [0..1] — keep with next paragraph
3. w:keepLines [0..1] — keep lines together
4. w:pageBreakBefore [0..1] — page break before paragraph
5. w:framePr [0..1] — text frame properties
6. w:widowControl [0..1] — widow/orphan control
7. w:numPr [0..1] — numbering properties
8. w:suppressLineNumbers [0..1]
9. w:pBdr [0..1] — paragraph borders
10. w:shd [0..1] — shading
11. w:tabs [0..1] — tab stops
12. w:suppressAutoHyphens [0..1]
13. w:kinsoku [0..1] — CJK kinsoku settings
14. w:wordWrap [0..1]
15. w:overflowPunct [0..1]
16. w:topLinePunct [0..1]
17. w:autoSpaceDE [0..1]
18. w:autoSpaceDN [0..1]
19. w:bidi [0..1] — right-to-left paragraph
20. w:adjustRightInd [0..1]
21. w:snapToGrid [0..1]
22. w:spacing [0..1] — line and paragraph spacing
23. w:ind [0..1] — indentation
24. w:contextualSpacing [0..1]
25. w:mirrorIndents [0..1]
26. w:suppressOverlap [0..1]
27. w:jc [0..1] — justification (left/center/right/both)
28. w:textDirection [0..1]
29. w:textAlignment [0..1]
30. w:outlineLvl [0..1] — outline level
31. w:divId [0..1]
32. w:rPr [0..1] — run properties for paragraph mark
33. w:sectPr [0..1] — section break (section ends at this paragraph)
34. w:pPrChange [0..1] — tracked paragraph property change
```
---
## w:r (Run)
```
Children in order:
1. w:rPr [0..1] — run properties (MUST be first)
Then any of (one per run, typically):
- w:t [0..*] — text content
- w:br [0..*] — break (line, page, column)
- w:tab [0..*] — tab character
- w:cr [0..*] — carriage return
- w:sym [0..*] — symbol character
- w:drawing [0..*] — DrawingML object (images)
- w:pict [0..*] — VML picture (legacy)
- w:fldChar [0..*] — complex field character
- w:instrText [0..*] — field instruction text
- w:delText [0..*] — deleted text (inside w:del)
- w:footnoteReference [0..*]
- w:endnoteReference [0..*]
- w:commentReference [0..*]
- w:lastRenderedPageBreak [0..*]
```
---
## w:rPr (Run Properties)
```
Children in order:
1. w:rStyle [0..1] — character style reference
2. w:rFonts [0..1] — font specification
3. w:b [0..1] — bold
4. w:bCs [0..1] — complex script bold
5. w:i [0..1] — italic
6. w:iCs [0..1] — complex script italic
7. w:caps [0..1] — all capitals
8. w:smallCaps [0..1] — small capitals
9. w:strike [0..1] — strikethrough
10. w:dstrike [0..1] — double strikethrough
11. w:outline [0..1]
12. w:shadow [0..1]
13. w:emboss [0..1]
14. w:imprint [0..1]
15. w:noProof [0..1] — suppress proofing
16. w:snapToGrid [0..1]
17. w:vanish [0..1] — hidden text
18. w:color [0..1] — text color
19. w:spacing [0..1] — character spacing
20. w:w [0..1] — character width scaling
21. w:kern [0..1] — font kerning
22. w:position [0..1] — vertical position (raise/lower)
23. w:sz [0..1] — font size (half-points)
24. w:szCs [0..1] — complex script font size
25. w:highlight [0..1] — text highlight color
26. w:u [0..1] — underline
27. w:effect [0..1] — text effect (animated)
28. w:bdr [0..1] — run border
29. w:shd [0..1] — run shading
30. w:vertAlign [0..1] — superscript/subscript
31. w:rtl [0..1] — right-to-left
32. w:cs [0..1] — complex script
33. w:lang [0..1] — language
34. w:rPrChange [0..1] — tracked run property change
```
---
## w:tbl (Table)
```
Children in order:
1. w:tblPr [1..1] — table properties (REQUIRED, must be first)
2. w:tblGrid [1..1] — column width definitions (REQUIRED)
3. w:tr [1..*] — table row(s)
```
---
## w:tblPr (Table Properties)
```
Children in order:
1. w:tblStyle [0..1] — table style reference
2. w:tblpPr [0..1] — table positioning
3. w:tblOverlap [0..1]
4. w:bidiVisual [0..1] — right-to-left table
5. w:tblStyleRowBandSize [0..1]
6. w:tblStyleColBandSize [0..1]
7. w:tblW [0..1] — preferred table width
8. w:jc [0..1] — table alignment
9. w:tblCellSpacing [0..1]
10. w:tblInd [0..1] — table indent from margin
11. w:tblBorders [0..1] — table borders
12. w:shd [0..1] — table shading
13. w:tblLayout [0..1] — fixed or autofit
14. w:tblCellMar [0..1] — default cell margins
15. w:tblLook [0..1] — conditional formatting flags
16. w:tblCaption [0..1] — accessibility caption
17. w:tblDescription [0..1] — accessibility description
18. w:tblPrChange [0..1] — tracked table property change
```
---
## w:tr (Table Row)
```
Children in order:
1. w:trPr [0..1] — row properties (must be first)
2. w:tc [1..*] — table cell(s)
```
---
## w:trPr (Table Row Properties)
```
Children in order:
1. w:cnfStyle [0..1] — conditional formatting
2. w:divId [0..1]
3. w:gridBefore [0..1] — grid columns before first cell
4. w:gridAfter [0..1] — grid columns after last cell
5. w:wBefore [0..1]
6. w:wAfter [0..1]
7. w:cantSplit [0..1] — don't split row across pages
8. w:trHeight [0..1] — row height
9. w:tblHeader [0..1] — repeat as header row
10. w:tblCellSpacing [0..1]
11. w:jc [0..1] — row alignment
12. w:hidden [0..1]
13. w:ins [0..1] — tracked row insertion
14. w:del [0..1] — tracked row deletion
15. w:trPrChange [0..1] — tracked row property change
```
---
## w:tc (Table Cell)
```
Children in order:
1. w:tcPr [0..1] — cell properties (must be first)
2. w:p [1..*] — paragraph(s) — at least one required
3. w:tbl [0..*] — nested table(s)
```
---
## w:tcPr (Table Cell Properties)
```
Children in order:
1. w:cnfStyle [0..1]
2. w:tcW [0..1] — cell width
3. w:gridSpan [0..1] — horizontal merge (column span)
4. w:hMerge [0..1] — legacy horizontal merge
5. w:vMerge [0..1] — vertical merge
6. w:tcBorders [0..1] — cell borders
7. w:shd [0..1] — cell shading
8. w:noWrap [0..1]
9. w:tcMar [0..1] — cell margins
10. w:textDirection [0..1]
11. w:tcFitText [0..1]
12. w:vAlign [0..1] — vertical alignment
13. w:hideMark [0..1]
14. w:tcPrChange [0..1] — tracked cell property change
```
---
## w:sectPr (Section Properties)
```
Children in order:
1. w:headerReference [0..*] — header references (type: default/first/even)
2. w:footerReference [0..*] — footer references
3. w:endnotePr [0..1]
4. w:footnotePr [0..1]
5. w:type [0..1] — section break type (nextPage/continuous/evenPage/oddPage)
6. w:pgSz [0..1] — page size
7. w:pgMar [0..1] — page margins
8. w:paperSrc [0..1]
9. w:pgBorders [0..1] — page borders
10. w:lnNumType [0..1] — line numbering
11. w:pgNumType [0..1] — page numbering
12. w:cols [0..1] — column definitions
13. w:formProt [0..1]
14. w:vAlign [0..1] — vertical alignment of page
15. w:noEndnote [0..1]
16. w:titlePg [0..1] — different first page header/footer
17. w:textDirection [0..1]
18. w:bidi [0..1]
19. w:rtlGutter [0..1]
20. w:docGrid [0..1] — document grid
21. w:sectPrChange [0..1] — tracked section property change
```
---
## w:hdr (Header) / w:ftr (Footer)
```
Children (same structure as w:body content):
1. w:p [0..*] — paragraph(s)
2. w:tbl [0..*] — table(s)
3. w:sdt [0..*] — content controls
```
Headers and footers are essentially mini-documents. They follow the same content model as `w:body` but without a final `w:sectPr`.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,82 @@
# OpenXML Namespaces, Relationship Types, and Content Types
## Core Namespaces
| Prefix | URI | Used In |
|--------|-----|---------|
| `w` | `http://schemas.openxmlformats.org/wordprocessingml/2006/main` | document.xml, styles.xml, numbering.xml, headers, footers |
| `r` | `http://schemas.openxmlformats.org/officeDocument/2006/relationships` | Relationship references (r:id) |
| `wp` | `http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing` | Image/drawing placement in document |
| `a` | `http://schemas.openxmlformats.org/drawingml/2006/main` | DrawingML core (shapes, images, themes) |
| `pic` | `http://schemas.openxmlformats.org/drawingml/2006/picture` | Picture element in DrawingML |
| `v` | `urn:schemas-microsoft-com:vml` | VML (legacy shapes, watermarks) |
| `o` | `urn:schemas-microsoft-com:office:office` | Office VML extensions |
| `m` | `http://schemas.openxmlformats.org/officeDocument/2006/math` | Math equations (OMML) |
| `mc` | `http://schemas.openxmlformats.org/markup-compatibility/2006` | Markup compatibility (Ignorable, AlternateContent) |
## Extended Namespaces
| Prefix | URI | Purpose |
|--------|-----|---------|
| `w14` | `http://schemas.microsoft.com/office/word/2010/wordml` | Word 2010 extensions (contentPart, etc.) |
| `w15` | `http://schemas.microsoft.com/office/word/2012/wordml` | Word 2013 extensions (commentEx, etc.) |
| `w16cid` | `http://schemas.microsoft.com/office/word/2016/wordml/cid` | Comment IDs (durable IDs) |
| `w16cex` | `http://schemas.microsoft.com/office/word/2018/wordml/cex` | Comment extensible |
| `w16se` | `http://schemas.microsoft.com/office/word/2015/wordml/symex` | Symbol extensions |
| `wps` | `http://schemas.microsoft.com/office/word/2010/wordprocessingShape` | WordprocessingML shapes |
| `wpc` | `http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas` | Drawing canvas |
## Relationship Types
| Relationship | Type URI |
|-------------|----------|
| Document | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument` |
| Styles | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles` |
| Numbering | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/numbering` |
| Font Table | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/fontTable` |
| Settings | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings` |
| Theme | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme` |
| Image | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/image` |
| Hyperlink | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink` |
| Header | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/header` |
| Footer | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/footer` |
| Comments | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments` |
| CommentsExtended | `http://schemas.microsoft.com/office/2011/relationships/commentsExtended` |
| CommentsIds | `http://schemas.microsoft.com/office/2016/09/relationships/commentsIds` |
| CommentsExtensible | `http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible` |
| Footnotes | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes` |
| Endnotes | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes` |
| Glossary | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/glossaryDocument` |
| Web Settings | `http://schemas.openxmlformats.org/officeDocument/2006/relationships/webSettings` |
## Content Types (`[Content_Types].xml`)
### Default Extensions
```xml
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
<Default Extension="xml" ContentType="application/xml" />
<Default Extension="png" ContentType="image/png" />
<Default Extension="jpeg" ContentType="image/jpeg" />
<Default Extension="gif" ContentType="image/gif" />
<Default Extension="emf" ContentType="image/x-emf" />
```
### Part Overrides
| Part | Content Type |
|------|-------------|
| `/word/document.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml` |
| `/word/styles.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml` |
| `/word/numbering.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml` |
| `/word/settings.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml` |
| `/word/fontTable.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml` |
| `/word/theme/theme1.xml` | `application/vnd.openxmlformats-officedocument.theme+xml` |
| `/word/header1.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml` |
| `/word/footer1.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml` |
| `/word/comments.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml` |
| `/word/commentsExtended.xml` | `application/vnd.ms-word.commentsExtended+xml` |
| `/word/commentsIds.xml` | `application/vnd.ms-word.commentsIds+xml` |
| `/word/commentsExtensible.xml` | `application/vnd.ms-word.commentsExtensible+xml` |
| `/word/footnotes.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml` |
| `/word/endnotes.xml` | `application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml` |

View File

@@ -0,0 +1,72 @@
# OpenXML Unit Conversion Quick Reference
## Master Conversion Table
| Unit | 1 inch | 1 cm | 1 mm | 1 pt | Description |
|------|--------|------|------|------|-------------|
| DXA (twips) | 1440 | 567 | 56.7 | 20 | 1/20 of a point. Used for margins, indents, spacing, page size. |
| EMU | 914400 | 360000 | 36000 | 12700 | English Metric Unit. Used for images, drawings, shapes. |
| Half-points | 144 | 56.7 | 5.67 | 2 | Used for font sizes (`w:sz`, `w:szCs`). |
| Points | 72 | 28.35 | 2.835 | 1 | Standard typographic unit. Not used directly in most attributes. |
| Eighths of a point | 576 | 226.8 | 22.68 | 8 | Used for `w:spacing` character spacing. |
## Common Page Sizes
| Size | Width (DXA) | Height (DXA) | Width (mm) | Height (mm) |
|------|-------------|--------------|------------|-------------|
| A4 | 11906 | 16838 | 210 | 297 |
| Letter | 12240 | 15840 | 215.9 | 279.4 |
| Legal | 12240 | 20160 | 215.9 | 355.6 |
| A3 | 16838 | 23811 | 297 | 420 |
| A5 | 8391 | 11906 | 148 | 210 |
## Common Margin Values
| Margin | DXA | Inches | cm |
|--------|-----|--------|----|
| 0.5 inch | 720 | 0.5 | 1.27 |
| 0.75 inch | 1080 | 0.75 | 1.91 |
| 1 inch | 1440 | 1.0 | 2.54 |
| 1.25 inch | 1800 | 1.25 | 3.18 |
| 1.5 inch | 2160 | 1.5 | 3.81 |
## Font Size Values (`w:sz`)
| Display Size | w:sz value | Notes |
|-------------|-----------|-------|
| 8pt | 16 | |
| 9pt | 18 | |
| 10pt | 20 | |
| 10.5pt | 21 | Common CJK body size |
| 11pt | 22 | Default Calibri body |
| 12pt | 24 | Default TNR body |
| 14pt | 28 | Small heading |
| 16pt | 32 | |
| 18pt | 36 | |
| 20pt | 40 | |
| 24pt | 48 | |
| 28pt | 56 | |
| 36pt | 72 | |
## Line Spacing Values
Line spacing in `w:spacing` uses the `w:line` attribute in 240ths of a line (when `w:lineRule="auto"`):
| Spacing | w:line value | w:lineRule |
|---------|-------------|-----------|
| Single | 240 | auto |
| 1.15 (Word default) | 276 | auto |
| 1.5 | 360 | auto |
| Double | 480 | auto |
| Exact 12pt | 240 | exact |
| At least 12pt | 240 | atLeast |
Note: When `lineRule="exact"` or `"atLeast"`, `w:line` is in **twips** (DXA), not 240ths. So `line="240"` with `lineRule="exact"` means exactly 12pt (240/20 = 12pt).
## Conversion Formulas
```
DXA = inches × 1440 = cm × 567 = pt × 20
EMU = inches × 914400 = cm × 360000 = pt × 12700
sz = pt × 2 (half-points)
```

View File

@@ -0,0 +1,223 @@
# Performance & Stability Guidelines
Android Vitals thresholds, performance requirements, and stability best practices.
## Android Vitals Thresholds
### Core Metrics (Google Play)
Exceeding these thresholds affects app visibility on Google Play:
| Metric | Overall Threshold | Per Phone Model | Per Watch Model |
|--------|-------------------|-----------------|-----------------|
| User-perceived crash rate | **1.09%** | 8% | 4% |
| User-perceived ANR rate | **0.47%** | 8% | 5% |
| Excessive battery usage | 1% | - | 1% |
| Excessive wake locks | 5% | - | - |
### Consequences of Exceeding Thresholds
- Reduced app visibility in Google Play
- Warning label on store listing
- Lower ranking in search results
- Negative impact on user trust
## Startup Performance
### Requirements
| Metric | Target | Maximum |
|--------|--------|---------|
| Cold start | < 1 second | 2 seconds |
| Warm start | < 500ms | 1 second |
| Hot start | < 100ms | 500ms |
### If Startup Exceeds 2 Seconds
Must provide visual feedback:
- Progress indicator
- Splash screen with animation
- Loading skeleton
### Optimization Techniques
| Technique | Impact |
|-----------|--------|
| Lazy initialization | Defer non-critical work |
| Async loading | Move I/O off main thread |
| View hierarchy optimization | Reduce layout depth |
| App Startup library | Initialize components efficiently |
| Baseline Profiles | Pre-compile hot paths |
## Rendering Performance
### Frame Rate Requirements
| Target | Frame Time | Notes |
|--------|------------|-------|
| 60 FPS | ≤ 16.67ms | Standard requirement |
| 90 FPS | ≤ 11.11ms | High refresh rate displays |
| 120 FPS | ≤ 8.33ms | Premium devices |
### Jank Detection
| Metric | Threshold | Severity |
|--------|-----------|----------|
| Slow frames | > 16ms | Warning |
| Frozen frames | > 700ms | Critical |
| Jank rate | > 1% of frames | Poor experience |
### Common Rendering Issues
| Issue | Cause | Solution |
|-------|-------|----------|
| Overdraw | Multiple layers drawn | Reduce background stacking |
| Deep hierarchy | Complex view nesting | Use ConstraintLayout, Compose |
| Main thread work | Blocking operations | Move to background thread |
| Large bitmaps | Unoptimized images | Downsample, use vector |
## ANR Prevention
### ANR Triggers
| Scenario | Timeout |
|----------|---------|
| Input dispatch | 5 seconds |
| Broadcast receiver | 10 seconds |
| Service start | 20 seconds |
### Prevention Strategies
- Never perform network calls on main thread
- Never perform database operations on main thread
- Never perform file I/O on main thread
- Use coroutines, RxJava, or other async mechanisms
- Reduce synchronized block contention
### Common ANR Causes
| Cause | Solution |
|-------|----------|
| Network on main thread | Use coroutines/RxJava |
| Database on main thread | Use Room with suspend |
| File I/O on main thread | Use Dispatchers.IO |
| Lock contention | Reduce synchronized blocks |
| Dead locks | Careful threading design |
## Battery Optimization
### Wake Lock Guidelines
| Rule | Implementation |
|------|----------------|
| Minimize duration | Release as soon as possible |
| Use appropriate type | PARTIAL_WAKE_LOCK only when needed |
| Always release | Use try-finally or lifecycle |
| Prefer WorkManager | System-managed scheduling |
### Background Restrictions
| Feature | Best Practice |
|---------|---------------|
| Background services | Use WorkManager instead |
| Location | Request only when necessary |
| Network | Batch requests, respect connectivity |
| Alarms | Use inexact alarms when possible |
### Doze and App Standby
| Mode | Behavior | Adaptation |
|------|----------|------------|
| Doze | Limited network, alarms delayed | Use FCM for high-priority |
| App Standby | Background work restricted | Use expedited WorkManager |
| Buckets | Frequency limits by usage | Design for infrequent execution |
## Memory Management
### Memory Best Practices
| Practice | Benefit |
|----------|---------|
| Avoid memory leaks | Prevent OutOfMemoryError |
| Use weak references | Allow garbage collection |
| Recycle bitmaps | Reduce memory pressure |
| Monitor heap | Profile regularly |
### Common Memory Issues
| Issue | Detection | Solution |
|-------|-----------|----------|
| Activity leak | LeakCanary | Fix lifecycle references |
| Bitmap leak | Memory profiler | Recycle, use Glide/Coil |
| Context leak | Static analysis | Use application context |
| Handler leak | Lint warning | Use WeakReference |
## StrictMode
### What StrictMode Detects
| Category | Issues |
|----------|--------|
| Thread | Disk reads/writes, network, slow calls |
| VM | Leaked objects, unsafe intents, content URI exposure |
Enable StrictMode in debug builds to detect violations during development.
## SDK Requirements
### Version Requirements
| Property | Requirement |
|----------|-------------|
| targetSdk | Latest Android SDK (Google Play requirement) |
| compileSdk | Latest Android SDK |
| minSdk | Based on target audience |
### Third-Party SDK Management
| Practice | Reason |
|----------|--------|
| Keep updated | Security fixes, compatibility |
| Audit regularly | Remove unused dependencies |
| Monitor crashes | SDKs can cause issues |
| Check permissions | SDKs may request excessive permissions |
### Non-SDK Interface Restrictions
- Don't use reflection for hidden APIs
- Use Android Studio lint to detect
- APIs may break in future versions
## Monitoring and Profiling
### Tools
| Tool | Purpose |
|------|---------|
| Android Studio Profiler | CPU, memory, network, energy |
| Android Vitals (Play Console) | Production crash/ANR data |
| Firebase Performance | Real-time performance monitoring |
| Perfetto | Advanced system tracing |
| Benchmark library | Reproducible measurements |
### Key Metrics to Track
| Metric | Tool |
|--------|------|
| Startup time | Macrobenchmark |
| Frame timing | JankStats |
| Memory usage | Memory Profiler |
| Network latency | Network Profiler |
| Battery drain | Energy Profiler |
## Performance Checklist
- [ ] Cold startup < 2 seconds
- [ ] Rendering at 60 FPS
- [ ] No StrictMode violations
- [ ] Crash rate < 1.09%
- [ ] ANR rate < 0.47%
- [ ] No memory leaks
- [ ] Background work uses WorkManager
- [ ] Wake locks properly released
- [ ] SDKs up to date

View File

@@ -0,0 +1,49 @@
{
"blog-hero": {
"description": "Dark cinematic blog hero with logo overlay",
"estimated_cost": "$0.043",
"pipeline": [
{ "op": "generate", "prompt": "<scene_description>", "size": "1200x630" },
{ "op": "edit", "prompt": "place Figure 1 as a large glowing element in center", "assets": ["logo.png"] },
{ "op": "grade", "name": "cinematic" },
{ "op": "vignette" }
]
},
"product-comparison": {
"description": "Side-by-side product comparison with remove-bg",
"estimated_cost": "$0.01",
"pipeline": [
{ "op": "generate", "prompt": "clean gradient backdrop, professional lighting", "size": "1200x630" },
{ "op": "remove-bg", "assets": ["product-a.png"] },
{ "op": "compose", "overlays": "comparison-layout.json" },
{ "op": "grade", "name": "clean" }
]
},
"youtube-thumbnail": {
"description": "Bold text-behind-subject YouTube thumbnail",
"estimated_cost": "$0.07",
"pipeline": [
{ "op": "generate", "prompt": "<scene>", "size": "1280x720" },
{ "op": "edit", "prompt": "place text '<title>' behind the subject, keep subject fully visible", "assets": [] },
{ "op": "grade", "name": "cinematic" },
{ "op": "vignette" }
]
},
"instagram-square": {
"description": "Clean Instagram square post",
"estimated_cost": "$0.003-0.15",
"pipeline": [
{ "op": "generate", "prompt": "<prompt>", "size": "1080x1080" },
{ "op": "grade", "name": "vibrant" }
]
},
"social-card": {
"description": "Text overlay on generated background",
"estimated_cost": "$0.003",
"pipeline": [
{ "op": "generate", "prompt": "<background_prompt>", "size": "1200x630" },
{ "op": "text", "title": "<title>", "fontSize": 64, "font": "Space Grotesk" },
{ "op": "grade", "name": "cinematic" }
]
}
}

View File

@@ -0,0 +1,112 @@
# QA Process & Common Pitfalls
## QA Process
**Assume there are problems. Your job is to find them.**
Your first render is almost never correct. Approach QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you weren't looking hard enough.
### Content QA
```bash
python -m markitdown output.pptx
```
Check for missing content, typos, wrong order.
**Check for leftover placeholder text:**
```bash
python -m markitdown output.pptx | grep -iE "xxxx|lorem|ipsum|placeholder|this.*(page|slide).*layout"
```
If grep returns results, fix them before declaring success.
### Verification Loop
1. Generate slides -> Extract text with `python -m markitdown output.pptx` -> Review content
2. **List issues found** (if none found, look again more critically)
3. Fix issues
4. **Re-verify affected slides** — one fix often creates another problem
5. Repeat until a full pass reveals no new issues
**Do not declare success until you've completed at least one fix-and-verify cycle.**
### Per-Slide QA (for from-scratch creation)
```bash
python -m markitdown slide-XX-preview.pptx
```
Check for missing content, placeholder text, missing page number badge.
---
## Common Mistakes to Avoid
- **Don't repeat the same layout** — vary columns, cards, and callouts across slides
- **Don't center body text** — left-align paragraphs and lists; center only titles
- **Don't skimp on size contrast** — titles need 36pt+ to stand out from 14-16pt body
- **Don't default to blue** — pick colors that reflect the specific topic
- **Don't mix spacing randomly** — choose 0.3" or 0.5" gaps and use consistently
- **Don't style one slide and leave the rest plain** — commit fully or keep it simple throughout
- **Don't create text-only slides** — add images, icons, charts, or visual elements; avoid plain title + bullets
- **Don't forget text box padding** — when aligning lines or shapes with text edges, set `margin: 0` on the text box or offset the shape to account for padding
- **Don't use low-contrast elements** — icons AND text need strong contrast against the background
- **NEVER use accent lines under titles** — these are a hallmark of AI-generated slides; use whitespace or background color instead
- **NEVER use "#" with hex colors** — causes file corruption in PptxGenJS
- **NEVER encode opacity in hex strings** — use the `opacity` property instead
- **NEVER use async/await in createSlide()** — compile.js won't await
- **NEVER reuse option objects across PptxGenJS calls** — PptxGenJS mutates objects in-place
---
## Critical Pitfalls — PptxGenJS
### NEVER use async/await in createSlide()
```javascript
// WRONG - compile.js won't await
async function createSlide(pres, theme) { ... }
// CORRECT
function createSlide(pres, theme) { ... }
```
### NEVER use "#" with hex colors
```javascript
color: "FF0000" // CORRECT
color: "#FF0000" // CORRUPTS FILE
```
### NEVER encode opacity in hex strings
```javascript
shadow: { color: "00000020" } // CORRUPTS FILE
shadow: { color: "000000", opacity: 0.12 } // CORRECT
```
### Prevent text wrapping in titles
```javascript
// Use fit:'shrink' for long titles
slide.addText("Long Title Here", {
x: 0.5, y: 2, w: 9, h: 1,
fontSize: 48, fit: "shrink"
});
```
### NEVER reuse option objects across calls
```javascript
// WRONG
const shadow = { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 };
slide.addShape(pres.shapes.RECTANGLE, { shadow, ... });
slide.addShape(pres.shapes.RECTANGLE, { shadow, ... });
// CORRECT - factory function
const makeShadow = () => ({ type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 });
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... });
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... });
```

View File

@@ -0,0 +1,420 @@
# PptxGenJS Tutorial
## Setup & Basic Structure
```javascript
const pptxgen = require("pptxgenjs");
let pres = new pptxgen();
pres.layout = 'LAYOUT_16x9'; // or 'LAYOUT_16x10', 'LAYOUT_4x3', 'LAYOUT_WIDE'
pres.author = 'Your Name';
pres.title = 'Presentation Title';
let slide = pres.addSlide();
slide.addText("Hello World!", { x: 0.5, y: 0.5, fontSize: 36, color: "363636" });
pres.writeFile({ fileName: "Presentation.pptx" });
```
## Layout Dimensions
Slide dimensions (coordinates in inches):
- `LAYOUT_16x9`: 10" x 5.625" (default)
- `LAYOUT_16x10`: 10" x 6.25"
- `LAYOUT_4x3`: 10" x 7.5"
- `LAYOUT_WIDE`: 13.3" x 7.5"
---
## Text & Formatting
```javascript
// Basic text
slide.addText("Simple Text", {
x: 1, y: 1, w: 8, h: 2, fontSize: 24, fontFace: "Arial",
color: "363636", bold: true, align: "center", valign: "middle"
});
// Character spacing (use charSpacing, not letterSpacing which is silently ignored)
slide.addText("SPACED TEXT", { x: 1, y: 1, w: 8, h: 1, charSpacing: 6 });
// Rich text arrays
slide.addText([
{ text: "Bold ", options: { bold: true } },
{ text: "Italic ", options: { italic: true } }
], { x: 1, y: 3, w: 8, h: 1 });
// Multi-line text (requires breakLine: true)
slide.addText([
{ text: "Line 1", options: { breakLine: true } },
{ text: "Line 2", options: { breakLine: true } },
{ text: "Line 3" } // Last item doesn't need breakLine
], { x: 0.5, y: 0.5, w: 8, h: 2 });
// Text box margin (internal padding)
slide.addText("Title", {
x: 0.5, y: 0.3, w: 9, h: 0.6,
margin: 0 // Use 0 when aligning text with other elements like shapes or icons
});
```
**Tip:** Text boxes have internal margin by default. Set `margin: 0` when you need text to align precisely with shapes, lines, or icons at the same x-position.
---
## Lists & Bullets
```javascript
// CORRECT: Multiple bullets
slide.addText([
{ text: "First item", options: { bullet: true, breakLine: true } },
{ text: "Second item", options: { bullet: true, breakLine: true } },
{ text: "Third item", options: { bullet: true } }
], { x: 0.5, y: 0.5, w: 8, h: 3 });
// WRONG: Never use unicode bullets
slide.addText("* First item", { ... }); // Creates double bullets
// Sub-items and numbered lists
{ text: "Sub-item", options: { bullet: true, indentLevel: 1 } }
{ text: "First", options: { bullet: { type: "number" }, breakLine: true } }
```
---
## Shapes
```javascript
slide.addShape(pres.shapes.RECTANGLE, {
x: 0.5, y: 0.8, w: 1.5, h: 3.0,
fill: { color: "FF0000" }, line: { color: "000000", width: 2 }
});
slide.addShape(pres.shapes.OVAL, { x: 4, y: 1, w: 2, h: 2, fill: { color: "0000FF" } });
slide.addShape(pres.shapes.LINE, {
x: 1, y: 3, w: 5, h: 0, line: { color: "FF0000", width: 3, dashType: "dash" }
});
// With transparency
slide.addShape(pres.shapes.RECTANGLE, {
x: 1, y: 1, w: 3, h: 2,
fill: { color: "0088CC", transparency: 50 }
});
// Rounded rectangle (rectRadius only works with ROUNDED_RECTANGLE, not RECTANGLE)
// Don't pair with rectangular accent overlays -- they won't cover rounded corners. Use RECTANGLE instead.
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, {
x: 1, y: 1, w: 3, h: 2,
fill: { color: "FFFFFF" }, rectRadius: 0.1
});
// With shadow
slide.addShape(pres.shapes.RECTANGLE, {
x: 1, y: 1, w: 3, h: 2,
fill: { color: "FFFFFF" },
shadow: { type: "outer", color: "000000", blur: 6, offset: 2, angle: 135, opacity: 0.15 }
});
```
Shadow options:
| Property | Type | Range | Notes |
|----------|------|-------|-------|
| `type` | string | `"outer"`, `"inner"` | |
| `color` | string | 6-char hex (e.g. `"000000"`) | No `#` prefix, no 8-char hex -- see Common Pitfalls |
| `blur` | number | 0-100 pt | |
| `offset` | number | 0-200 pt | **Must be non-negative** -- negative values corrupt the file |
| `angle` | number | 0-359 degrees | Direction the shadow falls (135 = bottom-right, 270 = upward) |
| `opacity` | number | 0.0-1.0 | Use this for transparency, never encode in color string |
To cast a shadow upward (e.g. on a footer bar), use `angle: 270` with a positive offset -- do **not** use a negative offset.
**Note**: Gradient fills are not natively supported. Use a gradient image as a background instead.
---
## Images
### Image Sources
```javascript
// From file path
slide.addImage({ path: "images/chart.png", x: 1, y: 1, w: 5, h: 3 });
// From URL
slide.addImage({ path: "https://example.com/image.jpg", x: 1, y: 1, w: 5, h: 3 });
// From base64 (faster, no file I/O)
slide.addImage({ data: "image/png;base64,iVBORw0KGgo...", x: 1, y: 1, w: 5, h: 3 });
```
### Image Options
```javascript
slide.addImage({
path: "image.png",
x: 1, y: 1, w: 5, h: 3,
rotate: 45, // 0-359 degrees
rounding: true, // Circular crop
transparency: 50, // 0-100
flipH: true, // Horizontal flip
flipV: false, // Vertical flip
altText: "Description", // Accessibility
hyperlink: { url: "https://example.com" }
});
```
### Image Sizing Modes
```javascript
// Contain - fit inside, preserve ratio
{ sizing: { type: 'contain', w: 4, h: 3 } }
// Cover - fill area, preserve ratio (may crop)
{ sizing: { type: 'cover', w: 4, h: 3 } }
// Crop - cut specific portion
{ sizing: { type: 'crop', x: 0.5, y: 0.5, w: 2, h: 2 } }
```
### Calculate Dimensions (preserve aspect ratio)
```javascript
const origWidth = 1978, origHeight = 923, maxHeight = 3.0;
const calcWidth = maxHeight * (origWidth / origHeight);
const centerX = (10 - calcWidth) / 2;
slide.addImage({ path: "image.png", x: centerX, y: 1.2, w: calcWidth, h: maxHeight });
```
### Supported Formats
- **Standard**: PNG, JPG, GIF (animated GIFs work in Microsoft 365)
- **SVG**: Works in modern PowerPoint/Microsoft 365
---
## Icons
Use react-icons to generate SVG icons, then rasterize to PNG for universal compatibility.
### Setup
```javascript
const React = require("react");
const ReactDOMServer = require("react-dom/server");
const sharp = require("sharp");
const { FaCheckCircle, FaChartLine } = require("react-icons/fa");
function renderIconSvg(IconComponent, color = "#000000", size = 256) {
return ReactDOMServer.renderToStaticMarkup(
React.createElement(IconComponent, { color, size: String(size) })
);
}
async function iconToBase64Png(IconComponent, color, size = 256) {
const svg = renderIconSvg(IconComponent, color, size);
const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer();
return "image/png;base64," + pngBuffer.toString("base64");
}
```
### Add Icon to Slide
```javascript
const iconData = await iconToBase64Png(FaCheckCircle, "#4472C4", 256);
slide.addImage({
data: iconData,
x: 1, y: 1, w: 0.5, h: 0.5 // Size in inches
});
```
**Note**: Use size 256 or higher for crisp icons. The size parameter controls the rasterization resolution, not the display size on the slide (which is set by `w` and `h` in inches).
### Icon Libraries
Install: `npm install -g react-icons react react-dom sharp`
Popular icon sets in react-icons:
- `react-icons/fa` - Font Awesome
- `react-icons/md` - Material Design
- `react-icons/hi` - Heroicons
- `react-icons/bi` - Bootstrap Icons
---
## Slide Backgrounds
```javascript
// Solid color
slide.background = { color: "F1F1F1" };
// Color with transparency
slide.background = { color: "FF3399", transparency: 50 };
// Image from URL
slide.background = { path: "https://example.com/bg.jpg" };
// Image from base64
slide.background = { data: "image/png;base64,iVBORw0KGgo..." };
```
---
## Tables
```javascript
slide.addTable([
["Header 1", "Header 2"],
["Cell 1", "Cell 2"]
], {
x: 1, y: 1, w: 8, h: 2,
border: { pt: 1, color: "999999" }, fill: { color: "F1F1F1" }
});
// Advanced with merged cells
let tableData = [
[{ text: "Header", options: { fill: { color: "6699CC" }, color: "FFFFFF", bold: true } }, "Cell"],
[{ text: "Merged", options: { colspan: 2 } }]
];
slide.addTable(tableData, { x: 1, y: 3.5, w: 8, colW: [4, 4] });
```
---
## Charts
```javascript
// Bar chart
slide.addChart(pres.charts.BAR, [{
name: "Sales", labels: ["Q1", "Q2", "Q3", "Q4"], values: [4500, 5500, 6200, 7100]
}], {
x: 0.5, y: 0.6, w: 6, h: 3, barDir: 'col',
showTitle: true, title: 'Quarterly Sales'
});
// Line chart
slide.addChart(pres.charts.LINE, [{
name: "Temp", labels: ["Jan", "Feb", "Mar"], values: [32, 35, 42]
}], { x: 0.5, y: 4, w: 6, h: 3, lineSize: 3, lineSmooth: true });
// Pie chart
slide.addChart(pres.charts.PIE, [{
name: "Share", labels: ["A", "B", "Other"], values: [35, 45, 20]
}], { x: 7, y: 1, w: 5, h: 4, showPercent: true });
```
### Better-Looking Charts
Default charts look dated. Apply these options for a modern, clean appearance:
```javascript
slide.addChart(pres.charts.BAR, chartData, {
x: 0.5, y: 1, w: 9, h: 4, barDir: "col",
// Custom colors (match your presentation palette)
chartColors: ["0D9488", "14B8A6", "5EEAD4"],
// Clean background
chartArea: { fill: { color: "FFFFFF" }, roundedCorners: true },
// Muted axis labels
catAxisLabelColor: "64748B",
valAxisLabelColor: "64748B",
// Subtle grid (value axis only)
valGridLine: { color: "E2E8F0", size: 0.5 },
catGridLine: { style: "none" },
// Data labels on bars
showValue: true,
dataLabelPosition: "outEnd",
dataLabelColor: "1E293B",
// Hide legend for single series
showLegend: false,
});
```
**Key styling options:**
- `chartColors: [...]` - hex colors for series/segments
- `chartArea: { fill, border, roundedCorners }` - chart background
- `catGridLine/valGridLine: { color, style, size }` - grid lines (`style: "none"` to hide)
- `lineSmooth: true` - curved lines (line charts)
- `legendPos: "r"` - legend position: "b", "t", "l", "r", "tr"
---
## Slide Masters
```javascript
pres.defineSlideMaster({
title: 'TITLE_SLIDE', background: { color: '283A5E' },
objects: [{
placeholder: { options: { name: 'title', type: 'title', x: 1, y: 2, w: 8, h: 2 } }
}]
});
let titleSlide = pres.addSlide({ masterName: "TITLE_SLIDE" });
titleSlide.addText("My Title", { placeholder: "title" });
```
---
## Common Pitfalls
These issues cause file corruption, visual bugs, or broken output. Avoid them.
1. **NEVER use "#" with hex colors** - causes file corruption
```javascript
color: "FF0000" // CORRECT
color: "#FF0000" // WRONG
```
2. **NEVER encode opacity in hex color strings** - 8-char colors (e.g., `"00000020"`) corrupt the file. Use the `opacity` property instead.
```javascript
shadow: { type: "outer", blur: 6, offset: 2, color: "00000020" } // CORRUPTS FILE
shadow: { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.12 } // CORRECT
```
3. **Use `bullet: true`** - NEVER unicode symbols like "o" (creates double bullets)
4. **Use `breakLine: true`** between array items or text runs together
5. **Avoid `lineSpacing` with bullets** - causes excessive gaps; use `paraSpaceAfter` instead
6. **Each presentation needs fresh instance** - don't reuse `pptxgen()` objects
7. **NEVER reuse option objects across calls** - PptxGenJS mutates objects in-place (e.g. converting shadow values to EMU). Sharing one object between multiple calls corrupts the second shape.
```javascript
const shadow = { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 };
slide.addShape(pres.shapes.RECTANGLE, { shadow, ... }); // second call gets already-converted values
slide.addShape(pres.shapes.RECTANGLE, { shadow, ... });
const makeShadow = () => ({ type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 });
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... }); // fresh object each time
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... });
```
8. **Don't use `ROUNDED_RECTANGLE` with accent borders** - rectangular overlay bars won't cover rounded corners. Use `RECTANGLE` instead.
```javascript
// WRONG: Accent bar doesn't cover rounded corners
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, { x: 1, y: 1, w: 3, h: 1.5, fill: { color: "FFFFFF" } });
slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 0.08, h: 1.5, fill: { color: "0891B2" } });
// CORRECT: Use RECTANGLE for clean alignment
slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 3, h: 1.5, fill: { color: "FFFFFF" } });
slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 0.08, h: 1.5, fill: { color: "0891B2" } });
```
---
## Quick Reference
- **Shapes**: RECTANGLE, OVAL, LINE, ROUNDED_RECTANGLE
- **Charts**: BAR, LINE, PIE, DOUGHNUT, SCATTER, BUBBLE, RADAR
- **Layouts**: LAYOUT_16x9 (10"x5.625"), LAYOUT_16x10, LAYOUT_4x3, LAYOUT_WIDE
- **Alignment**: "left", "center", "right"
- **Chart data labels**: "outEnd", "inEnd", "center"

View File

@@ -0,0 +1,240 @@
# Presenter Mode Guide · 演讲者模式指南
这份文档说明如何在 html-ppt skill 里做出**带逐字稿的演讲者模式 PPT**。
## 何时使用演讲者模式
当用户的需求涉及以下任何一项时,**优先使用演讲者模式**
- 提到"**演讲**"、"**分享**"、"**讲稿**"、"**逐字稿**"、"**speaker notes**"
- 提到"**presenter view**"、"**演讲者视图**"、"**演讲者模式**"
- 需要"**30 分钟 / 45 分钟 / 1 小时**的分享"
- 说"我要去给团队讲 xxx"、"要做一场技术分享"、"要做路演"
- 强调"**不想忘词**"、"**怕讲不流畅**"、"**需要提词器**"
如果用户只要做一份"静态好看的 PPT"(例如小红书图文、产品图册、汇报 slides 自己不讲),**不需要**演讲者模式。
## 两种做法
### ✅ 推荐做法:直接用 `presenter-mode-reveal` 模板
```bash
cp -r templates/full-decks/presenter-mode-reveal examples/my-talk
```
这个模板已经预设好所有必需元素:
- 支持 S 键切换演讲者视图
- 5 个主题可用 T 键循环tokyo-night / dracula / catppuccin-mocha / nord / corporate-clean
- 左右键翻页
- 每一页都有 150300 字的示例逐字稿
- 底部有键位提示
直接改内容即可。
### 🔧 进阶做法:给任意已有模板加演讲者模式
html-ppt 的 **S 键演讲者视图是 `runtime.js` 内置的,所有 full-deck 模板都自动支持**。你只需要做两件事:
1. **每张 slide 末尾加 `<aside class="notes">`**(或 `<div class="notes">`),里面写逐字稿
2. **确认 HTML 引入了 `assets/runtime.js`**
```html
<section class="slide">
<h2>你的标题</h2>
<p>内容...</p>
<aside class="notes">
<p>这里是演讲时要说的话150-300 字...</p>
</aside>
</section>
```
## 逐字稿写作三铁律
这是整个方法论的核心。AI 在帮用户写逐字稿时必须遵守:
### 铁律 1不是讲稿是"提示信号"
**错误写法**(像在念稿):
```
大家好,欢迎来到今天的分享。今天我将要给大家介绍一下我们团队在过去三个月做的工作。
首先,我们来看一下背景情况。在过去的三个月中,我们遇到了以下几个问题……
```
**正确写法**(提示信号 + 加粗核心):
```
<p>欢迎!今天分享我们团队<strong>过去 3 个月</strong>的工作。</p>
<p>先说<em>背景</em>——三个月前我们遇到了<strong>三个核心问题</strong>
延迟高、成本炸、稳定性差。</p>
<p>接下来逐个讲解怎么解的。</p>
```
**差别**:正确版本把关键词加粗,过渡句独立成段,看一眼就能接上。
### 铁律 2每页 150300 字
- **少于 150 字**:提示不够,讲到一半会卡
- **多于 300 字**:你根本来不及扫完
- **23 分钟/页** 是最舒服的节奏
### 铁律 3用口语不用书面语
| ❌ 书面语 | ✅ 口语 |
|---|---|
| 因此 | 所以 |
| 该方案 | 这个方案 |
| 然而 | 但是 / 不过 |
| 进行优化 | 优化一下 |
| 我们将会 | 我们会 / 接下来 |
| 综上所述 | 所以简单来说 |
**检查方法**:写完读一遍,听起来像说话才对。
## 必备 HTML 结构
```html
<!DOCTYPE html>
<html lang="zh-CN" data-themes="tokyo-night,dracula,corporate-clean">
<head>
<meta charset="utf-8">
<title>...</title>
<link rel="stylesheet" href="../../../assets/fonts.css">
<link rel="stylesheet" href="../../../assets/base.css">
<link rel="stylesheet" id="theme-link" href="../../../assets/themes/tokyo-night.css">
<link rel="stylesheet" href="../../../assets/animations/animations.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="deck">
<section class="slide" data-title="Cover">
<h1>你的标题</h1>
<p>副标题</p>
<aside class="notes">
<p>讲稿段落 1<strong>加粗关键词</strong>)。</p>
<p>讲稿段落 2过渡句独立成段</p>
<p>讲稿段落 3自然收尾引出下一页</p>
</aside>
</section>
<!-- 更多 slide ... -->
</div>
<script src="../../../assets/runtime.js"></script>
</body>
</html>
```
## 演讲者视图显示的内容
`S` 键后,**弹出一个独立的演讲者窗口**(原页面保持观众视图不变)。演讲者窗口是 **4 个独立的磁吸卡片**
```
观众窗口(原页面) 演讲者窗口(磁吸卡片)
┌─────────────────┐ ┌─────────────────────┬──────────────────┐
│ │ │ 🔵 CURRENT │ 🟣 NEXT │
│ 正常 slide │ │ ━━━━━━━━━━━━━━━━ │ ━━━━━━━━━━━━━ │
│ 全屏展示 │◄►│ │ iframe preview │
│ │ │ iframe preview │ (下一页) │
│ │ │ (当前页) ├──────────────────┤
│ │ │ │ 🟠 SPEAKER SCRIPT │
│ │ │ │ ━━━━━━━━━━━━━ │
│ │ ├─────────────────────┤ [大字号逐字稿] │
│ │ │ 🟢 TIMER │ [可滚动] │
│ │ │ ⏱ 12:34 3 / 8 │ │
│ │ │ [← Prev][Next →] │ │
└─────────────────┘ └─────────────────────┴──────────────────┘
↑ BroadcastChannel 双向同步翻页 ↑
```
卡片交互规则:
- **拖动卡片 header**(带彩色圆点和标题的顶部条)→ 移动卡片位置
- **拖动卡片右下角的三角手柄** → 调整卡片大小
- **位置/尺寸自动保存到 localStorage**,下次打开恢复
- 底部 "重置布局" 按钮恢复默认排列
卡片内容:
- 🔵 **CURRENT** — 当前页 **像素级完美预览**iframe 加载原 HTML 文件的 `?preview=N` 模式,错色不可能)
- 🟣 **NEXT** — 下一页预览,同样像素级完美
- 🟠 **SPEAKER SCRIPT** — 逐字稿,字号 18px支持 `<strong>` (橘色加粗)、`<em>` (蓝色强调)、`<code>` 等 inline 样式
- 🟢 **TIMER** — 计时器不会丢失焦点,带切页按钮
两窗口同步:在任一窗口按 ← → 翻页另一个窗口自动同步BroadcastChannel
丝滑翻页iframe 只加载一次,后续翻页用 `postMessage` 切换可见的 slide**不重新加载、不闪烁**。
## 键盘快捷键(演讲者模式)
| 键 | 动作 |
|---|---|
| `S` | 打开演讲者窗口(弹出新窗口,原页面保持观众视图) |
| `←` `→` / Space / PgDn | 翻页(即使在演讲者视图里) |
| `T` | 切换主题 |
| `R` | 重置计时器(仅演讲者视图下) |
| `F` | 全屏 |
| `O` | 总览 |
| `Esc` | 关闭所有浮层 |
## 双屏演讲的标准流程
1. 打开 `index.html`,按 `S` → 弹出演讲者窗口
2. 把**观众窗口**(原页面)拖到投影 / 外接屏,按 `F` 全屏
3. 把**演讲者窗口**(弹窗)留在你面前的屏幕
4. 在任一窗口按 ← → 翻页,两边自动同步
5. 演讲者窗口里看逐字稿 + 下一页 + 计时器
> 💡 **为什么预览像素级完美**:每个预览是一个 `<iframe>`,它加载的就是同一个 deck HTML 文件,只是 URL 多了 `?preview=N` 参数。`runtime.js` 检测到这个参数时只渲染第 N 页、隐藏所有 chrome。**iframe 使用与观众视图完全相同的 CSS、主题、字体和 viewport**——颜色和排版保证一致。外层用 CSS `transform: scale()` 把 1920×1080 缩到卡片宽高,等比缩放不变形。
> 💡 **为什么不闪烁**iframe 初次加载后就常驻,翻页时 presenter 窗口通过 `postMessage({type:'preview-goto', idx:N})` 告诉 iframe 切换到第 N 页。iframe 内的 runtime.js 只切换 `.is-active` class**不重新加载、不渲染白屏**。
## 常见错误
### ❌ 把逐字稿写在 slide 可见位置
```html
<!-- 错误:这段文字观众会看到 -->
<p style="font-size:12px;color:gray">
这里讲 xxx然后讲 yyy...
</p>
```
✅ 正确:
```html
<aside class="notes">
<p>这里讲 xxx然后讲 yyy...</p>
</aside>
```
`.notes` 类默认 `display:none`,只在演讲者视图可见。
### ❌ 忘记引入 runtime.js
没有 `<script src="../../../assets/runtime.js"></script>` = 没有 S 键、没有演讲者视图、没有翻页。
### ❌ 逐字稿用书面语
念出来像 AI 机器人。**写完一定读一遍**。
### ❌ 每页 50 字
提示不够,照样忘词。
### ❌ 每页 500 字
眼睛根本扫不过来,等于没写。
## 用 AI 生成逐字稿的标准 prompt
> "请为每一张 slide 写一段 **150-300 字**的逐字稿,放在 `<aside class="notes">` 里。
> 要求:
> 1. 用**口语**,不要书面语(所以/但是/接下来,不是因此/然而/综上所述)
> 2. 把**核心关键词**用 `<strong>` 加粗
> 3. 过渡句独立成段(每段 1-3 句)
> 4. 读起来像说话,不像念稿
> 5. 结尾要有自然的过渡,引出下一页"
## 推荐搭配
- **主题**`tokyo-night`(深色,技术分享首选)、`corporate-clean`(浅色,商务汇报)、`dracula`(深色备选)
- **字体**:默认 Noto Sans SC + JetBrains Mono无需更改
- **动效**:克制使用,`fade-up` / `rise-in` 最自然,不要用 `glitch-in` / `confetti-burst` 之类花哨的
- **页数**30 分钟分享 = 812 页45 分钟 = 1216 页1 小时 = 1622 页

View File

@@ -0,0 +1,36 @@
# Annotation Callout (italic-serif aside)
Use for editorial asides — the "italic pointer" that marks a detail without competing with the primary diagram grammar. Think marginalia: *"structure IS the index"*, *"no imports, no configuration"*.
## Grammar
```svg
<!-- 1. Italic Instrument Serif text -->
<text x="904" y="36" fill="#0b0d0b" font-size="14" font-style="italic"
font-family="'Instrument Serif', serif" text-anchor="end">no imports, no configuration</text>
<!-- 2. Dashed Bézier leader -->
<path d="M 820 44 Q 700 84 520 216" fill="none"
stroke="rgba(11,13,11,0.40)" stroke-width="1" stroke-dasharray="4,3"/>
<!-- 3. Landing dot -->
<circle cx="520" cy="216" r="2" fill="#0b0d0b"/>
```
## Rules
- Italic + serif together signal "editorial voice" against the diagram's sans/mono body. Don't substitute italic sans or italic mono — the combination is load-bearing.
- Dashed path (`stroke-dasharray="4,3"`) distinguishes the callout leader from primary arrows (which are solid).
- Place callouts in margins (top-right, bottom-left). Never inside the active diagram area.
- Max 2 callouts per diagram. More becomes commentary, not signal.
## Colors
| Intent | Text | Leader |
|---|---|---|
| Neutral aside | ink `#0b0d0b` | `rgba(11,13,11,0.40)` |
| Focal / accent | coral `#f7591f` | `rgba(247,89,31,0.50)` |
| Tertiary (muted) | muted `#52534e` | `rgba(11,13,11,0.30)` |
## Anti-patterns
- Solid arrow leader (reads as a flow arrow).
- Italic sans or italic mono — the serif is load-bearing.
- Callouts crossing primary arrows / lifelines — offset to a clear margin.
- Using a callout to label something the diagram should label directly — put the label on the element.

View File

@@ -0,0 +1,43 @@
# Sketchy Filter (hand-drawn variant)
Optional displacement filter that wobbles every stroke and edge slightly — turns any minimal variant into a hand-drawn "editorial" register without changing layout. Use when the diagram accompanies an essay rather than technical docs.
## Grammar
```svg
<defs>
<filter id="sketchy" x="-2%" y="-2%" width="104%" height="104%">
<feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="2" seed="4"/>
<feDisplacementMap in="SourceGraphic" scale="1.5"/>
</filter>
</defs>
<!-- Apply to a group wrapping shapes — NOT text -->
<g filter="url(#sketchy)">
<!-- rects, paths, circles, lines go here -->
</g>
<!-- Text sits OUTSIDE the filtered group — legibility stays crisp -->
<text ...>Labels go here</text>
```
## Tuning
| Parameter | Range | Effect |
|---|---|---|
| `baseFrequency` | 0.010.04 | Lower = lazy wavy lines; higher = jittery. 0.02 default. |
| `numOctaves` | 13 | More = more noise detail. 2 is plenty. |
| `scale` | 16 | 1 barely-there, 1.5 default, 2 visible, 4+ cartoon. |
| `seed` | integer | Swap for a different random pattern. |
## Critical rule
Filter shapes, NOT text. Displacement-mapped text becomes illegible. Structure your SVG so text is in a sibling group outside the filtered group.
## When to use
- Essay / blog post / newsletter where the diagram is the hero of a narrative page.
- "Working sketch" register — showing something is mid-thought, not final architecture.
## When not to use
- Technical documentation (precision matters).
- Diagrams with dense labels or tight alignments (filter reads as noise).
- Dark variants — wobble reads as artifact on dark backgrounds. Test first.

View File

@@ -0,0 +1,203 @@
# Primitive Tokens
Raw design values - foundation of the design system.
## Color Scales
### Gray Scale
```css
:root {
--color-gray-50: #F9FAFB;
--color-gray-100: #F3F4F6;
--color-gray-200: #E5E7EB;
--color-gray-300: #D1D5DB;
--color-gray-400: #9CA3AF;
--color-gray-500: #6B7280;
--color-gray-600: #4B5563;
--color-gray-700: #374151;
--color-gray-800: #1F2937;
--color-gray-900: #111827;
--color-gray-950: #030712;
}
```
### Primary Colors (Blue)
```css
:root {
--color-blue-50: #EFF6FF;
--color-blue-100: #DBEAFE;
--color-blue-200: #BFDBFE;
--color-blue-300: #93C5FD;
--color-blue-400: #60A5FA;
--color-blue-500: #3B82F6;
--color-blue-600: #2563EB;
--color-blue-700: #1D4ED8;
--color-blue-800: #1E40AF;
--color-blue-900: #1E3A8A;
}
```
### Status Colors
```css
:root {
/* Success - Green */
--color-green-500: #22C55E;
--color-green-600: #16A34A;
/* Warning - Yellow */
--color-yellow-500: #EAB308;
--color-yellow-600: #CA8A04;
/* Error - Red */
--color-red-500: #EF4444;
--color-red-600: #DC2626;
/* Info - Blue */
--color-info: var(--color-blue-500);
}
```
## Spacing Scale
4px base unit system.
```css
:root {
--space-0: 0;
--space-px: 1px;
--space-0-5: 0.125rem; /* 2px */
--space-1: 0.25rem; /* 4px */
--space-1-5: 0.375rem; /* 6px */
--space-2: 0.5rem; /* 8px */
--space-2-5: 0.625rem; /* 10px */
--space-3: 0.75rem; /* 12px */
--space-3-5: 0.875rem; /* 14px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-7: 1.75rem; /* 28px */
--space-8: 2rem; /* 32px */
--space-9: 2.25rem; /* 36px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-14: 3.5rem; /* 56px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
}
```
## Typography Scale
```css
:root {
/* Font Sizes */
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.875rem; /* 14px */
--font-size-base: 1rem; /* 16px */
--font-size-lg: 1.125rem; /* 18px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 1.875rem; /* 30px */
--font-size-4xl: 2.25rem; /* 36px */
--font-size-5xl: 3rem; /* 48px */
/* Line Heights */
--leading-none: 1;
--leading-tight: 1.25;
--leading-snug: 1.375;
--leading-normal: 1.5;
--leading-relaxed: 1.625;
--leading-loose: 2;
/* Font Weights */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
/* Letter Spacing */
--tracking-tighter: -0.05em;
--tracking-tight: -0.025em;
--tracking-normal: 0;
--tracking-wide: 0.025em;
--tracking-wider: 0.05em;
}
```
## Border Radius
```css
:root {
--radius-none: 0;
--radius-sm: 0.125rem; /* 2px */
--radius-default: 0.25rem; /* 4px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
--radius-3xl: 1.5rem; /* 24px */
--radius-full: 9999px;
}
```
## Shadows
```css
:root {
--shadow-none: none;
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--shadow-default: 0 1px 3px 0 rgb(0 0 0 / 0.1),
0 1px 2px -1px rgb(0 0 0 / 0.1);
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
--shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25);
--shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
}
```
## Motion / Duration
```css
:root {
--duration-75: 75ms;
--duration-100: 100ms;
--duration-150: 150ms;
--duration-200: 200ms;
--duration-300: 300ms;
--duration-500: 500ms;
--duration-700: 700ms;
--duration-1000: 1000ms;
/* Semantic durations */
--duration-fast: var(--duration-150);
--duration-normal: var(--duration-200);
--duration-slow: var(--duration-300);
}
```
## Z-Index Scale
```css
:root {
--z-auto: auto;
--z-0: 0;
--z-10: 10;
--z-20: 20;
--z-30: 30;
--z-40: 40;
--z-50: 50;
--z-dropdown: 1000;
--z-sticky: 1100;
--z-modal: 1200;
--z-popover: 1300;
--z-tooltip: 1400;
}
```

View File

@@ -0,0 +1,244 @@
# Privacy & Security Guidelines
Security best practices and privacy requirements for Android applications.
## Permissions
### Principle of Least Privilege
| Rule | Implementation |
|------|----------------|
| Request minimum | Only permissions essential for core features |
| Request when needed | At point of use, not app startup |
| Explain why | Show rationale before system dialog |
| Degrade gracefully | App works (limited) if denied |
### Permission Request Flow
1. Check if already granted
2. If not, show educational UI (rationale)
3. Request permission
4. Handle result (grant or denial)
5. If denied, offer alternative or reduced functionality
### Sensitive Permissions
| Permission | Consideration |
|------------|---------------|
| Location | Use coarse if fine not needed |
| Camera | Request only when capturing |
| Microphone | Request only when recording |
| Contacts | Consider contact picker intent |
| Storage | Use scoped storage |
| SMS/Call Log | Restricted, needs approval |
### Alternative Approaches
| Instead of... | Consider... |
|---------------|-------------|
| READ_CONTACTS | Contact picker intent |
| ACCESS_FINE_LOCATION | Coarse location |
| READ_EXTERNAL_STORAGE | Storage Access Framework |
| CAMERA | Camera intent |
## Data Storage
### Storage Types
| Type | Security | Usage |
|------|----------|-------|
| Internal storage | Private to app | Sensitive data |
| External storage | World-readable | Shared files only |
| SharedPreferences | Private, unencrypted | Non-sensitive settings |
| EncryptedSharedPreferences | Private, encrypted | Sensitive settings |
| Room database | Private, optional encryption | Structured data |
### Sensitive Data Rules
| Rule | Implementation |
|------|----------------|
| Store internally | Use internal storage, not external |
| Encrypt at rest | Use EncryptedSharedPreferences, SQLCipher |
| Don't log | Never log PII or credentials |
| Clear on logout | Wipe user data completely |
### Data Logging
Never log sensitive data such as passwords, emails, tokens, or personal information. Only log non-sensitive operational information.
## Network Security
### HTTPS Requirements
- All network traffic must use SSL/TLS
- Configure Network Security Config
- Don't allow cleartext traffic
### Network Security Config
Define a network security configuration that:
- Disables cleartext traffic
- Specifies trusted certificate authorities
- Optionally implements certificate pinning for high-security apps
### Certificate Pinning (Optional)
For high-security apps, pin certificates to prevent MITM attacks. Include backup pins and plan for certificate rotation.
## User Identity
### Credential Manager
Integrate Credential Manager for unified sign-in supporting:
- Passkeys
- Federated identity
- Traditional passwords
### Biometric Authentication
Use biometric authentication for sensitive operations like:
- Financial transactions
- Accessing sensitive documents
- Confirming identity
### Autofill Support
Provide autofill hints on input fields:
- emailAddress, username for identity fields
- password for credential fields
- creditCardNumber, postalCode for payment fields
## App Components Security
### Exported Components
| Component | Exported Rule |
|-----------|---------------|
| Launcher Activity | exported="true" with intent-filter |
| Internal Activity | exported="false" |
| Internal Service | exported="false" |
| Content Provider (shared) | exported="true" with permissions |
Always explicitly set the exported attribute on all components.
### Custom Permissions
Use signature-level protection for custom permissions that control access between your own apps.
### Intent Validation
- Validate all intent data before use
- Check URI scheme and host
- Use explicit intents when possible
- Don't trust extras from unknown sources
### PendingIntent Security
Use FLAG_IMMUTABLE for PendingIntents unless mutability is required. This prevents other apps from modifying the intent.
## WebView Security
### Safe WebView Configuration
| Setting | Recommendation |
|---------|----------------|
| JavaScript | Disabled unless required |
| File access | Disabled |
| Content access | Disabled |
| Universal file access | Never enable |
### Avoid Dangerous Practices
| Don't | Why |
|-------|-----|
| setAllowUniversalAccessFromFileURLs(true) | Security vulnerability |
| addJavascriptInterface() with untrusted content | Code injection risk |
| Load untrusted URLs | XSS, phishing |
## Cryptography
### Use Platform APIs
- Use Android Keystore for key storage
- Use standard algorithms (AES-GCM, RSA)
- Never implement custom cryptography
- Use SecureRandom for random generation
### Avoid
- Custom encryption implementations
- Weak algorithms (MD5, SHA1 for security)
- Hardcoded keys or secrets
- Non-cryptographic random generators
## Code Security
### No Dynamic Code Loading
| Don't | Do Instead |
|-------|------------|
| Load code at runtime | Android App Bundles |
| Download DEX files | Play Feature Delivery |
| Execute scripts | Predefined functionality |
### Debug Code Removal
- Set debuggable=false in release builds
- Enable minification (R8/ProGuard)
- Remove debug libraries from production
## Device Identifiers
### Don't Use Hardware IDs
| Identifier | Status |
|------------|--------|
| IMEI | Don't use |
| MAC address | Don't use |
| Serial number | Don't use |
| Android ID | Limited use only |
### Recommended Alternatives
| Use Case | Solution |
|----------|----------|
| Analytics | Firebase Analytics ID |
| Advertising | Advertising ID (resettable) |
| App instance | Generate UUID on install |
| User identity | Account-based ID |
## Google Play Policies
### Data Safety
- Declare all data collected
- Explain data usage
- Provide privacy policy
- Allow data deletion requests
### User Data Policy
| Rule | Requirement |
|------|-------------|
| Transparency | Clear disclosure of data use |
| Security | Protect user data appropriately |
| Minimization | Collect only what's needed |
| Control | Allow users to manage data |
## Security Checklist
- [ ] Permissions requested only when needed
- [ ] Permissions explained to user
- [ ] Sensitive data stored internally
- [ ] No sensitive data in logs
- [ ] All network traffic over HTTPS
- [ ] Network security config defined
- [ ] Components export status explicit
- [ ] Custom permissions use signature protection
- [ ] Intents validated before use
- [ ] PendingIntents use FLAG_IMMUTABLE
- [ ] WebView configured securely
- [ ] Platform crypto APIs used
- [ ] No debug code in production
- [ ] No hardware IDs used
- [ ] Privacy policy available

View File

@@ -0,0 +1,179 @@
# Pre-Project Questions
## Creative Brief Questions
ใช้เป็นแนวทางถามคำถามลูกค้าก่อนเริ่มสร้างเว็บไซต์
เน้นว่า "ทำไม" ไม่ใช่แค่ "ทำอะไร"
---
## 1. ข้อมูลพื้นฐาน
1.1 **ชื่อเว็บไซต์/บริษัท**
- ชื่อที่จะแสดงบนเว็บ
- ชื่อในเอกสาร (ถ้าต่างจากชื่อบนเว็บ)
1.2 **ทำอะไร?**
- ขายสินค้าอะไร?
- ให้บริการอะไร?
- มี unique selling point อะไร?
---
## 2. ทำไมต่างจากคนอื่น?
2.1 **ทำไมลูกค้าควรเลือกคุณ ไม่ใช่คู่แข่ง?**
- คู่แข่งของคุณคือใครบ้าง?
- คุณต่างจากเขาอย่างไร?
2.2 **มี brand story หรือ origin story อะไรที่น่าสนใจไหม?**
- ทำไมถึงเริ่มทำสิ่งนี้?
- มีจุดเริ่มต้นที่น่าสนใจไหม?
2.3 **มี values หรือ beliefs อะไรที่คุณยึดถือไหม?**
- สิ่งที่คุณทำ/ไม่ทำเพราะอะไร?
---
## 3. กลุ่มเป้าหมาย + Emotions
3.1 **กลุ่มลูกค้าเป้าหมาย**
- อายุ, เพศ, อาชีพ
- Pain points หลักของพวกเขาคืออะไร?
- พวกเขากำลัง looking for อะไรอยู่?
3.2 **ต้องการให้คนเข้าเว็บแล้ว "รู้สึก" อย่างไร?**
- [ ] มั่นใจ / เชื่อถือได้
- [ ] สบายใจ / ไม่กังวล
- [ ] ตื่นเต้น / กระตือรือร้น
- [ ] ประทับใจ / หรูหรา
- [ ] เข้าใจง่าย / ชัดเจน
- [ ] ทันสมัย / นำหน้า
- [ ] อื่นๆ: ____________
3.3 **มีภาพที่ไม่อยากให้เว็บสื่อสารไหม?**
- เช่น: ธรรมดาเกินไป, ดูถูกเบา, เก่าจัง, ตลก, ไม่น่าเชื่อถือ
---
## 4. Design Context
4.1 **มี brand guidelines/logo files ไหม?**
- ถ้ามี: แชร์มาได้เลย
- Logo files (SVG, PNG, AI)
- สีที่ใช้ (hex codes)
- Typography ที่ใช้
4.2 **มี reference sites ที่ชอบ/ไม่ชอบบ้างไหม?**
- ชอบ: URL + บอกว่าชอบอะไรจากเว็บนั้น
- ไม่ชอบ: URL + บอกว่าไม่ชอบอะไร
4.3 **มีสีที่ห้ามใช้/ต้องใช้ไหม?**
- สีที่ต้องใช้ (ถ้ามี brand colors)
- สีที่ไม่ชอบ/ห้ามใช้
4.4 **ชอบ Dark Mode, Light Mode หรือทั้งสอง?**
- Light mode อย่างเดียว
- Dark mode อย่างเดียว
- ทั้งสอง (user เลือกได้)
---
## 5. หน้าที่ต้องการ
5.1 **ต้องการหน้าอะไรบ้าง?**
- [ ] Home
- [ ] About Us / บริษัทของเรา
- [ ] Services / บริการ
- [ ] Products / สินค้า
- [ ] Portfolio / ผลงาน
- [ ] Blog / ข่าวสาร
- [ ] Contact / ติดต่อ
- [ ] FAQ / คำถามที่พบบ่อย
- [ ] Careers / ร่วมงานกับเรา
- [ ] Other: ____________
5.2 **มีฟอร์มที่ต้องการไหม?**
- [ ] Contact Form
- [ ] Quote Request Form
- [ ] Newsletter Signup
- [ ] Booking Form
- [ ] Registration Form
- [ ] Other: ____________
---
## 6. Technical Requirements
6.1 **มี SMTP/Email server ไหม?**
- สำหรับส่ง email จากเว็บ
- เช่น contact form, newsletter
6.2 **มี Tracking tools ต้องติดตั้งไหม?**
- Google Analytics 4 (GA4)
- Facebook Pixel
- Google Ads Conversion
- TikTok Pixel
- LINE Channel Tag
- Umami / Plausible
- Microsoft Clarity
- Other: ____________
6.3 **มี Third-party integrations ไหม?**
- Payment gateways
- CRM systems
- Other APIs
---
## 7. PDPA Compliance
7.1 **มี DPO (Data Protection Officer) หรือยัง?**
- ถ้ายัง: ต้องการให้ช่วยจัดหาไหม?
7.2 **เว็บจะเก็บข้อมูลอะไรบ้าง?**
- ข้อมูลลูกค้า
- ข้อมูลการสั่งซื้อ
- Newsletter subscribers
- Other: ____________
7.3 **ConsentOS Integration**
- ใช้ ConsentOS สำหรับ cookie consent (บังคับ PDPA)
---
## 8. เว็บไซต์เดิม (ถ้ามี)
8.1 **มีเว็บอยู่แล้วหรือยัง?**
- ถ้ามี: URL ของเว็บเก่า
- ถ้ามี: ทำไมอยากเปลี่ยน?
- ถ้ามี: มี source code ไหม?
8.2 **มี domain และ hosting แล้วหรือยัง?**
- ถ้ามี: provider อะไร?
- ถ้ามี: domain ชื่ออะไร?
---
## Checklist สำหรับ Website Creator
เมื่อถามครบแล้ว ต้องได้:
- [ ] ชื่อเว็บ/บริษัท
- [ ] ธุรกิจทำอะไร + ทำไมต่าง
- [ ] Target emotion ที่ต้องการ
- [ ] Design references (ชอบ/ไม่ชอบ)
- [ ] Brand assets (logo, colors) ถ้ามี
- [ ] Sitemap (หน้าที่ต้องการ)
- [ ] Tracking tools ที่ต้องติดตั้ง
- [ ] PDPA requirements
---
## หลังได้ Context แล้ว
1. สร้าง **Creative Brief** (ฉบับย่อ)
2. สร้าง **2-3 Creative Directions** ที่ต่างกันจริงๆ
3. นำเสนอ + รอ user เลือก
4. สร้าง **Master Plan** รวมทั้งหมด
5. ⏸️ **รอ APPROVE** ก่อนทำงาน

Some files were not shown because too many files have changed in this diff Show More