229 lines
11 KiB
Markdown
229 lines
11 KiB
Markdown
---
|
||
|
||
name: tweaks
|
||
description: |
|
||
将任意 HTML 制品包裹在一个侧面板中,提供实时的、参数化的控件——强调色、字号比例、密度、动效、主题——这些控件会实时改写 CSS 自定义属性,并持久化到 localStorage。让用户可以在不重新提示 agent 的情况下探索设计的各种变体。当需求中提到「变体」「并排选项」「调一下」「让我调整」「实时 knob」「实时调参」时使用。
|
||
triggers:
|
||
- "tweaks"
|
||
- "variants"
|
||
- "tweak panel"
|
||
- "live controls"
|
||
- "adjust on the fly"
|
||
- "实时调参"
|
||
- "可调参数面板"
|
||
- "side panel"
|
||
- "knobs"
|
||
od:
|
||
mode: prototype
|
||
platform: desktop
|
||
scenario: design
|
||
upstream: "https://github.com/alchaincyf/huashu-design"
|
||
preview:
|
||
type: html
|
||
entry: index.html
|
||
design_system:
|
||
requires: false
|
||
example_prompt: "Wrap this landing page with a tweak panel — accent color, type scale, density, light/dark — persist to localStorage so the user can refresh without losing their choice."
|
||
|
||
---
|
||
|
||
# Tweaks Skill · 参数化变体面板
|
||
|
||
将任意 HTML 制品包裹在一个侧面板中,提供实时控件来改写 CSS 自定义属性,并持久化到 `localStorage`。灵感来自 *huashu-design* 的调参模式。
|
||
|
||
## 产出内容
|
||
|
||
一个自包含的单一 HTML 文件,包含两层:
|
||
|
||
1. **舞台(Stage)**——原始制品(着陆页 / 幻灯片 / 仪表板),经过改写,使所有视觉决策都从 CSS 自定义属性中读取:`--accent`、`--scale`、`--density`、`--mode`、`--motion`。
|
||
2. **面板(Panel)**——一个固定侧边栏(小视口下为抽屉式),包含绑定到这些自定义属性的表单控件,通过一个极简的 vanilla-JS 桥接层实现。每次更改都会按制品标识持久化到 `localStorage`。
|
||
|
||
用户可以:
|
||
|
||
- 打开制品,看到舞台以其保存的偏好(或合理的默认值)呈现。
|
||
- 在面板中调整强调色 / 字号比例 / 密度 / 模式 / 动效,舞台立即更新——无需重渲染。
|
||
- 按 <kbd>T</kbd> 隐藏/显示面板;按 <kbd>R</kbd> 重置为默认值。
|
||
- 刷新页面——所有选择都会持久保留。
|
||
|
||
## 何时使用
|
||
|
||
- 用户生成的内容他们喜欢 80%,剩下的 20% 想自己微调。
|
||
- 你在展示设计系统/品牌,希望观众实时感受变体效果(而不是让你重新运行 agent)。
|
||
- 你在交付一个独立演示(例如作品集项目),希望观众可以交互体验。
|
||
|
||
## 何时*不要*使用
|
||
|
||
- 一次性制品,无需迭代(例如 runbook——参数无意义)。
|
||
- 制品的价值在于固定比例(例如数据可视化信息图,经过精心平衡——加上 knob 反而会破坏它)。
|
||
|
||
## 5 个标准 knob
|
||
|
||
> 选择与制品匹配的子集。如果只有 2 个重要,不要全部 5 个都上——杂乱就是退步。
|
||
|
||
### 1. `--accent`——强调色
|
||
|
||
一个包含 5–8 种精选色板的下拉选择(不要提供一个自由颜色选择器——用户会选到糟糕的颜色然后怪你)。
|
||
|
||
```js
|
||
const ACCENT_PRESETS = [
|
||
{ id: 'rust', val: '#c96442', label: 'Rust' },
|
||
{ id: 'cobalt', val: '#2c4d8e', label: 'Cobalt' },
|
||
{ id: 'sage', val: '#4a7a3f', label: 'Sage' },
|
||
{ id: 'plum', val: '#7a3f6a', label: 'Plum' },
|
||
{ id: 'graphite',val: '#3a3a3a', label: 'Graphite' },
|
||
];
|
||
```
|
||
|
||
制品中所有原先硬编码强调色的地方都使用 `var(--accent)`。边框 / 链接 / 引用块分割线 / CTA 全部同步切换。
|
||
|
||
### 2. `--scale`——字号比例(0.85 / 1.0 / 1.15)
|
||
|
||
三种设置:*紧凑*(0.85)、*正常*(1.0)、*宽松*(1.15)。所有 `font-size` 声明通过 `calc(... * var(--scale))` 乘以 `var(--scale)`。
|
||
|
||
不要超过 ±15%——超出后布局会破坏(列流、断点、行数)。
|
||
|
||
### 3. `--density`——布局密度(紧凑 / 正常 / 宽敞)
|
||
|
||
三种设置,切换间距比例:*紧凑*(0.75)/ *正常*(1.0)/ *宽敞*(1.4)。所有 `padding` / `gap` / `margin` 声明乘以 `var(--density)`。
|
||
|
||
这是影响最大的 knob——也是最脆弱的,因此**每个布局关键容器必须在包裹之前将其基础间距声明为自定义属性**。
|
||
|
||
### 4. `--mode`——浅色 / 深色
|
||
|
||
一个二态切换。在 `<html>` 元素上设置 `data-mode="light"` 与 `"dark"`,制品的 `:root` 选择器以两组颜色集响应。
|
||
|
||
如果制品已经有基于媒体查询的深色模式,*替换*为 data-attr 版本——用户的选择应优先于其操作系统。
|
||
|
||
### 5. `--motion`——关闭 / 微妙 / 生动
|
||
|
||
三种设置。映射到一个 CSS 变量 `--motion-mult`,用于缩放所有 `transition-duration` / `animation-duration` 声明:
|
||
|
||
- *关闭*——`0s`(同时禁用 WebGL canvas / 装饰性动画)。
|
||
- *微妙*——`1.0`(制品原生的时间设定)。
|
||
- *生动*——`1.6`(较慢的过渡,更明显的动效)。
|
||
|
||
尊重 `prefers-reduced-motion`:如果用户设置了该偏好,默认使用*关闭*,无论存储的偏好是什么。
|
||
|
||
## 宿主集成协议(必读)
|
||
|
||
Open Design 查看器工具栏有一个 **Tweaks** 开关,用于从 iframe 外部驱动面板可见性。要让该开关绑定到你的面板,你的制品**必须**采用以下两种协议之一(选其一,不要混用)。工具栏一旦检测到任一信号就会启用自身。
|
||
|
||
### 协议 A——postMessage(推荐用于 agent 生成的制品)
|
||
|
||
当面板通过 JS(React、vanilla、任意动态方式)挂载时使用。
|
||
|
||
**制品 → 宿主:**
|
||
- 挂载时,向 `window.parent` 发送 `{ type: '__edit_mode_available', visible?: boolean }`。告知工具栏存在面板;可选的 `visible` 报告面板的初始状态,使工具栏开关一开始就同步。省略 `visible` 适用于常见的"面板已在屏幕上"的情况(宿主将缺失字段视为 `true`,以保证旧版无参数消息继续工作)。传递 `visible: false` 以声明默认关闭的面板。
|
||
- 当用户本地关闭面板(× 按钮、Esc 等)时,发送 `{ type: '__edit_mode_dismissed' }`。工具栏切换为"关闭"。
|
||
|
||
**宿主 → 制品:**
|
||
- `{ type: '__activate_edit_mode' }`——打开面板(`setOpen(true)`)。
|
||
- `{ type: '__deactivate_edit_mode' }`——关闭面板(`setOpen(false)`)。
|
||
|
||
最小监听器:
|
||
|
||
```js
|
||
window.addEventListener('message', (e) => {
|
||
const t = e?.data?.type;
|
||
if (t === '__activate_edit_mode') setOpen(true);
|
||
else if (t === '__deactivate_edit_mode') setOpen(false);
|
||
});
|
||
// 或者,对于默认关闭的面板:
|
||
// window.parent.postMessage({ type: '__edit_mode_available', visible: open }, '*');
|
||
window.parent.postMessage({ type: '__edit_mode_available' }, '*');
|
||
// 在关闭处理程序中:
|
||
const dismiss = () => {
|
||
setOpen(false);
|
||
window.parent.postMessage({ type: '__edit_mode_dismissed' }, '*');
|
||
};
|
||
```
|
||
|
||
面板可以默认打开或关闭——宿主会将其开关同步到制品报告的状态。
|
||
|
||
### 协议 B——基于 class(由 `assets/wrap.html` 使用)
|
||
|
||
仅在直接包裹模板时使用。制品包含一个 `.tw-panel` 元素,并通过切换 `.tw-hidden` 类来控制可见性。查看器的 iframe 桥接层(位于 `apps/web/src/runtime/srcdoc.ts`)在初始渲染时隐藏面板,通过 `MutationObserver` 监听该类,并在两个方向中继状态。制品无需额外 JS,模板已包含所需内容。
|
||
|
||
选择器是固定的:`.tw-panel`(面板根元素)和 `.tw-hidden`(隐藏状态)。如果重命名其中任何一个,桥接层将无法找到它。
|
||
|
||
### 反模式
|
||
|
||
不要发明第三种协议,也不要重命名任何一组标识符。工具栏开关只绑定到 A 或 B。使用自定义 class 且没有 postMessage 的自定义面板将导致工具栏变灰不可用。
|
||
|
||
## 实现原语
|
||
|
||
阅读 `assets/wrap.html`——它将面板 + 桥接层作为惰性模板提供。你的工作是:
|
||
|
||
1. 获取用户现有的制品 HTML。
|
||
2. 将其强调色 / 模式 / 间距 / 字号提升为自定义属性(搜索硬编码的 `#hex` / `Npx` / `Nrem` 并转换)。
|
||
3. 将内容粘贴到 `wrap.html` 的标记区域。
|
||
4. 编辑 `assets/wrap.html` 的 `KNOBS` 数组,只保留你确定与*这个*制品相关的 knob。如果只有 2 个重要,不要全部 5 个都上。
|
||
5. 将 `STORAGE_KEY` 改为一个唯一的 slug(`tweaks-<artifact-slug>`)。
|
||
|
||
`wrap.html` 中的桥接层:
|
||
- 首次渲染时加载 `localStorage[STORAGE_KEY]` JSON。
|
||
- 将值作为 `document.documentElement.style.setProperty('--accent', ...)` 应用。
|
||
- 监听每个表单控件的 `change` 事件并写回。
|
||
- 暴露 <kbd>T</kbd>(切换面板)和 <kbd>R</kbd>(重置)。
|
||
|
||
## 工作流程
|
||
|
||
### 步骤 1——获取制品
|
||
|
||
与 critique skill 相同的选项:
|
||
|
||
1. 项目文件(项目文件夹中的 `index.html`)。
|
||
2. 粘贴到聊天中的 HTML。
|
||
3. 由你在此轮对话中生成。
|
||
|
||
### 步骤 2——决定哪些 knob 适用
|
||
|
||
先阅读制品的 CSS。对每个 knob,决定*是 / 否*:
|
||
|
||
- `--accent`——如果制品有 1 个使用了 ≥ 3 次的强调色,则为是。
|
||
- `--scale`——如果制品以文字驱动(文章、幻灯片、定价页),则为是。
|
||
- `--density`——如果制品有一致的 gap / padding 节奏(幻灯片、仪表板、着陆页),则为是。runbook 不需要(已经很密集了)。
|
||
- `--mode`——如果制品有原创的深色模式 token,或者你愿意为其推导,则为是。
|
||
- `--motion`——如果制品有任何值得缩放的 transition / animation,则为是。静态报告 / critique 报告不需要。
|
||
|
||
默认:**3 个 knob 是最佳选择。** 五个太杂,一个不值得做一个面板。
|
||
|
||
### 步骤 3——将硬编码值提升为自定义属性
|
||
|
||
打开 `assets/wrap.html` 的 `<style>` 块——复制其自定义属性命名方案(`--accent`、`--scale` 等)。在用户的制品中,找到所有相关位置并改写:
|
||
|
||
- `color: #c96442` → `color: var(--accent)`
|
||
- `font-size: 18px` → `font-size: calc(18px * var(--scale))`
|
||
- `padding: 24px 32px` → `padding: calc(24px * var(--density)) calc(32px * var(--density))`
|
||
- `transition: opacity 200ms` → `transition: opacity calc(200ms * var(--motion-mult))`
|
||
|
||
如果制品已经使用了 `clamp()` 或 `vw`,将*外层*值乘以自定义属性——不要拆解 `clamp(...)`。
|
||
|
||
### 步骤 4——粘贴到包裹模板中
|
||
|
||
将制品的 `<style>` 和 `<body>` 复制到 `wrap.html` 的标记区域。保持面板 + 桥接层完整。
|
||
|
||
### 步骤 5——测试循环
|
||
|
||
打开结果,点击每个 knob 至少一次,刷新页面,确认选择已持久化。如果某个 knob 破坏了布局——*移除它*,不要发布。
|
||
|
||
## 输出约定
|
||
|
||
```
|
||
<artifact identifier="tweaks-<artifact-slug>" type="text/html" title="<Artifact Title> · Tweaks">
|
||
<!doctype html>
|
||
<html>...</html>
|
||
</artifact>
|
||
```
|
||
|
||
在 artifact 之前放一句话("已将 X 包裹上 3-knob 调参面板——强调色 / 字号比例 / 模式。")。在 `</artifact>` 之后结束。
|
||
|
||
## 硬性规则
|
||
|
||
- **不要提供自由颜色选择器**——只提供精选色板。用户有了自由度就会选糟糕的颜色;帮他们避免这一点就是全部意义所在。
|
||
- **按制品标识持久化**——使用 `tweaks-<slug>`,而不是全局键。两个选项卡中打开的两个制品不得共享状态。
|
||
- **尊重 `prefers-reduced-motion`**——如果用户设置了该偏好,动效默认使用*关闭*,仅在显式点击时覆盖。
|
||
- **单文件**——除了制品已有的导入外,无外部 CSS / JS / 字体。将面板 + 桥接层内联。
|
||
- **在小于 720px 的视口上,面板默认隐藏**——通过右上角的"T"按钮滑入抽屉。
|
||
- **不要发布超过 5 个 knob。** 3 个是最佳选择。
|