ultrahand: theme
一次构建项目的主题,然后持续发展它。主题是一个设计令牌文件,加上记录其决定的简短说明,保存在项目中,这样后续的每个设计和审查都建立在相同的基础上,而不是重新开始新的外观。文件格式请参见 shared/theme/README.md;规则请参见 shared/theme/rules.md,每条规则都引用了 shared/theme/sources.md。
每个主题都保留不可协商项(NN-1 至 NN-12:层级、8 点网格、两种配色方案、对比度、真实的字体比例及其他),并在每个创意轴(CR-1 至 CR-10:色相、中性色、字体风格、圆角、深度、密度……)上做出新的选择。规则是固定的,确保每个主题都做得很好;轴是开放的,因此没有两个产品看起来相同。开始前请阅读 rules.md。
以下脚本位于 ${CLAUDE_PLUGIN_ROOT}/shared/theme/ 中。
1. 查找现有内容
先查看,再提问。对于仅以画布制品形式存在的设计,先列出制品的文件,然后从那里读取 design/。在项目中搜索 design/THEME.md、*.tokens.json、tokens/、Style Dictionary 配置以及平台的主题代码:CSS 自定义属性、tailwind.config.*、Flutter ThemeData / ColorScheme、SwiftUI 颜色资源、Compose MaterialTheme。结果选择模式:
| 找到的内容 | 模式 |
|---|---|
| 存在主题文件和 THEME.md | 演进(第 4 步)。切勿重新生成。 |
| 没有主题文件,但有样式代码 | 提取(第 2 步),然后演进。 |
| 无任何内容(新产品) | 创建(第 3 步)。 |
2. 提取代码中已有的主题
目标是捕获当前的外观并进行清理,而不是重新设计。
- 运行
node scan.mjs <src dir>(添加--json可获得完整列表)。它会列出每种颜色、间距值、圆角和字号及其计数和位置,对近似重复的颜色进行分组,并标记偏离网格的间距。同时阅读平台主题代码:主题对象命名的值是预期的值,散落的原始值是偏离。 - 命名角色。 对于每个颜色簇,找出它用于什么(页面背景、正文文本、按钮、边框、错误),并将其映射到语义角色(README.md 列出了所需的角色)。一个角色,一个值:当两个近似重复服务于一个角色时,保留使用更广泛的那个。
- 对齐规则。 将间距折叠到 NN-2 尺度,大小折叠到字体比例(NN-6),圆角折叠到短尺度(NN-7)。通过沿相同色相的色调步骤移动(NN-4)来修复对比度失败。每次更改都是产品的可见更改,因此将它们列成表格(
13px → 12px,4 处)供所有者批准,并保留他们希望保持不变的任何内容。 - 填补空白。 大多数代码没有深色模式、没有焦点环、也没有
on-角色。用主题自身的调色板(NN-3、NN-10)设计它们,而不是添加通用角色。 - 盘点组件。 列出可复用组件(查看
components/、ui/、widgets/、views/),它们的变体以及每个组件的定义位置。注意由两个不同组件完成的任何工作:这是一个合并候选对象(EV-4),而不是要复制到主题中的内容。 - 记录其已有的个性。 将现有外观放在每个创意轴上,并写入 THEME.md。品牌指定的选择(徽标色相、授权字体)是
locked;其余的是open。
然后写入文件(第 5 步)。
3. 创建新主题
- 个性。 询问三个形容词以及为谁创建,再加上任何固定内容:品牌颜色、徽标、字体、平台(原生 iOS 或 Android 应用倾向于使用系统字体和颜色;主题映射到它们上面 [HIG-COLOR] [M3-COMPOSE])。
- 三个方向。 提出在至少三个创意轴上不同的三个方向,每个方向带有一句与形容词相关的理由。避免使用 rules.md 描述的默认外观,除非所有者明确要求。并排展示它们(第 6 步),让所有者选择一个或混合它们。
- 构建它。
- 调色板: 对于每种关键颜色(主色、中性色、加上错误色和任何强调色),一个大约 10-13 步从深到浅的色调比例 [M3-HCT] [M3-COMPOSE],以便每个角色都能找到在两种配色方案中满足对比度的步进。
- 语义颜色: 每个必需的角色,每个角色都有其深色值,每个
on-X都针对X进行设计。 - 空间: NN-2 尺度,然后按任务划分语义间距:
inset步进用于内边距,以及gap-related、gap-group和gap-section,以便组间间隙明显大于组内间隙的构图规则(shared/composition/principles.md 中的 SEC-2)内置于其中。 - 字体: 根据所选比例(CR-5)确定一个尺度,包含角色 display、headline、title、body、label;body 使用平台的默认值(Web 上为 16,iOS 上为 17 pt;仅在密集型桌面工具中为 13-14,请参阅 NN-6)。
- 形状、深度、动效、焦点: 以所选圆角样式的圆角尺度;以所选深度方法的层级;持续时间和缓动令牌;一个 2 px 或更宽的焦点环。
- 组件: 仅包含组件需要与角色不同的组件令牌(EV-2)。大多数组件不需要任何令牌。
每个值从版本 1.0.0 开始。
4. 演进现有主题
对于类似“添加警告颜色”、“我们需要更密集的表格”、“让它感觉更温暖”的请求,或设计技能或 theme-review 提出的添加建议:
- 首先阅读 THEME.md 和令牌(EV-1)。尊重
locked的决定;如果请求需要更改其中之一,请说明并询问。 - 如果存在合适的角色,则重用(EV-2)。仅当存在真正的缺口时才添加令牌,并将其别名指向现有的原始步进(EV-3)。
- 广泛的更改(“更温暖”、“更有趣”)移动一个
open的创意轴:移动原始值,保留所有语义名称,并重新运行对比度检查。名称在品牌重塑期间保持真实(NN-11)。 - 弃用时使用
$deprecated命名替换项;在后续版本中移除(EV-6)。 - 升级版本并添加一条变更日志,说明更改了什么以及哪些屏幕需要它(EV-5)。
5. 编写和检查
- 编写
design/theme.tokens.json(或项目现有的令牌位置)和design/THEME.md,遵循 README.md。example.tokens.json 展示了结构。 - 运行
node check.mjs design/theme.tokens.json并修复每个错误。警告需要在 THEME.md 中给出修复或理由。 - 交付给代码。 对于 Web,运行
node css.mjs design/theme.tokens.json --out <path>/theme.css可生成包含内建深色模式的 CSS 自定义属性。对于其他平台,或者同时针对多个平台,推荐使用 Style Dictionary,它可以读取此格式并输出 Swift、Android、Compose 和 Flutter 代码 [STYLE-DICT]。主动提议用令牌替换扫描发现的原始值;未经询问不要执行,因为它会触及每个样式文件。
6. 展示主题
在插件画布(shared/canvas/;其 README 中有约定)上构建一个样本,以便所有者看到主题的实际效果,而不是一串十六进制颜色代码:
node "${CLAUDE_PLUGIN_ROOT}/shared/theme/css.mjs" design/theme.tokens.json --style --out theme.html
node "${CLAUDE_PLUGIN_ROOT}/shared/canvas/build.mjs" \
--presets "${CLAUDE_PLUGIN_ROOT}/skills/desktop/references/presets.md" \
--head theme.html --screen screen.html --title "<Product> 主题" --out index.html
样本的 screen.html 仅使用主题的自定义属性,并展示:颜色角色以 on-X 文本显示在 X 上,并附带测量的对比度比率;字体比例及真实副本;间距尺度;圆角和层级;每个已盘点的组件及其状态;以及一个微小的真实屏幕(一个列表和一个表单),使用主题构建。画布上的浅色/深色和 200% 文本切换可测试两种配色方案和文本缩放。如果主题需要 Web 字体,将其 <link> 放在同一个 head 文件中。
比较方向(第 3 步):不使用 --style 输出每个方向,而是用 --selector '[data-state="a"]'(然后是 b、c)限定作用域,并将它们粘贴到屏幕模板自己的 <style> 顶部:画布在屏幕内部设置 data-state,而页面头部无法触及此处。在 config.json 中将 states 声明为 a、b、c,并以方向名称作为标签,这样状态选择器可以在它们之间切换。
将 index.html 作为制品发布。当设计本身也是制品时,使用主题构建的版本重新发布它(--head theme.html),并包含两个 design/ 文件。
7. 演示
分享制品链接。用几句话说明:模式(创建、提取或演进)、版本、个性在创意轴上的位置及原因、哪些是 locked 哪些是 open、任何需要所有者批准的将对齐更改、检查结果,以及下一步:使用它进行设计(mobile、tablet 和 desktop 技能会读取它),或使用 theme-review 对代码进行审计。