DESIGN-SKILL.md
Open Analytics のデザイン言語を1ファイルにまとめ、AIエージェントとそれを操る人間のために記述されています。ここにあるすべての値(16進カラーコード、バネ定数、半径、イージング)は製品にそのまま反映されます。このファイルをエージェントのコンテキストに貼り付けるか(またはリポジトリにルールファイルとして配置する)、「OAスタイルで」スクリーンを生成するよう依頼してください。
これは、貼り付け用にコンパイルされた1ファイル版です。同じ言語は、このリポジトリ内で適切なエージェントスキル(skills/oa-design/:型チェックされた完全なソースコードを埋め込んだコンポーネントごとのレシピ)およびCLI(npx getopen-design add <component>)としても提供されています。Claude Codeやその他のスキル対応エージェントには、そちらをお勧めします。
一言で表すと、連続曲線の角を持つ白い表面が、静かなグレーのステージの上に置かれ、単一のインクで描かれ、単一のバネで動かされるというルックです。
1. 10のルール
- 単一のインク、すべてはそこから派生する。 ニュートラルシステム全体は、1つの色を透明度に固定の割合で混ぜたもの。2つ目のグレーを選んではいけません。
- 2つのレイヤー、その隙間がページとなる。 サーフェスは、くぼんだグレーのインセットを保持する白いフレームです。セクションはプレートであり、それらの間のページ背景が唯一の区切りです。水平ルールは使用しません。
- サーフェスにはスカークル、アクションにはピル。 カードには連続曲線の角を、カード以外のクリック可能な要素はすべてピルとします。
- 1つのバネファミリー。 7つの名前付きバネが製品全体をカバーします。8つ目を発明してはいけません。
- クロームは待たせない。 レイアウトとタイトルは即座にレンダリングし、データのスワップのみをピクセルマッチされたスケルトンから行い、ポップではなくブラーで表示します。
- ウェイトは500で止める。 どこにもボールドは使用しません。階層はサイズ、色、スペーシングで作ります。
- 1つのアクセントカラー、1か所にのみ使う。 プライマリアクションとプライマリチャートラインには単一の青を使用。セマンティックカラーはテキストの色合いであり、塗りつぶしではありません。
- 状態はピルで表現し、イベントはそれ自体で終了する。 継続的な状態は、その状態が続く間だけ存在する非表示不可のストリップでレンダリングします。1回限りの結果は、単独で消えるトーストで表現します。
- コピーもデザインの一部。 文は文頭のみ大文字、ボタンは何が起こるかを説明し、エラーは原因と解決策を非難なく示します。
- 常に品質の基準を満たす。 フォーカスリング、
role="status"、aria-hiddenの装飾、prefers-reduced-motionへの対応、水平方向のページスクロールは禁止。
2. トークン
ライト
: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つの体系的な変更:ヘアラインパーセンテージが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);
}
半径
1つのベースを乗算。特注の半径は決して使用しません:
--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を使用。
シャドウ
浮き上がりは2値:
/* 静止カード */ 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. レイアウト
プレート。 セクションは自己完結型のパネルです。それらの間に表示されるページ背景が唯一の境界です。垂直方向のギャップは大きめ(マーケティングは gap-12 sm:gap-20、アプリ内は gap-6)、サイドパディングはタイトめ(px-4 sm:px-6)。兄弟要素は親の gap で間隔を空け、子要素ごとのマージンは使用しません。セクション間に <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 + 右側に1つのコントロール
オプションの通知ストリップ
プレート:flex flex-col gap-6
タイトル行には正確に1つのコントロールを配置します。pb-36 はコンテンツがビューポートの端に寄らないようにします。
グリッド:grid gap-3、レスポンシブカラム(統計は grid-cols-2 sm:grid-cols-4)。カードは固定のコンテンツ高を共有し、内部でスクロールします。グリッドのベースラインは決して壊れません。
レスポンシブ:390pxおよび1280pxでデザインします。水平方向のペアは sm: 以下でスタックし、アクションは最後に配置します。幅の広いコンテンツは、自身の overflow-x-auto コンテナ内でスクロールします。ページは決して横にスクロールしません。
設定:狭いタブレール + セクションパネルのコンテンツカラム。各セクションは見出しブロック(タイトル + 1つのグレー文、右側にオプションの単一アクション)で始まります。?tab=<id> を使用してタブにディープリンクします。
4. コンポーネント
スカークルサーフェス
2つの変数でパラメータ化された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 muted アイコン + text-sm font-medium text-foreground/80 タイトル
│ タイトルの中央線上のオプションチップ
│ "すべて表示 ›"(text-xs muted; ホバー:accent ウォッシュ、矢印が 2px スライド)
└── 内側スカークル:bg #f6f6f6、border、rounded-[22px] sm:rounded-[44px]、
固定高さ(h-44 = 5つの32px行)、overflow hidden
カードはデータに合わせて成長せず、長いリストは内部でスクロールします。オーバーフローは、右下に24pxのシェブロンチップを表示し、y:[0,2.5,0](1.4秒 easeInOut ループ)で上下に動き、スクロールの最後にフェードアウト(0.18秒、scale 0.7)します。行:32px、フルブリードのaccent-ウォッシュホバー、tabular-nums 値。
ボタン
ピル、font-medium、物理的に押された感触:active:translate-y-px active:scale-[0.98]。
- プライマリ:ベベルのあるアクセントブルー(背景/ボーダーは濃いインディゴに向かってカラーミックス、上部
inset 0 1px 0 rgba(255,255,255,0.22)、下部は暗いインセット)。ホバーで純粋なプライマリに明るくなります。 - セカンダリ:フラット
--secondary、透明ボーダー、ホバーで5%インクを混ぜます。主力のボタンです。 - ゴースト:muted テキストのみ。デストラクティブ:赤い塗りつぶし、確認ステップのみ。
- サイズ 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-ウォッシュホバー。アクティブなハイライトは1つの共有要素で移動(layoutId または幅/高さのバネ)、アイテムごとの再描画はありません。外部の pointerdown と Escape キーで閉じます。
モーダル
背景は約0.15秒でフェードイン。パネルは POP(400/26)で opacity 0, scale .96 からバネイン、380/28 で終了。マルチステップダイアログは1つのパネルを保持し、その高さが各ステップの測定サイズにアニメーションします(LAYOUT 550/40 + ResizeObserver)。ステップはスライドスルーします(x: ±12px、popLayout、0.16秒 easeOut フェード)。フッター:h-12 ストリップ、戻る(セカンダリ xs)+ プライマリ xs、右揃え。
タブバー
アクティブなピルは固定ウィンドウ。ラベルはその背後をスライドします。入ってくるラベルは移動方向から自身の幅の110%で入り、出ていくラベルは反対方向に固定130px移動します(ピルが長い単語に向かって成長する場合、パーセンテージでは不十分です)。ピルの幅/高さは、LAYOUTバネで測定されたラベルに追従します。全体で完全不透明度。マスクが作業を行います。
スケルトン
- クロームは待たせません。スケルトンになるのはデータ領域のみです。
- ピクセルマッチ:バーは、それが表すテキストと同じ行高さスロット内に配置されます(
flex h-6 items-centerがh-3バーをラップ)。スワップは何も動かしません。 - データはフォーカスで到着:
opacity ~0.4 + blur(4px)からシャープへのワンショットアニメーション。アニメーションであり、トランジションではありません(コンテンツはすでに最終状態でマウントされます。トランジションには前の値がありません)。その後はfilter: blur(0)を維持します。
バー:rounded bg-muted-foreground/15、背景位置のスイープ(2秒 linear 無限、-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>
1つの太字の主張、1つのグレーの文、1つのセカンダリアクション。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。アプリのクロームで0.2秒より長いトゥイーンはありません。遅いと感じたら、継続時間を延ばすのではなく、ダンピングを下げてください。
特徴的な動き:測定された高さの振り付け(1つの一定のパネル、ステップがスライドスルー)、移動するハイライト(1つの共有アクティブ要素、アイテムごとの再描画なし)、入場/退場の非対称性(退場は常に速い)、ホバーは色のみ(加えて最大2pxの矢印スライド。カードは決して持ち上がったり拡大縮小したりしない)、そして画面ごとに最大1つの小さなアンビエントループ。
ルール:マウント解除されるすべてのものに AnimatePresence を使用。prefers-reduced-motion を尊重(表示は visible に折りたたみ、ループは停止)。未測定のレイアウトをアニメーション化しない。レンダリング中に壁時計を読み取らない(分単位のバケットでティックするストアにサブスクライブ)。
6. ランディングページ
同じトークン、同じ…
…(翻訳のため、以降は省略)