kaze-design-system 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,14 +5,14 @@
5
5
 
6
6
  **Kaze (風)** は、管理画面・ダッシュボード・金融系アプリのための React デザインシステムです。**白と温かみのあるグレーを基調にした静かな配色**と、日本語組版への丁寧な最適化(PALT・和欧混植・読みやすい行間)、そしてコピペで完結する 63 のコンポーネントを 1 パッケージにまとめています。派手な色や装飾ではなく「読みやすさ」「情報密度」「長時間見続けても疲れないこと」を設計の中心に置いています。
7
7
 
8
- - 🎨 **63 コンポーネント** — フォーム、テーブル、チャート、ダッシュボード UI、ランディングページ、マーケティングパーツまで
9
- - 🌏 **日本語組版に最適化** — PALT グローバル適用、和欧混植、括弧ツメ、適切な line-height
10
- - 🌓 **ライト / ダーク両対応** — `data-theme` 属性で切替、`prefers-color-scheme` 連動
11
- - 🎯 **素の CSS + カスケードレイヤー** — `@layer tokens, reset, components, utilities` でカスケード管理、Tailwind/CSS-in-JS 不要
12
- - 📦 **ESM only + Tree-shakeable** — React 19 + TypeScript、peer deps は `react`, `react-dom`, `lucide-react` のみ
13
- - 🤖 **AI フレンドリー** — [`/llms.txt`](https://kaze-design-system.vercel.app/llms.txt) で全コンポーネント API を 1 ファイル配信
8
+ - **63 コンポーネント**: フォーム、テーブル、チャート、ダッシュボード UI、ランディングページ、マーケティングパーツまで
9
+ - **日本語組版に最適化**: PALT グローバル適用、和欧混植、括弧ツメ、適切な line-height
10
+ - **ライト / ダーク両対応**: `data-theme` 属性で切替、`prefers-color-scheme` 連動
11
+ - **素の CSS + カスケードレイヤー**: `@layer tokens, reset, components, utilities` でカスケード管理、Tailwind/CSS-in-JS 不要
12
+ - **ESM only + Tree-shakeable**: React 18 / 19 + TypeScript、peer deps は `react`, `react-dom`(`lucide-react` は任意)のみ
13
+ - **AI フレンドリー**: [`/llms.txt`](https://kaze-design-system.vercel.app/llms.txt) で全コンポーネント API を 1 ファイル配信
14
14
 
15
- ## 🚀 Links
15
+ ## Links
16
16
 
17
17
  | | |
18
18
  |---|---|
@@ -22,32 +22,32 @@
22
22
  | **npm パッケージ** | https://www.npmjs.com/package/kaze-design-system |
23
23
  | **GitHub** | https://github.com/yuki930/kaze-design-system |
24
24
 
25
- ## 🖼 Live Examples
25
+ ## Live Examples
26
26
 
27
27
  実際のアプリケーション/ページ単位のサンプル実装。Vercel にデプロイされています:
28
28
 
29
29
  ### ダッシュボード / 管理画面
30
- - [**分析ダッシュボード**](https://kaze-design-system.vercel.app/dashboard) — メトリック、チャート、テーブル組み合わせ
31
- - [**Analytics**](https://kaze-design-system.vercel.app/analytics) — KPI 一覧、BarList、Tracker
32
- - [**SaaS Stats**](https://kaze-design-system.vercel.app/saas-stats) — SaaS 系ダッシュボード
33
- - [**EC ダッシュボード**](https://kaze-design-system.vercel.app/ec-dashboard) — EC 管理画面
34
- - [**設定画面**](https://kaze-design-system.vercel.app/settings) — フォーム・FormField 活用例
30
+ - [**分析ダッシュボード**](https://kaze-design-system.vercel.app/dashboard): メトリック、チャート、テーブル組み合わせ
31
+ - [**Analytics**](https://kaze-design-system.vercel.app/analytics): KPI 一覧、BarList、Tracker
32
+ - [**SaaS Stats**](https://kaze-design-system.vercel.app/saas-stats): SaaS 系ダッシュボード
33
+ - [**EC ダッシュボード**](https://kaze-design-system.vercel.app/ec-dashboard): EC 管理画面
34
+ - [**設定画面**](https://kaze-design-system.vercel.app/settings): フォーム・FormField 活用例
35
35
 
36
36
  ### ランディング・マーケティング
37
- - [**LP (SaaS)**](https://kaze-design-system.vercel.app/lp) — Hero, Features, Pricing, Testimonial, FAQ, CTA
38
- - [**LP 2**](https://kaze-design-system.vercel.app/lp2) — 異なる雰囲気の LP
39
- - [**Pricing**](https://kaze-design-system.vercel.app/pricing) — 料金プラン
40
- - [**Blog**](https://kaze-design-system.vercel.app/blog) — 記事一覧・記事詳細
41
- - [**Jobs**](https://kaze-design-system.vercel.app/jobs) — 求人ページ
37
+ - [**LP (SaaS)**](https://kaze-design-system.vercel.app/lp): Hero, Features, Pricing, Testimonial, FAQ, CTA
38
+ - [**LP 2**](https://kaze-design-system.vercel.app/lp2): 異なる雰囲気の LP
39
+ - [**Pricing**](https://kaze-design-system.vercel.app/pricing): 料金プラン
40
+ - [**Blog**](https://kaze-design-system.vercel.app/blog): 記事一覧・記事詳細
41
+ - [**Jobs**](https://kaze-design-system.vercel.app/jobs): 求人ページ
42
42
 
43
43
  ### アプリケーション
44
- - [**Login**](https://kaze-design-system.vercel.app/login) — 認証画面
45
- - [**Todo**](https://kaze-design-system.vercel.app/todo) — シンプルな Todo アプリ
46
- - [**Shopping**](https://kaze-design-system.vercel.app/shopping) — EC 商品一覧
47
- - [**Article**](https://kaze-design-system.vercel.app/article) — 記事ページ
48
- - [**Help**](https://kaze-design-system.vercel.app/help) — ヘルプセンター
44
+ - [**Login**](https://kaze-design-system.vercel.app/login): 認証画面
45
+ - [**Todo**](https://kaze-design-system.vercel.app/todo): シンプルな Todo アプリ
46
+ - [**Shopping**](https://kaze-design-system.vercel.app/shopping): EC 商品一覧
47
+ - [**Article**](https://kaze-design-system.vercel.app/article): 記事ページ
48
+ - [**Help**](https://kaze-design-system.vercel.app/help): ヘルプセンター
49
49
 
50
- > **ESM only** — このパッケージは ES Modules のみを提供します。
50
+ > **ESM only**: このパッケージは ES Modules のみを提供します。
51
51
  > `"type": "module"` が設定された環境、または ESM 対応バンドラー(Vite, Next.js, webpack 5+)で使用してください。
52
52
 
53
53
  ## For AI assistants (Claude / Cursor / ChatGPT)
@@ -57,7 +57,7 @@ AI コーディングアシスタントは以下のファイルからライブ
57
57
  - **Short index**: https://kaze-design-system.vercel.app/llms.txt
58
58
  - **Full API reference**: https://kaze-design-system.vercel.app/llms-full.txt
59
59
 
60
- `llms-full.txt` には 62 コンポーネントの `Props` / `Usage` / カテゴリが機械可読形式で含まれており、Storybook を参照しなくても正確なコード提案が可能です。[llmstxt.org](https://llmstxt.org) 準拠。
60
+ `llms-full.txt` には 63 コンポーネントの `Props` / `Usage` / カテゴリが機械可読形式で含まれており、Storybook を参照しなくても正確なコード提案が可能です。[llmstxt.org](https://llmstxt.org) 準拠。
61
61
 
62
62
  ## インストール
63
63
 
@@ -65,7 +65,7 @@ AI コーディングアシスタントは以下のファイルからライブ
65
65
  npm install kaze-design-system
66
66
  ```
67
67
 
68
- Peer dependencies: `react@^19`, `react-dom@^19`, `lucide-react`
68
+ Peer dependencies: `react@^18 || ^19`, `react-dom@^18 || ^19`。`lucide-react` は任意(`Icon` に lucide のアイコンを渡す場合やサンプルコードで使用。コンポーネント本体は lucide なしで動きます)
69
69
 
70
70
  ## CSS の読み込み
71
71
 
@@ -117,8 +117,26 @@ function App() {
117
117
  }
118
118
  ```
119
119
 
120
+ ## レイヤー構成
121
+
122
+ kaze は1パッケージ・3レイヤーで構成されています。
123
+
124
+ - **Foundation**: デザイントークン(カラー・タイポグラフィ・スペーシング)と CSS。両レイヤー共通の土台
125
+ - **Application UI**: 管理画面・アプリ向けの52コンポーネント。`kaze-design-system` から import
126
+ - **Marketing**: LP・キャンペーンページ向けの11コンポーネント。`kaze-design-system/marketing` から import
127
+
128
+ ```tsx
129
+ // Application UI
130
+ import { Table, Sidebar, CommandPalette } from "kaze-design-system";
131
+
132
+ // Marketing(ルートからの import も引き続き使えます)
133
+ import { Hero, PricingGrid, CTABanner } from "kaze-design-system/marketing";
134
+ ```
135
+
120
136
  ## コンポーネント一覧(63 コンポーネント)
121
137
 
138
+ ### Application UI(52)
139
+
122
140
  | カテゴリ | コンポーネント |
123
141
  |---|---|
124
142
  | **フォーム** | Button, FAB, Input, NumberField, Select, Checkbox, Radio, Textarea, Switch, FormField, FilterPill |
@@ -129,6 +147,11 @@ function App() {
129
147
  | **オーバーレイ** | Dialog, Tooltip, HelpButton, Dropdown, CommandPalette |
130
148
  | **レイアウト** | AppLayout, Divider, Grid, Search, Logo, Icon, Heading, Text |
131
149
  | **リスト** | List, DescriptionList, Timeline |
150
+
151
+ ### Marketing(11)
152
+
153
+ | カテゴリ | コンポーネント |
154
+ |---|---|
132
155
  | **マーケティング** | Navbar, Hero, Section, SplitSection, FeatureGrid, Stats, Pricing, Testimonial, FAQ, CTABanner, LPFooter |
133
156
 
134
157
  詳細は [kaze-design-system.vercel.app/docs/components](https://kaze-design-system.vercel.app/docs/components) を参照してください。
@@ -136,13 +159,30 @@ function App() {
136
159
  ## Hooks
137
160
 
138
161
  ```tsx
139
- import { ThemeProvider, useTheme, useFocusTrap } from "kaze-design-system";
140
- import { useLegendToggle } from "kaze-design-system/hooks";
162
+ import { ThemeProvider, useTheme } from "kaze-design-system";
163
+ import { useFocusTrap, useLegendToggle } from "kaze-design-system/hooks";
141
164
  ```
142
165
 
143
- - `useTheme` — ライト/ダークの切り替え
144
- - `useFocusTrap` — モーダル向けフォーカストラップ
145
- - `useLegendToggle` — Recharts の凡例クリックで系列 show/hide を切り替え
166
+ - `useTheme`: ライト/ダークの切り替え
167
+ - `useFocusTrap`: モーダル向けフォーカストラップ
168
+ - `useLegendToggle`: Recharts の凡例クリックで系列 show/hide を切り替え
169
+
170
+ ## バンドルサイズ
171
+
172
+ 63 コンポーネント全部込みで **JS 38 kB gzip + CSS 23 kB gzip ≒ 61 kB gz**。ただし、個別 import すれば使った分だけが bundle に入ります。
173
+
174
+ | 区分 | 生サイズ | gzip |
175
+ |---|---:|---:|
176
+ | JS: 全コンポーネント合計 (tree-shake 対象) | 110 kB | **38 kB** |
177
+ | JS: 最大 (CommandPalette) | 6.4 kB | 1.9 kB |
178
+ | JS: 中央値 (StatusBadge) | 896 B | 476 B |
179
+ | JS: 最小 (Textarea) | 481 B | 280 B |
180
+ | CSS: `tokens.css` | 20.1 kB | 4.9 kB |
181
+ | CSS: `components.css` | 101 kB | 13.6 kB |
182
+ | CSS: `utilities.css` | 18.9 kB | 3.4 kB |
183
+ | CSS: `reset.css` | 3.7 kB | 1.1 kB |
184
+
185
+ `npm run size` でローカルで最新のサイズを計測できます。React / lucide-react 等の peer dependencies は含まれていません。
146
186
 
147
187
  ## デザイントークン
148
188
 
@@ -153,12 +193,33 @@ CSS カスタムプロパティベース:
153
193
  - タイポグラフィ: `--font-size-*`, `--font-weight-*`
154
194
  - ボーダー: `--radius-*`
155
195
  - シャドウ: `--shadow-*`
196
+ - 密度: `--density-*`(コントロール高さ・行パディング・UI フォントサイズ。下記「密度」参照)
197
+
198
+ JS から値を参照したい場合は `kaze-design-system/tokens` から import できます。
199
+
200
+ ```ts
201
+ import { space } from "kaze-design-system/tokens";
202
+ ```
156
203
 
157
204
  ## テーマ(ライト / ダーク)
158
205
 
159
206
  `data-theme="light"` / `data-theme="dark"` で切り替え。
160
207
  `ThemeProvider` で自動管理。`prefers-color-scheme` による OS 設定連動にも対応。
161
208
 
209
+ `data-palette="cool"` を付けると warm zinc から cool slate のパレットに切り替わります(ライト / ダークどちらとも併用可)。
210
+
211
+ ## 密度(Density)
212
+
213
+ `data-density="compact"` を祖先要素(`<html>` やパネル単位)に付けると、Input / Select / Table / List / Tabs / Dropdown / Sidebar などが詰まった表示になります。テーマ・パレットとは独立に組み合わせられます。
214
+
215
+ ```html
216
+ <html data-theme="dark" data-density="compact">
217
+ ```
218
+
219
+ ## CSS のレイヤーについて
220
+
221
+ `css/all`(`kaze.css`)は `@layer tokens, reset, components, utilities` でラップされています。`css/components` などを個別に読み込むとレイヤーは付きません。また、レイヤー外のスタイル(CSS Modules など)は常にレイヤー内のスタイルより優先されます。
222
+
162
223
  ## 開発
163
224
 
164
225
  ```bash
@@ -168,11 +229,13 @@ npm run build:lib # ライブラリビルド(dist/ に出力)
168
229
  npm run docs:llms # llms.txt / llms-full.txt を再生成
169
230
  npm run storybook # Storybook 起動
170
231
  npm run typecheck # TypeScript 型チェック
232
+ npm run test # ユニットテスト(jsdom)
233
+ npm run test:storybook # Storybook ブラウザテスト(Playwright)
171
234
  ```
172
235
 
173
236
  ### リリース
174
237
 
175
- main への push 時に GitHub Actions が自動で `npm version patch` → build → `npm publish` を実行します。ライブラリ本体(`src/components/**`, `src/hooks/**`, CSS ファイル等)への変更がある場合のみトリガーされます。
238
+ main への push 時に GitHub Actions が Conventional Commits からバージョン(patch / minor / major)を判定し、build → `npm publish` → release コミットとタグの push を実行します。ライブラリ本体(`src/components/**`, `src/hooks/**`, CSS ファイル、`package.json` 等)への変更がある場合のみトリガーされます。詳しくは [CONTRIBUTING.md](./CONTRIBUTING.md) を参照してください。
176
239
 
177
240
  ## コントリビュート
178
241
 
package/components.css CHANGED
@@ -65,9 +65,9 @@
65
65
  }
66
66
 
67
67
  .btn--md {
68
- height: 2.25rem;
68
+ height: var(--density-control-height);
69
69
  padding-inline: var(--space-4);
70
- font-size: var(--font-size-sm);
70
+ font-size: var(--density-font-size-ui);
71
71
  }
72
72
 
73
73
  .btn--lg {
@@ -290,9 +290,9 @@
290
290
  .input {
291
291
  display: flex;
292
292
  width: 100%;
293
- height: 2.25rem;
293
+ height: var(--density-control-height);
294
294
  padding-inline: var(--space-3);
295
- font-size: var(--font-size-sm);
295
+ font-size: var(--density-font-size-ui);
296
296
  background-color: var(--color-bg-tertiary);
297
297
  border: 1px solid transparent;
298
298
  border-radius: var(--radius-field);
@@ -322,10 +322,10 @@
322
322
  .select {
323
323
  display: flex;
324
324
  width: 100%;
325
- height: 2.25rem;
325
+ height: var(--density-control-height);
326
326
  padding-inline: var(--space-3);
327
327
  padding-right: var(--space-8);
328
- font-size: var(--font-size-sm);
328
+ font-size: var(--density-font-size-ui);
329
329
  background-color: var(--color-bg-tertiary);
330
330
  border: 1px solid transparent;
331
331
  border-radius: var(--radius-field);
@@ -348,6 +348,11 @@
348
348
  [data-theme="dark"] .select {
349
349
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a8a29e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
350
350
  }
351
+ @media (prefers-color-scheme: dark) {
352
+ :root:not([data-theme="light"]) .select {
353
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a8a29e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
354
+ }
355
+ }
351
356
 
352
357
  /* ── Textarea ────────────────────────────────────────────── */
353
358
  .textarea {
@@ -355,7 +360,7 @@
355
360
  width: 100%;
356
361
  min-height: 5rem;
357
362
  padding: var(--space-2) var(--space-3);
358
- font-size: var(--font-size-sm);
363
+ font-size: var(--density-font-size-ui);
359
364
  line-height: var(--line-height-relaxed);
360
365
  background-color: var(--color-bg-tertiary);
361
366
  border: 1px solid transparent;
@@ -453,10 +458,20 @@
453
458
  [data-theme="dark"] .checkbox__input:checked {
454
459
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
455
460
  }
461
+ @media (prefers-color-scheme: dark) {
462
+ :root:not([data-theme="light"]) .checkbox__input:checked {
463
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
464
+ }
465
+ }
456
466
 
457
467
  [data-theme="dark"] .checkbox__input:indeterminate {
458
468
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
459
469
  }
470
+ @media (prefers-color-scheme: dark) {
471
+ :root:not([data-theme="light"]) .checkbox__input:indeterminate {
472
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23000000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
473
+ }
474
+ }
460
475
 
461
476
  /* ── Radio ───────────────────────────────────────────────── */
462
477
  .radio {
@@ -611,7 +626,8 @@
611
626
 
612
627
  .table {
613
628
  width: 100%;
614
- font-size: var(--font-size-sm);
629
+ font-size: var(--density-font-size-ui);
630
+ line-height: var(--density-line-height-ui);
615
631
  }
616
632
 
617
633
  .table th {
@@ -621,12 +637,12 @@
621
637
  text-transform: uppercase;
622
638
  letter-spacing: var(--letter-spacing-wider);
623
639
  color: var(--color-fg-tertiary);
624
- padding: var(--space-3) var(--space-4);
640
+ padding: var(--density-row-py) var(--density-row-px);
625
641
  border-bottom: 1px solid var(--color-border);
626
642
  }
627
643
 
628
644
  .table td {
629
- padding: var(--space-3) var(--space-4);
645
+ padding: var(--density-row-py) var(--density-row-px);
630
646
  border-bottom: 1px solid var(--color-border-subtle);
631
647
  color: var(--color-fg);
632
648
  vertical-align: middle;
@@ -685,6 +701,11 @@
685
701
  [data-theme="dark"] .avatar img {
686
702
  filter: brightness(0.85) contrast(1.1);
687
703
  }
704
+ @media (prefers-color-scheme: dark) {
705
+ :root:not([data-theme="light"]) .avatar img {
706
+ filter: brightness(0.85) contrast(1.1);
707
+ }
708
+ }
688
709
 
689
710
  /* ── Tabs ────────────────────────────────────────────────── */
690
711
  .tabs {
@@ -697,8 +718,8 @@
697
718
  display: inline-flex;
698
719
  align-items: center;
699
720
  justify-content: center;
700
- padding: var(--space-2) var(--space-3);
701
- font-size: var(--font-size-sm);
721
+ padding: var(--density-tab-py) var(--space-3);
722
+ font-size: var(--density-font-size-ui);
702
723
  font-weight: var(--font-weight-normal);
703
724
  color: var(--color-fg-tertiary);
704
725
  cursor: pointer;
@@ -750,9 +771,9 @@
750
771
  .nav-item {
751
772
  display: flex;
752
773
  align-items: center;
753
- gap: var(--space-2-5);
754
- padding: var(--space-2) var(--space-3);
755
- font-size: var(--font-size-sm);
774
+ gap: var(--density-nav-gap);
775
+ padding: var(--density-nav-py) var(--space-3);
776
+ font-size: var(--density-font-size-ui);
756
777
  font-weight: var(--font-weight-medium);
757
778
  color: var(--color-fg-tertiary);
758
779
  border-radius: var(--radius-md);
@@ -1398,12 +1419,7 @@
1398
1419
  .feature-card__icon {
1399
1420
  display: inline-flex;
1400
1421
  align-items: center;
1401
- justify-content: center;
1402
- width: 2.5rem;
1403
- height: 2.5rem;
1404
- border-radius: var(--radius-lg);
1405
- background: var(--color-bg-tertiary);
1406
- color: var(--color-fg);
1422
+ color: var(--color-fg-secondary);
1407
1423
  margin-bottom: var(--space-4);
1408
1424
  }
1409
1425
 
@@ -2266,14 +2282,19 @@
2266
2282
 
2267
2283
  /* ── Prose: mark ───────────────────────────────────────────── */
2268
2284
  .prose mark {
2269
- background: var(--color-amber-100);
2285
+ background: var(--amber-100);
2270
2286
  color: inherit;
2271
2287
  padding: var(--space-0-5) var(--space-1);
2272
2288
  border-radius: var(--radius-sm);
2273
2289
  }
2274
2290
 
2275
2291
  [data-theme="dark"] .prose mark {
2276
- background: var(--color-amber-900);
2292
+ background: var(--amber-900);
2293
+ }
2294
+ @media (prefers-color-scheme: dark) {
2295
+ :root:not([data-theme="light"]) .prose mark {
2296
+ background: var(--amber-900);
2297
+ }
2277
2298
  }
2278
2299
 
2279
2300
  /* ── Prose: kbd ────────────────────────────────────────────── */
@@ -2593,8 +2614,9 @@
2593
2614
  display: flex;
2594
2615
  align-items: center;
2595
2616
  gap: var(--space-3);
2596
- padding: var(--space-3) 0;
2597
- font-size: var(--font-size-sm);
2617
+ padding: var(--density-row-py) 0;
2618
+ font-size: var(--density-font-size-ui);
2619
+ line-height: var(--density-line-height-ui);
2598
2620
  color: var(--color-fg);
2599
2621
  }
2600
2622
 
@@ -2634,7 +2656,7 @@
2634
2656
  .list-item--interactive {
2635
2657
  cursor: pointer;
2636
2658
  border-radius: var(--radius-md);
2637
- padding: var(--space-3) var(--space-3);
2659
+ padding: var(--density-row-py) var(--space-3);
2638
2660
  transition: background var(--duration-fast) var(--ease-default);
2639
2661
  }
2640
2662
 
@@ -3204,6 +3226,13 @@
3204
3226
  line-height: var(--line-height-relaxed);
3205
3227
  }
3206
3228
 
3229
+ .alert--default {
3230
+ background: var(--color-surface);
3231
+ border-color: var(--color-border);
3232
+ }
3233
+
3234
+ .alert--default .alert__icon { color: var(--color-fg-secondary); }
3235
+
3207
3236
  .alert--info {
3208
3237
  background: var(--color-info-light);
3209
3238
  border-color: var(--color-info);
@@ -3552,6 +3581,7 @@
3552
3581
  align-items: center;
3553
3582
  border: 1px solid var(--color-border);
3554
3583
  border-radius: var(--radius-md);
3584
+ height: var(--density-control-height);
3555
3585
  background: var(--color-surface);
3556
3586
  transition: border-color var(--duration-normal) var(--ease-default),
3557
3587
  box-shadow var(--duration-normal) var(--ease-default);
@@ -3576,11 +3606,12 @@
3576
3606
  min-width: 0;
3577
3607
  border: none;
3578
3608
  background: transparent;
3609
+ height: 100%;
3579
3610
  font-family: var(--font-mono);
3580
- font-size: var(--font-size-sm);
3611
+ font-size: var(--density-font-size-ui);
3581
3612
  font-variant-numeric: tabular-nums;
3582
3613
  color: var(--color-fg);
3583
- padding: var(--space-2) var(--space-2);
3614
+ padding: 0 var(--space-2);
3584
3615
  text-align: right;
3585
3616
  outline: none;
3586
3617
  }
@@ -3607,7 +3638,7 @@
3607
3638
  align-items: center;
3608
3639
  justify-content: center;
3609
3640
  width: 32px;
3610
- height: 36px;
3641
+ height: 100%;
3611
3642
  border: none;
3612
3643
  background: var(--color-bg-secondary);
3613
3644
  color: var(--color-fg-secondary);
@@ -3880,6 +3911,14 @@
3880
3911
  color: var(--color-warning);
3881
3912
  }
3882
3913
 
3914
+ .toast--info {
3915
+ border-left: 3px solid var(--color-info);
3916
+ }
3917
+
3918
+ .toast--info .toast__icon {
3919
+ color: var(--color-info);
3920
+ }
3921
+
3883
3922
  /* ── Dropdown ───────────────────────────────────────────── */
3884
3923
 
3885
3924
  .dropdown__menu {
@@ -3902,8 +3941,8 @@
3902
3941
  display: flex;
3903
3942
  align-items: center;
3904
3943
  gap: var(--space-2);
3905
- padding: var(--space-1-5) var(--space-3);
3906
- font-size: var(--font-size-sm);
3944
+ padding: var(--density-dropdown-py) var(--space-3);
3945
+ font-size: var(--density-font-size-ui);
3907
3946
  border-radius: var(--radius-md);
3908
3947
  cursor: pointer;
3909
3948
  color: var(--color-fg);
@@ -4016,8 +4055,8 @@
4016
4055
  .help-button:hover,
4017
4056
  .help-button[aria-expanded="true"] {
4018
4057
  color: var(--color-fg);
4019
- border-color: var(--color-border-strong, var(--color-fg-tertiary));
4020
- background: var(--color-bg-subtle, var(--color-bg));
4058
+ border-color: var(--color-border-strong);
4059
+ background: var(--color-surface-hover);
4021
4060
  }
4022
4061
 
4023
4062
  .help-button:focus-visible {
@@ -4118,7 +4157,7 @@
4118
4157
  }
4119
4158
 
4120
4159
  .disclosure__trigger:hover:not(:disabled) {
4121
- background: var(--color-bg-subtle, rgba(0, 0, 0, 0.04));
4160
+ background: var(--color-surface-hover);
4122
4161
  }
4123
4162
 
4124
4163
  .disclosure__trigger:focus-visible {
@@ -1,14 +1,28 @@
1
1
  "use client";
2
2
  import { jsxs, jsx } from "react/jsx-runtime";
3
3
  import { forwardRef, useState, useCallback, useId, useMemo } from "react";
4
- import { ChevronRight, Plus } from "lucide-react";
5
4
  import { cn } from "../../lib/utils.js";
5
+ const iconSvgProps = {
6
+ width: 16,
7
+ height: 16,
8
+ viewBox: "0 0 24 24",
9
+ fill: "none",
10
+ stroke: "currentColor",
11
+ strokeWidth: 2,
12
+ strokeLinecap: "round",
13
+ strokeLinejoin: "round",
14
+ className: "disclosure__icon-svg",
15
+ "aria-hidden": true
16
+ };
6
17
  function renderIcon(icon) {
7
18
  if (icon === "chevron" || icon == null) {
8
- return /* @__PURE__ */ jsx(ChevronRight, { size: 16, className: "disclosure__icon-svg" });
19
+ return /* @__PURE__ */ jsx("svg", { ...iconSvgProps, children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
9
20
  }
10
21
  if (icon === "plus") {
11
- return /* @__PURE__ */ jsx(Plus, { size: 16, className: "disclosure__icon-svg" });
22
+ return /* @__PURE__ */ jsxs("svg", { ...iconSvgProps, children: [
23
+ /* @__PURE__ */ jsx("path", { d: "M5 12h14" }),
24
+ /* @__PURE__ */ jsx("path", { d: "M12 5v14" })
25
+ ] });
12
26
  }
13
27
  return icon;
14
28
  }
@@ -33,9 +33,6 @@ const Dropdown = forwardRef(
33
33
  },
34
34
  [isControlled, onOpenChange]
35
35
  );
36
- const registerItem = useCallback(() => {
37
- return itemCountRef.current++;
38
- }, []);
39
36
  const resetItems = useCallback(() => {
40
37
  itemCountRef.current = 0;
41
38
  }, []);
@@ -48,8 +45,6 @@ const Dropdown = forwardRef(
48
45
  triggerRef,
49
46
  highlightedIndex,
50
47
  setHighlightedIndex,
51
- itemCount: itemCountRef.current,
52
- registerItem,
53
48
  resetItems
54
49
  },
55
50
  children: /* @__PURE__ */ jsx("div", { ref, style: { display: "contents" }, children })
@@ -92,6 +92,25 @@ function ToastItem(props) {
92
92
  /* @__PURE__ */ jsx("path", { d: "M12 17h.01" })
93
93
  ]
94
94
  }
95
+ ),
96
+ data.variant === "info" && /* @__PURE__ */ jsxs(
97
+ "svg",
98
+ {
99
+ xmlns: "http://www.w3.org/2000/svg",
100
+ width: "16",
101
+ height: "16",
102
+ viewBox: "0 0 24 24",
103
+ fill: "none",
104
+ stroke: "currentColor",
105
+ strokeWidth: "2",
106
+ strokeLinecap: "round",
107
+ strokeLinejoin: "round",
108
+ children: [
109
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
110
+ /* @__PURE__ */ jsx("path", { d: "M12 16v-4" }),
111
+ /* @__PURE__ */ jsx("path", { d: "M12 8h.01" })
112
+ ]
113
+ }
95
114
  )
96
115
  ] }),
97
116
  /* @__PURE__ */ jsxs("div", { className: "toast__content", children: [
@@ -0,0 +1,33 @@
1
+ "use client";
2
+ import { CTABanner } from "./components/CTABanner/CTABanner.js";
3
+ import { FAQ, FAQItem } from "./components/FAQ/FAQ.js";
4
+ import { FeatureCard, FeatureGrid } from "./components/FeatureGrid/FeatureGrid.js";
5
+ import { Hero } from "./components/Hero/Hero.js";
6
+ import { LPFooter } from "./components/LPFooter/LPFooter.js";
7
+ import { Navbar, NavbarLink, NavbarLinks } from "./components/Navbar/Navbar.js";
8
+ import { PricingCard, PricingGrid } from "./components/Pricing/Pricing.js";
9
+ import { Section, SectionHeader } from "./components/Section/Section.js";
10
+ import { SplitSection } from "./components/SplitSection/SplitSection.js";
11
+ import { StatItem, Stats } from "./components/Stats/Stats.js";
12
+ import { TestimonialCard, TestimonialGrid } from "./components/Testimonial/Testimonial.js";
13
+ export {
14
+ CTABanner,
15
+ FAQ,
16
+ FAQItem,
17
+ FeatureCard,
18
+ FeatureGrid,
19
+ Hero,
20
+ LPFooter,
21
+ Navbar,
22
+ NavbarLink,
23
+ NavbarLinks,
24
+ PricingCard,
25
+ PricingGrid,
26
+ Section,
27
+ SectionHeader,
28
+ SplitSection,
29
+ StatItem,
30
+ Stats,
31
+ TestimonialCard,
32
+ TestimonialGrid
33
+ };
package/dist/tokens.js CHANGED
@@ -78,9 +78,21 @@ const radius = {
78
78
  "2xl": "var(--radius-2xl)",
79
79
  full: "var(--radius-full)"
80
80
  };
81
+ const density = {
82
+ controlHeight: "var(--density-control-height)",
83
+ rowPy: "var(--density-row-py)",
84
+ rowPx: "var(--density-row-px)",
85
+ fontSizeUi: "var(--density-font-size-ui)",
86
+ lineHeightUi: "var(--density-line-height-ui)",
87
+ navPy: "var(--density-nav-py)",
88
+ navGap: "var(--density-nav-gap)",
89
+ dropdownPy: "var(--density-dropdown-py)",
90
+ tabPy: "var(--density-tab-py)"
91
+ };
81
92
  export {
82
93
  chart,
83
94
  color,
95
+ density,
84
96
  radius,
85
97
  space
86
98
  };
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode, type HTMLAttributes } from "react";
2
- export type AlertVariant = "info" | "positive" | "warning" | "negative";
2
+ export type AlertVariant = "default" | "positive" | "negative" | "warning" | "info";
3
3
  export interface AlertProps extends HTMLAttributes<HTMLDivElement> {
4
4
  variant?: AlertVariant;
5
5
  icon?: ReactNode;
@@ -3,7 +3,7 @@ export type BadgeVariant = "default" | "positive" | "negative" | "warning" | "in
3
3
  export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
4
4
  variant?: BadgeVariant;
5
5
  dot?: boolean;
6
- /** Solid (inverted) style — filled background with white text */
6
+ /** Solid (inverted) style: filled background with white text */
7
7
  solid?: boolean;
8
8
  /** When true, adds role="status" for live-region announcements */
9
9
  live?: boolean;
@@ -2,7 +2,7 @@ import { type HTMLAttributes, type RefObject } from "react";
2
2
  export interface DialogProps extends HTMLAttributes<HTMLDivElement> {
3
3
  open: boolean;
4
4
  onClose: () => void;
5
- /** @deprecated useFocusTrap handles focus restoration automatically. Kept for backward compat — ignored. */
5
+ /** @deprecated useFocusTrap handles focus restoration automatically. Kept for backward compat: ignored. */
6
6
  returnFocusRef?: RefObject<HTMLElement | null>;
7
7
  }
8
8
  export declare const Dialog: import("react").ForwardRefExoticComponent<DialogProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -1,5 +1,5 @@
1
1
  import { type HTMLAttributes } from "react";
2
- import { space } from "@/tokens/tokens";
2
+ import { space } from "../../tokens/tokens";
3
3
  export type GridColumns = 1 | 2 | 3 | 4 | 5 | 6;
4
4
  type SpaceToken = keyof typeof space;
5
5
  export type GridGap = "xs" | "sm" | "md" | "lg" | "xl" | SpaceToken | number | (string & {});
@@ -10,11 +10,11 @@ export interface GridProps extends HTMLAttributes<HTMLDivElement> {
10
10
  columnsMd?: GridColumns;
11
11
  /** Column count at ≤640px */
12
12
  columnsSm?: GridColumns;
13
- /** Gap between items — semantic token ("sm", "md", etc.), space token (e.g. 4), or any CSS value */
13
+ /** Gap between items: semantic token ("sm", "md", etc.), space token (e.g. 4), or any CSS value */
14
14
  gap?: GridGap;
15
- /** Gap at ≤1024px — falls back to gap */
15
+ /** Gap at ≤1024px: falls back to gap */
16
16
  gapMd?: GridGap;
17
- /** Gap at ≤640px — falls back to gapMd, then gap */
17
+ /** Gap at ≤640px: falls back to gapMd, then gap */
18
18
  gapSm?: GridGap;
19
19
  }
20
20
  export declare const Grid: import("react").ForwardRefExoticComponent<GridProps & import("react").RefAttributes<HTMLDivElement>>;
@@ -3,9 +3,9 @@ export interface HeroProps extends HTMLAttributes<HTMLElement> {
3
3
  badge?: string;
4
4
  title: string;
5
5
  subtitle?: string;
6
- /** Background color — any CSS value, e.g. "var(--blue-500)" */
6
+ /** Background color: any CSS value, e.g. "var(--blue-500)" */
7
7
  bg?: string;
8
- /** Background image — URL or CSS gradient, e.g. "url(...)" or "linear-gradient(...)" */
8
+ /** Background image: URL or CSS gradient, e.g. "url(...)" or "linear-gradient(...)" */
9
9
  bgImage?: string;
10
10
  /** Dark overlay opacity (0–1). true = 0.5. Useful for readability on images. */
11
11
  overlay?: boolean | number;
@@ -1,12 +1,17 @@
1
- import { type HTMLAttributes } from "react";
2
- import type { LucideIcon } from "lucide-react";
1
+ import { type ComponentType, type HTMLAttributes } from "react";
3
2
  export type IconSize = "xs" | "sm" | "md" | "lg" | "xl";
3
+ /** Any icon component accepting `size` / `strokeWidth` (e.g. a lucide-react icon).
4
+ * Typed structurally so the published types don't require lucide-react. */
5
+ export type IconComponent = ComponentType<{
6
+ size?: number | string;
7
+ strokeWidth?: number | string;
8
+ }>;
4
9
  export interface IconProps extends HTMLAttributes<HTMLSpanElement> {
5
- /** A lucide-react icon component */
6
- icon: LucideIcon;
10
+ /** An icon component such as a lucide-react icon */
11
+ icon: IconComponent;
7
12
  /** Size of the icon */
8
13
  size?: IconSize;
9
- /** Accessible label — if omitted, the icon is decorative (aria-hidden) */
14
+ /** Accessible label. If omitted, the icon is decorative (aria-hidden) */
10
15
  label?: string;
11
16
  }
12
17
  export declare const Icon: import("react").ForwardRefExoticComponent<IconProps & import("react").RefAttributes<HTMLSpanElement>>;
@@ -1,2 +1,2 @@
1
1
  export { Icon } from "./Icon";
2
- export type { IconProps, IconSize } from "./Icon";
2
+ export type { IconComponent, IconProps, IconSize } from "./Icon";
@@ -23,7 +23,7 @@ export interface NumberFieldProps extends Omit<InputHTMLAttributes<HTMLInputElem
23
23
  error?: boolean;
24
24
  /** Error message */
25
25
  errorMessage?: string;
26
- /** Prefix text (e.g. "¥") — auto-set by currency */
26
+ /** Prefix text (e.g. "¥"): auto-set by currency */
27
27
  prefix?: string;
28
28
  /** Suffix text (e.g. "%", "件") */
29
29
  suffix?: string;
@@ -26,7 +26,7 @@ export declare const Tabs: import("react").ForwardRefExoticComponent<TabsProps &
26
26
  export interface TabProps extends ButtonHTMLAttributes<HTMLButtonElement> {
27
27
  active?: boolean;
28
28
  id?: string;
29
- /** Tab の value — aria-controls / id の自動生成に使用 */
29
+ /** Tab の value。aria-controls / id の自動生成に使用 */
30
30
  value?: string;
31
31
  }
32
32
  export declare const Tab: import("react").ForwardRefExoticComponent<TabProps & import("react").RefAttributes<HTMLButtonElement>>;
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from "react";
2
- export type ToastVariant = "default" | "positive" | "negative" | "warning";
2
+ export type ToastVariant = "default" | "positive" | "negative" | "warning" | "info";
3
3
  export interface ToastData {
4
4
  id: string;
5
5
  title: string;
@@ -57,7 +57,7 @@ export type { DropdownProps, DropdownTriggerProps, DropdownMenuProps, DropdownIt
57
57
  export { CommandPalette } from "./CommandPalette";
58
58
  export type { CommandPaletteProps, Command } from "./CommandPalette";
59
59
  export { Icon } from "./Icon";
60
- export type { IconProps, IconSize } from "./Icon";
60
+ export type { IconComponent, IconProps, IconSize } from "./Icon";
61
61
  export { Heading } from "./Heading";
62
62
  export type { HeadingProps, HeadingLevel } from "./Heading";
63
63
  export { Text } from "./Text";
@@ -0,0 +1,22 @@
1
+ export { Navbar, NavbarLinks, NavbarLink } from "./Navbar";
2
+ export type { NavbarProps, NavbarLinksProps, NavbarLinkProps } from "./Navbar";
3
+ export { Hero } from "./Hero";
4
+ export type { HeroProps } from "./Hero";
5
+ export { Section, SectionHeader } from "./Section";
6
+ export type { SectionProps, SectionHeaderProps, SectionSize, SectionBackground } from "./Section";
7
+ export { SplitSection } from "./SplitSection";
8
+ export type { SplitSectionProps } from "./SplitSection";
9
+ export { FeatureGrid, FeatureCard } from "./FeatureGrid";
10
+ export type { FeatureGridProps, FeatureCardProps, FeatureGridColumns } from "./FeatureGrid";
11
+ export { Stats, StatItem } from "./Stats";
12
+ export type { StatsProps, StatItemProps } from "./Stats";
13
+ export { PricingGrid, PricingCard } from "./Pricing";
14
+ export type { PricingGridProps, PricingCardProps } from "./Pricing";
15
+ export { TestimonialGrid, TestimonialCard } from "./Testimonial";
16
+ export type { TestimonialGridProps, TestimonialCardProps } from "./Testimonial";
17
+ export { FAQ, FAQItem } from "./FAQ";
18
+ export type { FAQProps, FAQItemProps } from "./FAQ";
19
+ export { CTABanner } from "./CTABanner";
20
+ export type { CTABannerProps } from "./CTABanner";
21
+ export { LPFooter } from "./LPFooter";
22
+ export type { LPFooterProps, FooterColumn } from "./LPFooter";
@@ -82,5 +82,18 @@ export declare const radius: {
82
82
  readonly "2xl": "var(--radius-2xl)";
83
83
  readonly full: "var(--radius-full)";
84
84
  };
85
+ /** Switched by `data-density="compact"` on an ancestor. */
86
+ export declare const density: {
87
+ readonly controlHeight: "var(--density-control-height)";
88
+ readonly rowPy: "var(--density-row-py)";
89
+ readonly rowPx: "var(--density-row-px)";
90
+ readonly fontSizeUi: "var(--density-font-size-ui)";
91
+ readonly lineHeightUi: "var(--density-line-height-ui)";
92
+ readonly navPy: "var(--density-nav-py)";
93
+ readonly navGap: "var(--density-nav-gap)";
94
+ readonly dropdownPy: "var(--density-dropdown-py)";
95
+ readonly tabPy: "var(--density-tab-py)";
96
+ };
85
97
  export type Theme = "light" | "dark";
86
98
  export type Palette = "warm" | "cool";
99
+ export type Density = "default" | "compact";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaze-design-system",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,7 +12,7 @@
12
12
  "bugs": {
13
13
  "url": "https://github.com/yuki930/kaze-design-system/issues"
14
14
  },
15
- "description": "Minimal React design system for dashboards and Japanese UI — quiet neutral grays, 63 components, light/dark themes, typography tuned for CJK text",
15
+ "description": "Minimal React design system for dashboards, landing pages and Japanese UI: warm neutral grays, 63 components, light/dark themes, typography tuned for CJK text",
16
16
  "keywords": [
17
17
  "design-system",
18
18
  "react",
@@ -32,6 +32,10 @@
32
32
  "types": "./dist/types/components/index.d.ts",
33
33
  "import": "./dist/index.js"
34
34
  },
35
+ "./marketing": {
36
+ "types": "./dist/types/components/marketing.d.ts",
37
+ "import": "./dist/marketing.js"
38
+ },
35
39
  "./hooks": {
36
40
  "types": "./dist/types/hooks/index.d.ts",
37
41
  "import": "./dist/hooks.js"
@@ -40,6 +44,7 @@
40
44
  "types": "./dist/types/tokens/tokens.d.ts",
41
45
  "import": "./dist/tokens.js"
42
46
  },
47
+ "./package.json": "./package.json",
43
48
  "./css/tokens": "./tokens.css",
44
49
  "./css/reset": "./reset.css",
45
50
  "./css/components": "./components.css",
@@ -54,6 +59,9 @@
54
59
  "utilities.css",
55
60
  "kaze.css"
56
61
  ],
62
+ "engines": {
63
+ "node": ">=18"
64
+ },
57
65
  "sideEffects": [
58
66
  "*.css"
59
67
  ],
@@ -63,7 +71,10 @@
63
71
  "build": "next build",
64
72
  "docs:llms": "node scripts/generate-llms.mjs",
65
73
  "docs:stories": "node scripts/generate-stories.mjs",
66
- "test": "vitest run",
74
+ "size": "npm run build:lib && node scripts/measure-size.mjs",
75
+ "test": "vitest run --project unit",
76
+ "test:storybook": "vitest run --project storybook",
77
+ "test:all": "vitest run",
67
78
  "test:watch": "vitest",
68
79
  "start": "next start",
69
80
  "build:lib": "rm -rf dist && vite build --config vite.config.lib.ts && tsc -p tsconfig.build.json",
@@ -71,12 +82,6 @@
71
82
  "build-storybook": "storybook build",
72
83
  "typecheck": "tsc --noEmit"
73
84
  },
74
- "dependencies": {
75
- "lucide-react": "^0.575.0",
76
- "next": "^16.1.6",
77
- "react": "^19.2.4",
78
- "react-dom": "^19.2.4"
79
- },
80
85
  "peerDependencies": {
81
86
  "lucide-react": "^0.575.0",
82
87
  "react": "^18.0.0 || ^19.0.0",
@@ -104,7 +109,11 @@
104
109
  "@vitest/browser-playwright": "^4.0.18",
105
110
  "@vitest/coverage-v8": "^4.0.18",
106
111
  "jsdom": "^28.1.0",
112
+ "lucide-react": "^0.575.0",
113
+ "next": "^16.1.6",
107
114
  "playwright": "^1.58.2",
115
+ "react": "^19.2.4",
116
+ "react-dom": "^19.2.4",
108
117
  "rollup-plugin-preserve-directives": "^0.4.0",
109
118
  "storybook": "^10.2.10",
110
119
  "typescript": "^5.9.3",
package/tokens.css CHANGED
@@ -296,6 +296,22 @@
296
296
  --topbar-height: 3.5rem;
297
297
  --content-max-width: 1280px;
298
298
 
299
+ /* ── Density (component sizing — orthogonal to color theme) ──
300
+ Two-stage system: normal values here, [data-density="compact"]
301
+ overrides below. Attach the attribute to :root or any container;
302
+ custom properties inherit down the subtree either way.
303
+ Every normal value below equals the literal it replaces, so the
304
+ default appearance does not change when this section is added. */
305
+ --density-control-height: 2.25rem; /* Input / Select / Button (default size) */
306
+ --density-row-py: var(--space-3); /* Table cell / List-item vertical padding */
307
+ --density-row-px: var(--space-4); /* Table cell horizontal padding */
308
+ --density-font-size-ui: var(--font-size-sm); /* Table / List / Tabs / Dropdown / Sidebar / controls */
309
+ --density-line-height-ui: var(--line-height-normal); /* Table / List row line-height */
310
+ --density-nav-py: var(--space-2); /* Sidebar nav-item vertical padding */
311
+ --density-nav-gap: var(--space-2-5); /* Sidebar nav-item icon gap */
312
+ --density-dropdown-py: var(--space-1-5); /* Dropdown item vertical padding */
313
+ --density-tab-py: var(--space-2); /* Tabs vertical padding */
314
+
299
315
  /* ── Fluid Typography (inspired by Pico CSS) ─────────── */
300
316
  /* Root font-size scales fluidly between 640px and 1280px viewport */
301
317
  /* Components using rem will scale proportionally */
@@ -310,6 +326,24 @@
310
326
  /* 2xl: 1536px — 超ワイド */
311
327
  }
312
328
 
329
+ /* ── Density: compact ──────────────────────────────────────────
330
+ Tightens control height, row padding, UI font-size and line-height
331
+ by roughly one step. Works wherever the attribute is applied —
332
+ :root for a whole app, or a single container (e.g. a dense table
333
+ panel) — since custom properties inherit to descendants either way.
334
+ Orthogonal to [data-theme] / [data-palette]: combine freely. ──── */
335
+ [data-density="compact"] {
336
+ --density-control-height: 2rem;
337
+ --density-row-py: var(--space-2);
338
+ --density-row-px: var(--space-3);
339
+ --density-font-size-ui: var(--font-size-xs);
340
+ --density-line-height-ui: var(--line-height-snug);
341
+ --density-nav-py: var(--space-1-5);
342
+ --density-nav-gap: var(--space-2);
343
+ --density-dropdown-py: var(--space-1);
344
+ --density-tab-py: var(--space-1-5);
345
+ }
346
+
313
347
  /* ── Dark Theme ────────────────────────────────────────────── */
314
348
  [data-theme="dark"] {
315
349
  --color-bg: var(--zinc-950);
@@ -446,8 +480,10 @@
446
480
  /* ── Dark Theme Fallback (JS-disabled) ────────────────────── */
447
481
  /* When JS is unavailable, respect OS dark mode preference.
448
482
  If JS sets data-theme="light", this will NOT apply (thanks to :not()).
449
- If JS sets data-theme="dark", the [data-theme="dark"] block above
450
- takes precedence due to higher specificity. */
483
+ If JS sets data-theme="dark", this block applies too (same values as
484
+ [data-theme="dark"]; :root:not(...) is the higher specificity).
485
+ The cool palette needs its own fallback below, otherwise this block
486
+ (0,2,0, later in source) overrides [data-palette="cool"][data-theme="dark"]. */
451
487
  @media (prefers-color-scheme: dark) {
452
488
  :root:not([data-theme="light"]) {
453
489
  --color-bg: var(--zinc-950);
@@ -515,3 +551,37 @@
515
551
  --ring-color: rgba(168, 162, 158, 0.3);
516
552
  }
517
553
  }
554
+
555
+ /* Cool palette × OS dark fallback (specificity 0,3,0 beats the zinc fallback) */
556
+ @media (prefers-color-scheme: dark) {
557
+ :root[data-palette="cool"]:not([data-theme="light"]) {
558
+ --color-bg: var(--slate-950);
559
+ --color-bg-secondary: var(--slate-900);
560
+ --color-bg-tertiary: var(--slate-800);
561
+ --color-bg-inverse: #ffffff;
562
+
563
+ --color-fg: var(--slate-50);
564
+ --color-fg-secondary: var(--slate-400);
565
+ --color-fg-tertiary: var(--slate-400);
566
+ --color-fg-inverse: var(--slate-950);
567
+ --color-fg-muted: var(--slate-400);
568
+
569
+ --color-border: var(--slate-800);
570
+ --color-border-strong: var(--slate-700);
571
+ --color-border-subtle: var(--slate-900);
572
+
573
+ --color-surface: var(--slate-900);
574
+ --color-surface-hover: var(--slate-800);
575
+ --color-surface-active: var(--slate-700);
576
+
577
+ --color-primary: #ffffff;
578
+ --color-primary-fg: var(--slate-950);
579
+ --color-primary-hover: var(--slate-200);
580
+
581
+ --color-secondary: var(--slate-800);
582
+ --color-secondary-fg: var(--slate-100);
583
+ --color-secondary-hover: var(--slate-700);
584
+
585
+ --ring-color: rgba(204, 202, 216, 0.3);
586
+ }
587
+ }
package/utilities.css CHANGED
@@ -404,6 +404,11 @@
404
404
  [data-theme="dark"] .theme-photo {
405
405
  filter: brightness(0.8) contrast(1.1);
406
406
  }
407
+ @media (prefers-color-scheme: dark) {
408
+ :root:not([data-theme="light"]) .theme-photo {
409
+ filter: brightness(0.8) contrast(1.1);
410
+ }
411
+ }
407
412
 
408
413
  /* ============================================================
409
414
  Responsive Utilities