DESIGN-SKILL.md
Open Analytics 的設計語言,濃縮在單一檔案中, 專為 AI 代理程式以及引導它們的人類所撰寫。此處的每個數值都實際應用於產品中:色碼、彈簧常數、圓角半徑、緩動函數。將此檔案貼入您的代理程式上下文(或將其作為規則檔案放入您的儲存庫),並要求生成「OA 風格」的畫面。
這是一個編譯後的單一檔案版本,方便直接貼上。相同的設計語言也以正規的代理程式技能形式(skills/oa-design/:包含完整的型別檢查原始碼,提供每個元件的配方)以及 CLI 工具(npx getopen-design add <component>)收錄於此儲存庫中;若使用 Claude Code 或其他支援技能的代理程式,建議優先選用這些版本。
一句話總結其外觀:白色表面搭配連續曲率邊角,靜置於沉穩的灰色舞台上,以單一墨色繪製,由單一彈簧驅動。
1. 十大規則
- 一種墨色,萬物衍生。 整個中性色系統僅由一種顏色混合至固定百分比的透明度構成。切勿選用第二種灰色。
- 兩層結構,頁面即間隙。 表面由一個白色框架包裹一個凹陷的灰色內嵌區塊所構成;區塊是盤面,區塊之間的頁面背景是唯一的分隔線。不使用水平分隔線。
- 表面用方圓角,操作用膠囊形。 卡片採用連續曲率邊角;非卡片的可點擊元素皆為膠囊形。
- 一個彈簧家族。 七個命名的彈簧涵蓋整個產品。請勿發明第八個。
- Chrome 元件從不等待。 版面與標題立即渲染;僅資料交換時會從一個像素完美匹配的骨架中浮現,以模糊效果而非彈跳方式呈現。
- 字重上限為 500。 任何地方不使用粗體。階層結構來自於字體大小、顏色和間距。
- 一個強調色,只用在一處。 單一的藍色用於主要操作按鈕和主要圖表線條;語義色是文字的色調,而不是填滿色。
- 狀態用膠囊條呈現,事件自行結束。 持續存在的狀態渲染為不可關閉的條帶,其存在時間與狀態持續時間完全一致;一次性結果則以自動消失的提示訊息呈現。
- 文案也是設計的一部分。 使用一般句子的書寫格式;按鈕說明將執行的動作;錯誤訊息說明原因與解決方法,不帶指責語氣。
- 品質底線,始終如一。 聚焦環、
role="status"、aria-hidden裝飾、prefers-reduced-motion,以及無水平頁面捲動。
2. Token
淺色模式
:root {
--ink: #292929;
--background: #f6f6f6; /* 盤面之間的灰色舞台 */
--card: #ffffff;
--popover: #ffffff;
/* 墨水色衍生的中性色:百分比本身就是系統 */
--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;
}
需要更明顯的分隔線?提高墨水色百分比(產品僅使用 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 檔案保留在儲存庫中)。
- Geist Mono 用於
code, kbd, samp, pre、按鍵和程式碼片段。 - 字重上限即為品牌識別:任何元素都不應比 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);
/* 浮動 Chrome */ 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. 版面配置
盤面。 區塊是各自獨立的面板;頁面背景在它們之間顯示,此即為唯一的邊界。垂直間距寬敞(行銷用 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 防止內容頂到視口邊緣。
網格:grid gap-3,自適應欄數(統計數字使用 grid-cols-2 sm:grid-cols-4)。卡片共享固定的內容高度,內部可捲動;網格基準線永不中斷。
響應式:以 390px 和 1280px 為設計基準。水平成對的元素在 sm: 斷點以下會垂直堆疊,且操作項在最後。寬內容在自己的 overflow-x-auto 容器內捲動;頁面永遠不會水平捲動。
設定頁面:窄標籤導覽列 + 由區段面板組成的內容欄;每個區段以一個標題區塊(標題 + 一行灰色說明文字,選擇性在右側放置一個操作)開頭。透過 ?tab=<id> 實現深層連結標籤。
4. 元件
方圓角表面
透過 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; }
}
迷你卡片
外部方圓角: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; 懸浮: 強調色底色, 箭頭向右滑動 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 高,懸浮時使用全出血的強調色底色,數值使用 tabular-nums。
按鈕
膠囊形,font-medium,按下時有物理回饋:active:translate-y-px active:scale-[0.98]。
- 主要:強調色藍色,帶有斜角(背景/邊框顏色向深靛藍混合,頂部
inset 0 1px 0 rgba(255,255,255,0.22),底部深色內陰影);懸浮時變亮,趨向純粹的主要色。 - 次要:扁平
--secondary,透明邊框,懸浮時混合 5% 墨水色。主要工作按鈕。 - 幽靈:僅顯示淡出文字。危險:紅色填滿,僅在確認步驟使用。
- 尺寸 h-9 / h-8 / h-7;應用程式 Chrome 元件大多使用小尺寸。
- 載入狀態保留標籤,並添加一個 14px 的環形轉輪(
border-2 border-current border-t-transparent);寬度永不跳動。 focus-visible:ring-[3px]使用--ring/50。永不移除。
下拉面板(選單、下拉選單、切換器)
觸發膠囊按鈕 → 浮動面板(浮動陰影、邊框、白色、rounded-2xl+),以 PANEL (550/38) 彈簧動畫搭配小幅 y/偏移縮放進入,更快速地退出。32px 高的 rounded-lg 選項,帶有強調色底色懸浮效果;啟用狀態的高亮顯示是一個共享的移動元素(使用 layoutId 或寬高彈簧),永不逐項重新繪製。點擊外部或按 Escape 鍵關閉。
模態框
背景遮罩淡入約 0.15 秒;面板以 POP (400/26) 彈簧動畫從 opacity 0, scale .96 進入,以 380/28 退出。多步驟對話框保持一個面板,其高度會根據每個步驟的測量大小(使用 LAYOUT 550/40 + ResizeObserver)進行動畫調整,同時步驟內容滑入(x: ±12px,popLayout,0.16 秒 easeOut 淡出)。頁尾:h-12 條帶,返回(次要 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="…次要膠囊按鈕…">檢查安裝</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 秒後自動消失。
提示訊息
持續時間低於 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。應用程式 Chrome 元件中沒有動畫持續時間超過 0.2 秒;如果感覺太慢,請降低阻尼,而不是延長時間。
標誌性動畫:透過測量高度進行編排(一個恆定面板,步驟內容滑入其中)、移動的高亮顯示(一個共享的啟用元素,永不逐項重新繪製)、進入/退出不對稱(退出總是更快)、懸浮效果僅改變顏色(加上最多 2px 的箭頭滑動;卡片永不浮起或縮放),以及每個畫面最多一個微小的環境循環。
規則:對所有卸載的元素使用 AnimatePresence;尊重 prefers-reduced-motion(展開效果收縮為可見,循環動畫停止);永遠不對未測量的版面進行動畫處理;永遠不要在渲染期間讀取 wall clock(應訂閱以分鐘為桶的 ticking store)。
6. 著陸頁
使用相同的 Token,但…
[為翻譯而截斷]