DESIGN-SKILL.md
Open Analytics 的设计语言,一个文件搞定, 专为 AI 代理及其人类指挥者编写。这里的每个值都已实际应用于产品中:十六进制颜色代码、弹簧常量、圆角半径、缓动函数。将此文件粘贴到代理的上下文中(或作为规则文件放入你的仓库),然后要求生成“以 OA 风格”的屏幕。
这是专为粘贴而编译的单文件版本。同样的设计语言也作为适当的代理技能(skills/oa-design/:包含完整类型检查源代码的每个组件配方)和 CLI 工具(npx getopen-design add <component>)存在于该仓库中。对于 Claude Code 和其他能识别技能的代理,优先使用这些版本。
用一句话概括它的外观:白色表面,连续曲率圆角,安放在宁静的灰色舞台上,用单一墨色绘制,由单个弹簧驱动。
1. 十条规则
- 一种墨色,一切由此派生。 整个中性系统由一种颜色以固定百分比混合到透明度中生成。永远不要挑选第二种灰色。
- 两个图层,间隙即页面。 表面由一个白色框架容纳一个嵌入式灰色内嵌组成;区块是板块,它们之间的页面背景是唯一的分隔线。不要使用水平分割线。
- 圆角矩形用于表面,药丸用于操作。 卡片使用连续曲率圆角;所有非卡片的可点击元素都是药丸形状。
- 一个弹簧族。 七个命名的弹簧覆盖整个产品。不要发明第八个。
- Chrome 绝不等待。 布局和标题立即渲染;只有数据会从像素匹配的骨架中交换出来,通过模糊效果而非弹跳效果呈现。
- 字重最高为 500。 任何地方都不使用粗体。层级通过字号、颜色和间距来体现。
- 一种强调色,用在一个地方。 一种蓝色专用于主要操作和主要图表线条;语义颜色是文本色调,而非填充色。
- 状态用药丸表示,事件自行结束。 持续性条件渲染不可关闭的条状组件,其寿命与状态持续的时间完全一致;一次性结果则使用 Toast,它们会自行消失。
- 文案是设计的一部分。 使用句子大小写;按钮说明会发生什么;错误信息说明原因和解决方法,不指责。
- 始终保证质量底线。 焦点环、
role="status"、aria-hidden装饰、prefers-reduced-motion,以及无水平页面滚动。
2. 令牌 (Tokens)
亮色模式
:root {
--ink: #292929;
--background: #f6f6f6; /* 板块之间的灰色舞台 */
--card: #ffffff;
--popover: #ffffff;
/* ink 派生的中性色:百分比本身就是系统 */
--border: color-mix(in srgb, var(--ink) 12%, transparent);
--input: color-mix(in srgb, var(--ink) 14%, transparent);
--accent: color-mix(in srgb, var(--ink) 5%, transparent); /* 悬停水洗色 */
--muted: color-mix(in srgb, var(--ink) 5%, transparent);
--muted-foreground: #6d6d6d;
--foreground: var(--ink);
--primary: #305dde; /* 唯一的强调色 */
--primary-foreground: #ffffff;
--ring: #3ba6f1;
--secondary: #e9e9e9; /* 平面灰色控件,无边框 */
--secondary-foreground: var(--ink);
/* 语义色,作为亮色模式下的文本色调(Tailwind 级别,700 行) */
--destructive: var(--color-red-500);
--destructive-foreground: var(--color-red-700);
--success-foreground: var(--color-emerald-700);
--warning-foreground: var(--color-amber-700);
--info-foreground: var(--color-blue-700);
--chart-1: #296FF0; /* 主要图表线条 */
--radius: 0.625rem;
}
需要更强的分隔线?提高 ink 百分比(产品仅使用 4、5、10、12、14)。语义色用于文字和 8px 圆点,从不用于面板填充。
暗色模式
相同的配方,重新配色,带有两个系统性的调整:线条百分比提高 2 点,语义文本从 700 行移至 400 行。
.dark {
--ink: #ededed;
--background: #191919;
--card: #212121;
--popover: #242424;
--border: color-mix(in srgb, var(--ink) 14%, transparent);
--input: color-mix(in srgb, var(--ink) 16%, transparent);
--muted-foreground: #969696;
--secondary: #2e2e2e;
--primary: #296FF0;
--destructive-foreground: var(--color-red-400);
--success-foreground: var(--color-emerald-400);
}
圆角半径
一个基础值,乘以倍数;绝不使用特制的半径:
--radius: 0.625rem; /* sm ×0.6, md ×0.8, lg ×1, xl ×1.4,
2xl ×1.8, 3xl ×2.2, 4xl ×2.6 */
rounded-full 保留给药丸形状。圆角矩形表面带有自己更大的半径(请参阅第 4 节)。
排版
- Inter Tight,可变字体,字重 300 到 500,自托管(字体 CDN 会导致构建失败;将 woff2 文件保存在仓库中)。
code, kbd, samp, pre等元素、键盘快捷键和代码片段使用 Geist Mono。- 字重上限即身份标识:没有任何东西渲染得比 500 更重。层级通过字号、
text-foreground/80对比text-muted-foreground以及间距来体现。 - 标题:
font-medium tracking-tight;营销标题添加text-wrap: balance。 - 数据:任何在列中或就地更新的数字使用
tabular-nums。 - 默认 UI 文本为 14px;次要文本为 12px,配合
leading-5/leading-6。
阴影
层级是二元的:
/* 静止卡片 */ box-shadow: 0 1px 2px rgba(0,0,0,0.06);
/* 浮动界面 */ box-shadow: 0 1px 2px rgba(0,0,0,0.08),
0 8px 24px rgba(0,0,0,0.08);
背景规范
body 为 白色(过度滚动会显示 body 的背景;灰色从白色屏幕边缘露出来看起来像另一个页面)。需要灰色舞台的屏幕会自行绘制背景。根元素设置 overflow-x: clip。
3. 布局
板块 (Plates)。区块是自包含的面板;它们之间显示的页面背景是唯一的边界。垂直间距慷慨(营销页面 gap-12 sm:gap-20,应用内 gap-6),侧边内边距更紧凑(px-4 sm:px-6)。同级元素由父元素的 gap 隔开,绝不使用每个子元素的 margin。区块之间不使用 <hr> 或 border-b。
按作用域设定宽度:营销页面 76rem (6.5xl),应用站点页面 max-w-6xl,列表/选择器屏幕 max-w-5xl,阅读表面约 65ch。居中,mx-auto。
应用页面结构:
固定头部 (h-14) → 容器 pt-14
可选的浮动药丸(固定定位 top-16,居中)
<main class="px-4 pb-36 pt-8 sm:px-6 bg-white">
标题行:h1 text-xl font-medium tracking-tight + 右侧一个控件
可选的通知条
板块:flex flex-col gap-6
标题行恰好包含一个控件。pb-36 确保内容不接触视口边缘。
网格 (Grids):grid gap-3,响应式列(统计数据使用 grid-cols-2 sm:grid-cols-4)。卡片共享固定的内容高度并在内部滚动;网格的基线永远不会被打断。
响应式:在 390px 和 1280px 宽度下设计。水平配对的元素在 sm: 断点下堆叠,操作项放在最后。宽内容在其自己的 overflow-x-auto 容器中滚动;页面永远不会横向滚动。
设置页面:窄选项卡导轨 + 由区块面板组成的内容列;每个区块以一个标题块开始(标题 + 一行灰色文字,右侧可选的一个操作)。通过 ?tab=<id> 深度链接到选项卡。
4. 组件
圆角矩形表面 (Squircle Surface)
通过 CSS shape() 裁剪路径实现连续曲率圆角,由两个变量参数化,并在支持 corner-shape: squircle 的地方使用该属性:
.squircle {
border-radius: 26px;
--card-clip-radius: 14px;
--card-clip-handle: 2.25px;
clip-path: shape(
from var(--card-clip-radius) 0px,
line to calc(100% - var(--card-clip-radius)) 0px,
curve to 100% var(--card-clip-radius)
with calc(100% - var(--card-clip-handle)) 0px / 100% var(--card-clip-handle),
line to 100% calc(100% - var(--card-clip-radius)),
curve to calc(100% - var(--card-clip-radius)) 100%
with 100% calc(100% - var(--card-clip-handle)) / calc(100% - var(--card-clip-handle)) 100%,
line to var(--card-clip-radius) 100%,
curve to 0px calc(100% - var(--card-clip-radius))
with var(--card-clip-handle) 100% / 0px calc(100% - var(--card-clip-handle)),
line to 0px var(--card-clip-radius),
curve to var(--card-clip-radius) 0px
with 0px var(--card-clip-handle) / var(--card-clip-handle) 0px,
close
);
corner-shape: squircle;
}
@media (min-width: 640px) {
.squircle { border-radius: 50px; --card-clip-radius: 20px; --card-clip-handle: 3px; }
}
迷你卡片 (Mini Card)
外层圆角矩形:bg-card, border, p-1, 静止阴影
├── 头部条 (pb-2 pl-3.5 pr-2 pt-1.5):
│ 16px 哑光图标 + text-sm font-medium text-foreground/80 标题
│ 可选的标签,位于标题中心线上
│ “查看更多 ›” (text-xs muted; 悬停: accent wash, 箭头滑动 2px)
└── 内层圆角矩形:bg #f6f6f6, border, rounded-[22px] sm:rounded-[44px],
固定高度 (h-44 = 五行 32px 的行), overflow hidden
卡片绝不对不对数据增长,更长的列表在内部滚动。溢出内容在右下角显示一个 24px 的 V 形图标标签,在 y:[0,2.5,0] 之间浮动(1.4 秒 easeInOut 循环),在滚动到底部时淡出(0.18s,缩放 0.7)。行:32px 高,全幅 accent 水洗色悬停效果,数值使用 tabular-nums。
按钮
药丸形状,font-medium,物理按压效果:active:translate-y-px active:scale-[0.98]。
- primary:强调色蓝色,带有斜面效果(背景/边框色向深靛蓝混合,顶部
inset 0 1px 0 rgba(255,255,255,0.22),底部深色内阴影);悬停时变亮,趋向纯蓝色。 - secondary:平面
--secondary,透明边框,悬停时混合 5% 的 ink。是最常用的主力按钮。 - ghost:仅显示哑光文本。destructive:红色填充,仅在确认步骤使用。
- 尺寸 h-9 / h-8 / h-7;应用界面主要使用小尺寸。
- 加载状态保持标签文本,并添加一个 14px 的环形旋转器 (
border-2 border-current border-t-transparent);宽度绝不跳动。 focus-visible:ring-[3px],颜色为--ring/50。绝不移除。
下拉面板(菜单、选择器、切换器)
触发药丸 → 浮动面板(浮动阴影,边框,白色,rounded-2xl+),以 PANEL (550/38) 弹簧进入,带有小的 y/缩放偏移,更快的退出。32px 高的 rounded-lg 项目,带有 accent 水洗色悬停效果;活动高亮是一个共享元素,会移动(使用 layoutId 或宽度/高度弹簧),绝不会逐项重绘。点击外部或按 Escape 键关闭。
模态框
背景遮罩淡入约 0.15 秒;面板以 POP (400/26) 弹簧从 opacity 0, scale .96 进入,以 380/28 退出。多步骤对话框保持一个面板,其高度通过 LAYOUT (550/40) 弹簧和 ResizeObserver 动画到每一步的测量大小,同时步骤之间滑动切换(x: ±12px,popLayout,0.16s easeOut 淡出)。底部栏:h-12 条,返回按钮(secondary xs)+ 主要 xs 按钮,右对齐。
选项卡栏
活动药丸是一个固定窗口;标签在其后面滑动。进入的标签从移动方向以其自身宽度的 110% 进入;退出的标签向相反方向退出固定 130px(当药丸朝向更长的单词增长时,百分比会不足)。药丸的宽度/高度跟随 LAYOUT 弹簧上测量的标签。全程保持完全不透明度;通过遮罩来实现效果。
骨架屏
- Chrome 绝不等待;只有数据区域是骨架屏。
- 像素匹配:一个条形位于与其所代表文本相同的行高插槽内(
flex h-6 items-center包裹一个h-3条形);内容交换时不移动任何东西。 - 数据通过焦点效果进入:一次性的动画,从
opacity ~0.4 + blur(4px)到清晰。一个动画,而不是过渡(内容挂载时已经是最终状态;过渡没有前一个值)。之后保持filter: blur(0)。
条形:rounded bg-muted-foreground/15,缓慢的背景位置扫描(2 秒线性无限循环,-1s 起始偏移,使条形不同步)。
通知、空状态、浮动药丸
通知条 位于其解释的内容上方:
<div class="flex flex-col gap-2 rounded-2xl border border-border bg-card
px-4 py-3 sm:flex-row sm:items-center sm:justify-between"
role="status">
<p class="text-sm leading-6 text-muted-foreground">
<span class="font-medium text-foreground">暂无事件。</span>
如果代码片段已启用,请浏览您的网站,我们将在几秒钟内捕获第一个页面浏览量。
</p>
<button class="…secondary pill…">检查安装</button>
</div>
一个粗体声明,一行灰色文字,一个次要操作;在 sm: 下堆叠;其寿命与状态持续的时间完全一致;不可关闭。
浮动药丸 用于应用范围内的持续性条件:固定定位 top-16,居中,rounded-full bg-card py-1.5 pl-3 pr-4 text-xs font-medium,浮动阴影,8px 状态圆点(紧急时脉冲),以 BANNER (400/30) 弹簧从 y:-8 进入,退出 0.15 秒。整个药丸链接到解决状态的位置。状态药丸从不自行关闭;事件药丸在大约 6 秒后消失。
Toast
持续时间为 0.35 秒,缓动函数 cubic-bezier(0.5, 1, 0.89, 1):
- 成功:脉冲
scale 1 → 1.025 → 0.99 → 1(0.32 秒)。 - 错误:抖动
x 0 → -3 → 3 → -3 → 0px (0.28 秒)。
交替使用奇/偶关键帧副本,以便重复时重新开始。
5. 动效
| 名称 | 值 | 用途 |
|---|---|---|
| PANEL | 550 / 38 | 下拉菜单、菜单、面板、切换 |
| LAYOUT | 550 / 40 | 测量后的高度/宽度、滑动标签 |
| POP | 400 / 26 | 模态框进入 |
| POP_EXIT | 380 / 28 | 模态框退出 |
| BANNER | 400 / 30 | 浮动药丸、页面横幅 |
| FLICK | 900 / 50 | 图标微观移动 |
| CHART | 300 / 25-30 | 图表工具提示、十字准线 |
微观淡入淡出:0.1 秒到 0.18 秒 easeOut。应用界面中没有动画持续时间超过 0.2 秒;如果感觉慢,降低阻尼,不要延长持续时间。
标志性动效:基于测量高度的编排(一个不变的面板,步骤在其间滑动)、移动的高亮(一个共享的活跃元素,绝不逐项重绘)、进入/退出不对称(退出总是更快)、悬停仅为颜色变化(加上最多 2px 的箭头滑动;卡片从不抬起或缩放),以及每屏最多一个微小的环境循环。
规则:对所有卸载的元素使用 AnimatePresence;尊重 prefers-reduced-motion(揭示内容折叠为可见,循环停止);绝不动画未经测量的布局;绝不在渲染期间读取系统时钟(订阅按分钟桶更新的时钟存储)。
6. 着陆页
相同的令牌,略……
……[内容已截断,等待翻译]