@momoi-labs/kiso 0.2.0 → 0.3.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/kiso/AGENTS.md CHANGED
@@ -45,11 +45,13 @@ documented; it does not become Kiso by repetition or copy-paste.
45
45
  6. If no contract fits, describe the gap and propose an addition. Do not
46
46
  create a parallel local system.
47
47
 
48
- Kiso is spec-first, with one exception: everywhere except
49
- [`blocks/`](blocks/README.md), its Markdown files are contracts and carry no
50
- implementation code. `blocks/` is the exception — reference screens built from
51
- the contracts, meant to be copied. Blocks are illustrations, not rules: when a
52
- block and a contract disagree, the contract is right and the block is the bug.
48
+ Kiso is spec-first: its Markdown files are contracts and carry no
49
+ implementation code. Two files carry implementation instead —
50
+ [`ui.css`](ui.css), the component layer, published as
51
+ `@momoi-labs/kiso/ui.css`; and [`blocks/`](blocks/README.md), reference
52
+ screens built from it, meant to be copied and not published. Neither is a
53
+ contract: when one disagrees with a contract, the contract is right and the
54
+ implementation is the bug.
53
55
 
54
56
  Deliberate omissions and the evidence-based growth model are recorded in
55
57
  [`docs/evolution.md`](docs/evolution.md).
package/kiso/README.md CHANGED
@@ -3,9 +3,11 @@
3
3
  Kiso is the Momoi Labs product design system. It combines product identity,
4
4
  semantic tokens, component contracts, and reusable screen patterns so products
5
5
  with different purposes still belong to the same family. Kiso is spec-first:
6
- it documents what to build and how it behaves, and it does not ship component
7
- implementation code — with one exception, [`blocks/`](blocks/README.md), which
8
- holds reference screens to copy from. If a block and a contract disagree, the
6
+ its Markdown files document what to build and how it behaves, and they carry
7
+ no implementation. Two files are the exception: [`ui.css`](ui.css), the
8
+ component layer every contract's visual values resolve through, and
9
+ [`blocks/`](blocks/README.md), reference screens built from it to copy from.
10
+ Neither overrides a contract — if a block and a contract disagree, the
9
11
  contract is right.
10
12
 
11
13
  ## For agents
@@ -41,6 +43,7 @@ keyboard flow where relevant.
41
43
  | Path | Purpose |
42
44
  | --- | --- |
43
45
  | [`AGENTS.md`](AGENTS.md) | Consumption contract and decision boundaries |
46
+ | [`ui.css`](ui.css) | Component layer — published as `@momoi-labs/kiso/ui.css` |
44
47
  | [`docs/brand.md`](docs/brand.md) | Product personality and visual direction |
45
48
  | [`docs/principles.md`](docs/principles.md) | Design principles and tie-breakers |
46
49
  | [`docs/voice-and-tone.md`](docs/voice-and-tone.md) | Product UI copy rules |
@@ -50,10 +53,27 @@ keyboard flow where relevant.
50
53
  | [`docs/data-interfaces.md`](docs/data-interfaces.md) | Data-heavy interface rules |
51
54
  | [`docs/accessibility.md`](docs/accessibility.md) | Accessibility requirements across all layers |
52
55
  | [`docs/evolution.md`](docs/evolution.md) | Deliberate deferrals and evidence-based growth |
56
+ | [`blocks/`](blocks/README.md) | Reference screens built from `ui.css` (not published) |
53
57
 
54
58
  Token sources and generated artifacts live in the repository-level `tokens/`
55
59
  directory; validation scripts live in `scripts/`.
56
60
 
61
+ ## Installing
62
+
63
+ Contracts, tokens, and the component layer ship in one versioned package, so a
64
+ project pins the whole system to a single version:
65
+
66
+ ```bash
67
+ npm install @momoi-labs/kiso
68
+ ```
69
+
70
+ ```css
71
+ @import "@momoi-labs/kiso/tokens.css";
72
+ @import "@momoi-labs/kiso/ui.css";
73
+ ```
74
+
75
+ `ui.css` reads tokens as custom properties, so import it after `tokens.css`.
76
+
57
77
  ## Proposing a change
58
78
 
59
79
  First confirm that an existing token, component, or pattern cannot express the
package/kiso/ui.css ADDED
@@ -0,0 +1,971 @@
1
+ /* ===========================================================================
2
+ Kiso v2 — component layer (prototype)
3
+ Every value here resolves to a token in tokens.css. No raw hex, no raw px
4
+ for anything a token covers.
5
+ =========================================================================== */
6
+
7
+ @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap");
8
+
9
+ *, *::before, *::after { box-sizing: border-box; }
10
+
11
+ html { -webkit-text-size-adjust: 100%; }
12
+
13
+ body {
14
+ margin: 0;
15
+ min-height: 100vh;
16
+ background: var(--color-background);
17
+ color: var(--color-foreground);
18
+ font-family: var(--font-body);
19
+ font-size: var(--type-size-body);
20
+ font-weight: var(--type-weight-regular);
21
+ line-height: var(--type-line-height-normal);
22
+ letter-spacing: var(--type-letter-spacing-normal);
23
+ -webkit-font-smoothing: antialiased;
24
+ font-feature-settings: "cv11", "ss01";
25
+ font-variant-numeric: tabular-nums;
26
+ }
27
+
28
+ ::selection { background: light-dark(var(--color-accent-200), var(--color-accent-800)); color: light-dark(var(--color-accent-900), var(--color-accent-50)); }
29
+
30
+ /* Focus: one ring, everywhere, violet. This is the accent's main job now. */
31
+ :focus-visible {
32
+ outline: 2px solid var(--color-ring);
33
+ outline-offset: 2px;
34
+ border-radius: var(--radius-sm);
35
+ }
36
+ :focus:not(:focus-visible) { outline: none; }
37
+
38
+ /* --- typography roles ---------------------------------------------------- */
39
+ h1, h2, h3, h4, p, figure { margin: 0; }
40
+
41
+ .t-display {
42
+ font-family: var(--font-heading);
43
+ font-size: var(--type-size-display);
44
+ font-weight: var(--type-weight-semibold);
45
+ line-height: var(--type-line-height-tight);
46
+ letter-spacing: var(--type-letter-spacing-display);
47
+ }
48
+ .t-h1 {
49
+ font-family: var(--font-heading);
50
+ font-size: var(--type-size-h1);
51
+ font-weight: var(--type-weight-semibold);
52
+ line-height: var(--type-line-height-tight);
53
+ letter-spacing: var(--type-letter-spacing-heading);
54
+ }
55
+ .t-h2 {
56
+ font-family: var(--font-heading);
57
+ font-size: var(--type-size-h2);
58
+ font-weight: var(--type-weight-semibold);
59
+ line-height: var(--type-line-height-snug);
60
+ letter-spacing: var(--type-letter-spacing-heading);
61
+ }
62
+ .t-h3 {
63
+ font-family: var(--font-heading);
64
+ font-size: var(--type-size-h3);
65
+ font-weight: var(--type-weight-semibold);
66
+ line-height: var(--type-line-height-snug);
67
+ letter-spacing: var(--type-letter-spacing-heading);
68
+ }
69
+ .t-body { font-size: var(--type-size-body); }
70
+ .t-label { font-size: var(--type-size-label); font-weight: var(--type-weight-medium); }
71
+ .t-metadata { font-size: var(--type-size-metadata); line-height: var(--type-line-height-normal); }
72
+ .t-caps {
73
+ font-size: var(--type-size-metadata);
74
+ font-weight: var(--type-weight-semibold);
75
+ letter-spacing: var(--type-letter-spacing-caps);
76
+ text-transform: uppercase;
77
+ color: var(--color-subtle-foreground);
78
+ }
79
+ .t-mono, .mono, code, pre, kbd {
80
+ font-family: var(--font-mono);
81
+ font-size: 0.925em;
82
+ font-variant-ligatures: none;
83
+ }
84
+ .t-numeric {
85
+ font-variant-numeric: tabular-nums;
86
+ font-feature-settings: "tnum";
87
+ }
88
+
89
+ .muted { color: var(--color-muted-foreground); }
90
+ .subtle { color: var(--color-subtle-foreground); }
91
+ .fg { color: var(--color-foreground); }
92
+ .success { color: var(--color-success); }
93
+ .warning { color: var(--color-warning); }
94
+ .danger { color: var(--color-danger); }
95
+ .info { color: var(--color-info); }
96
+ .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
97
+ .hidden { display: none !important; }
98
+
99
+ a, .link {
100
+ color: var(--color-link);
101
+ text-decoration: none;
102
+ text-underline-offset: 3px;
103
+ text-decoration-thickness: 1px;
104
+ }
105
+ a:hover, .link:hover { text-decoration: underline; }
106
+
107
+ /* --- layout helpers ------------------------------------------------------ */
108
+ .stack { display: grid; gap: var(--spacing-lg); }
109
+ .stack-sm{ display: grid; gap: var(--spacing-sm); }
110
+ .stack-xs{ display: grid; gap: var(--spacing-xs); }
111
+ .row { display: flex; align-items: center; gap: var(--spacing-sm); }
112
+ .row-md { display: flex; align-items: center; gap: var(--spacing-md); }
113
+ .row-wrap{ display: flex; align-items: center; gap: var(--spacing-sm); flex-wrap: wrap; }
114
+ .between { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-md); }
115
+ .grow { flex: 1; min-width: 0; }
116
+ .separator { height: 1px; background: var(--color-border); border: 0; margin: 0; }
117
+ .separator-v { width: 1px; align-self: stretch; background: var(--color-border); }
118
+ /* A configuration row: name on the left, control flushed right. One rule, so a
119
+ settings row cannot drift between screens. Inline padding is left to the
120
+ container — inside a Card the row aligns with the body, and the sidebar
121
+ footer below re-adds the nav item's inset so labels line up. */
122
+ .settings-row { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-md); padding-block: var(--spacing-xs); }
123
+
124
+ .icon { width: var(--size-icon-md); height: var(--size-icon-md); flex: none; stroke: currentColor; fill: none; stroke-width: 1.75; stroke-linecap: round; stroke-linejoin: round; }
125
+ .icon-sm { width: var(--size-icon-sm); height: var(--size-icon-sm); }
126
+ .icon-lg { width: var(--size-icon-lg); height: var(--size-icon-lg); }
127
+
128
+ /* ===========================================================================
129
+ Button
130
+ =========================================================================== */
131
+ .btn {
132
+ --btn-height: var(--size-control-md);
133
+ --btn-padding-x: var(--spacing-md);
134
+ display: inline-flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ gap: var(--spacing-sm);
138
+ height: var(--btn-height);
139
+ padding-inline: var(--btn-padding-x);
140
+ border: 1px solid transparent;
141
+ border-radius: var(--radius-md);
142
+ background: transparent;
143
+ color: var(--color-foreground);
144
+ font-family: var(--font-body);
145
+ font-size: var(--type-size-body);
146
+ font-weight: var(--type-weight-medium);
147
+ line-height: 1;
148
+ white-space: nowrap;
149
+ cursor: pointer;
150
+ user-select: none;
151
+ text-decoration: none;
152
+ transition:
153
+ background var(--motion-duration-fast) var(--motion-easing-standard),
154
+ border-color var(--motion-duration-fast) var(--motion-easing-standard),
155
+ color var(--motion-duration-fast) var(--motion-easing-standard);
156
+ }
157
+ .btn:disabled, .btn[aria-disabled="true"] {
158
+ cursor: not-allowed;
159
+ opacity: 0.5;
160
+ pointer-events: none;
161
+ }
162
+ .btn:hover { text-decoration: none; }
163
+
164
+ .btn-primary {
165
+ background: var(--color-primary);
166
+ color: var(--color-primary-foreground);
167
+ box-shadow: var(--shadow-xs);
168
+ }
169
+ .btn-primary:hover { background: var(--color-primary-hover); }
170
+
171
+ .btn-secondary {
172
+ background: var(--color-secondary);
173
+ color: var(--color-secondary-foreground);
174
+ }
175
+ .btn-secondary:hover { background: var(--color-secondary-hover); }
176
+
177
+ .btn-outline {
178
+ background: var(--color-card);
179
+ border-color: var(--color-input);
180
+ color: var(--color-foreground);
181
+ box-shadow: var(--shadow-xs);
182
+ }
183
+ .btn-outline:hover { background: var(--color-accent-surface); }
184
+
185
+ .btn-ghost { color: var(--color-muted-foreground); }
186
+ .btn-ghost:hover { background: var(--color-accent-surface-hover); color: var(--color-foreground); }
187
+
188
+ .btn-danger {
189
+ background: var(--color-danger);
190
+ color: var(--color-danger-foreground);
191
+ box-shadow: var(--shadow-xs);
192
+ }
193
+ .btn-danger:hover { filter: brightness(0.93); }
194
+
195
+ .btn-danger-ghost { color: var(--color-danger); }
196
+ .btn-danger-ghost:hover { background: var(--color-danger-surface); }
197
+
198
+ .btn-link { color: var(--color-link); padding-inline: 0; height: auto; }
199
+ .btn-link:hover { text-decoration: underline; }
200
+
201
+ .btn-xs { --btn-height: var(--size-control-xs); --btn-padding-x: var(--spacing-sm); font-size: var(--type-size-label); border-radius: var(--radius-sm); }
202
+ .btn-sm { --btn-height: var(--size-control-sm); --btn-padding-x: var(--spacing-md); font-size: var(--type-size-label); }
203
+ .btn-lg { --btn-height: var(--size-control-lg); --btn-padding-x: var(--spacing-lg); }
204
+
205
+ .btn-icon { --btn-padding-x: 0; width: var(--btn-height); }
206
+ .btn-block { width: 100%; }
207
+
208
+ /* Loading: the label stays in flow so the button never resizes. */
209
+ .btn[data-loading="true"] { position: relative; color: transparent; pointer-events: none; }
210
+ .btn[data-loading="true"]::after {
211
+ content: "";
212
+ position: absolute;
213
+ width: var(--size-icon-md); height: var(--size-icon-md);
214
+ border: 2px solid currentColor;
215
+ border-radius: var(--radius-full);
216
+ border-top-color: transparent;
217
+ animation: spin 600ms linear infinite;
218
+ }
219
+ .btn-primary[data-loading="true"]::after { border-color: var(--color-primary-foreground); border-top-color: transparent; }
220
+ @keyframes spin { to { transform: rotate(360deg); } }
221
+
222
+ /* Button group / segmented control */
223
+ .btn-group { display: inline-flex; }
224
+ .btn-group .btn { border-radius: 0; border-color: var(--color-input); background: var(--color-card); }
225
+ .btn-group .btn:first-child { border-start-start-radius: var(--radius-md); border-end-start-radius: var(--radius-md); }
226
+ .btn-group .btn:last-child { border-start-end-radius: var(--radius-md); border-end-end-radius: var(--radius-md); }
227
+ .btn-group .btn + .btn { margin-inline-start: -1px; }
228
+ .btn-group .btn:hover { background: var(--color-accent-surface); }
229
+ .btn-group .btn[aria-pressed="true"] { background: var(--color-secondary); z-index: 1; }
230
+
231
+ .segmented {
232
+ display: inline-flex;
233
+ gap: var(--spacing-2xs);
234
+ padding: var(--spacing-2xs);
235
+ background: var(--color-muted);
236
+ border: 1px solid var(--color-border);
237
+ border-radius: var(--radius-lg);
238
+ }
239
+ .segmented button {
240
+ height: var(--size-control-sm);
241
+ padding-inline: var(--spacing-md);
242
+ border: 0;
243
+ border-radius: var(--radius-md);
244
+ background: transparent;
245
+ color: var(--color-muted-foreground);
246
+ font: inherit;
247
+ font-size: var(--type-size-label);
248
+ font-weight: var(--type-weight-medium);
249
+ cursor: pointer;
250
+ transition: background var(--motion-duration-fast) var(--motion-easing-standard);
251
+ }
252
+ .segmented button:hover { color: var(--color-foreground); }
253
+ .segmented button[aria-selected="true"] {
254
+ background: var(--color-card);
255
+ color: var(--color-foreground);
256
+ box-shadow: var(--shadow-xs);
257
+ }
258
+
259
+ /* ===========================================================================
260
+ Badge / pill / status
261
+ =========================================================================== */
262
+ .badge {
263
+ display: inline-flex;
264
+ align-items: center;
265
+ gap: var(--spacing-xs);
266
+ height: 20px;
267
+ padding-inline: var(--spacing-sm);
268
+ border: 1px solid transparent;
269
+ border-radius: var(--radius-sm);
270
+ font-size: var(--type-size-metadata);
271
+ font-weight: var(--type-weight-medium);
272
+ line-height: 1;
273
+ white-space: nowrap;
274
+ }
275
+ .badge-solid { background: var(--color-primary); color: var(--color-primary-foreground); }
276
+ .badge-neutral { background: var(--color-secondary); color: var(--color-secondary-foreground); }
277
+ .badge-outline { border-color: var(--color-border-strong); color: var(--color-muted-foreground); }
278
+ .badge-success { background: var(--color-success-surface); border-color: var(--color-success-border); color: var(--color-success); }
279
+ .badge-warning { background: var(--color-warning-surface); border-color: var(--color-warning-border); color: var(--color-warning); }
280
+ .badge-danger { background: var(--color-danger-surface); border-color: var(--color-danger-border); color: var(--color-danger); }
281
+ .badge-info { background: var(--color-info-surface); border-color: var(--color-info-border); color: var(--color-info); }
282
+ .badge-pill { border-radius: var(--radius-full); padding-inline: var(--spacing-md); }
283
+ .badge-count { min-width: 20px; justify-content: center; padding-inline: var(--spacing-xs); border-radius: var(--radius-full); }
284
+ .badge .mono { font-size: inherit; }
285
+
286
+ .dot { width: 6px; height: 6px; border-radius: var(--radius-full); background: currentColor; flex: none; }
287
+ .dot-lg { width: 8px; height: 8px; }
288
+ .dot-pulse { position: relative; }
289
+ .dot-pulse::after {
290
+ content: ""; position: absolute; inset: -3px;
291
+ border-radius: var(--radius-full);
292
+ background: currentColor; opacity: 0.25;
293
+ animation: pulse 2s var(--motion-easing-standard) infinite;
294
+ }
295
+ @keyframes pulse { 0%,100% { transform: scale(1); opacity: .25 } 50% { transform: scale(1.4); opacity: 0 } }
296
+
297
+ /* ===========================================================================
298
+ Form controls
299
+ =========================================================================== */
300
+ .field { display: grid; gap: var(--spacing-xs); }
301
+ .field > label, .label {
302
+ font-size: var(--type-size-label);
303
+ font-weight: var(--type-weight-medium);
304
+ color: var(--color-foreground);
305
+ letter-spacing: var(--type-letter-spacing-label);
306
+ }
307
+ .field-hint { font-size: var(--type-size-label); color: var(--color-muted-foreground); }
308
+ .field-error { font-size: var(--type-size-label); color: var(--color-danger); display: flex; align-items: center; gap: var(--spacing-xs); }
309
+ .field-required { color: var(--color-danger); }
310
+
311
+ .input, .select, .textarea {
312
+ width: 100%;
313
+ min-width: 0;
314
+ height: var(--size-control-md);
315
+ padding-inline: var(--spacing-md);
316
+ border: 1px solid var(--color-input);
317
+ border-radius: var(--radius-md);
318
+ background: var(--color-card);
319
+ color: var(--color-foreground);
320
+ font-family: var(--font-body);
321
+ font-size: var(--type-size-body);
322
+ line-height: 1;
323
+ box-shadow: var(--shadow-xs);
324
+ transition: border-color var(--motion-duration-fast) var(--motion-easing-standard);
325
+ }
326
+ .textarea { height: auto; min-height: 76px; padding-block: var(--spacing-sm); line-height: var(--type-line-height-normal); resize: vertical; }
327
+ .input::placeholder, .textarea::placeholder { color: var(--color-subtle-foreground); }
328
+ .input:hover, .select:hover, .textarea:hover { border-color: var(--color-border-strong); }
329
+ .input:disabled, .select:disabled, .textarea:disabled {
330
+ background: var(--color-disabled-surface);
331
+ color: var(--color-disabled);
332
+ cursor: not-allowed;
333
+ }
334
+ .input[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: var(--color-danger); }
335
+ .input[aria-invalid="true"]:focus-visible { outline-color: var(--color-danger); }
336
+ .input-sm { height: var(--size-control-sm); font-size: var(--type-size-label); }
337
+ .input.mono, .textarea.mono { font-size: var(--type-size-label); }
338
+
339
+ .select {
340
+ appearance: none;
341
+ padding-inline-end: var(--spacing-2xl);
342
+ /* neutral-500 sits between both themes, so the chevron needs no fork */
343
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%238a857f' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
344
+ background-repeat: no-repeat;
345
+ background-position: right var(--spacing-md) center;
346
+ background-size: var(--size-icon-md);
347
+ }
348
+
349
+ /* input with a leading icon or a trailing affix */
350
+ .input-group { position: relative; display: flex; align-items: center; }
351
+ .input-group .icon {
352
+ position: absolute;
353
+ inset-inline-start: var(--spacing-md);
354
+ color: var(--color-subtle-foreground);
355
+ pointer-events: none;
356
+ }
357
+ .input-group .icon ~ .input { padding-inline-start: calc(var(--spacing-md) * 2 + var(--size-icon-md)); }
358
+ .input-group .affix {
359
+ position: absolute;
360
+ inset-inline-end: var(--spacing-sm);
361
+ display: flex; align-items: center; gap: var(--spacing-xs);
362
+ }
363
+
364
+ /* checkbox / radio */
365
+ .check {
366
+ display: inline-flex;
367
+ align-items: flex-start;
368
+ gap: var(--spacing-sm);
369
+ cursor: pointer;
370
+ font-size: var(--type-size-body);
371
+ }
372
+ .check input[type="checkbox"], .check input[type="radio"] {
373
+ appearance: none;
374
+ margin: 0;
375
+ width: var(--size-icon-md);
376
+ height: var(--size-icon-md);
377
+ flex: none;
378
+ margin-block-start: 3px;
379
+ border: 1px solid var(--color-input);
380
+ border-radius: var(--radius-xs);
381
+ background: var(--color-card);
382
+ cursor: pointer;
383
+ transition: background var(--motion-duration-fast) var(--motion-easing-standard);
384
+ }
385
+ .check input[type="radio"] { border-radius: var(--radius-full); }
386
+ .check input:checked {
387
+ background: var(--color-primary);
388
+ border-color: var(--color-primary);
389
+ }
390
+ /* The tick is a mask painted with `primary-foreground`, so it inverts with
391
+ the fill through a token. Two per-theme data URIs used to do this job and
392
+ they could not follow `color-scheme` when the theme is left to the OS. */
393
+ .check input[type="checkbox"] { display: inline-grid; place-content: center; }
394
+ .check input[type="checkbox"]::after {
395
+ content: "";
396
+ width: 10px; height: 10px;
397
+ background: var(--color-primary-foreground);
398
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23000' stroke-width='2.25' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6.5'/%3E%3C/svg%3E") center / contain no-repeat;
399
+ scale: 0;
400
+ transition: scale var(--motion-duration-fast) var(--motion-easing-standard);
401
+ }
402
+ .check input[type="checkbox"]:checked::after { scale: 1; }
403
+ .check input[type="radio"]:checked {
404
+ box-shadow: inset 0 0 0 3px var(--color-card);
405
+ }
406
+ .check input:disabled { background: var(--color-disabled-surface); border-color: var(--color-border); cursor: not-allowed; }
407
+ .check-text { display: grid; gap: var(--spacing-2xs); }
408
+
409
+ /* switch */
410
+ .switch { display: inline-flex; align-items: center; gap: var(--spacing-sm); cursor: pointer; }
411
+ .switch input {
412
+ appearance: none;
413
+ margin: 0;
414
+ position: relative;
415
+ width: 32px; height: 18px; flex: none;
416
+ border-radius: var(--radius-full);
417
+ background: var(--color-border-strong);
418
+ cursor: pointer;
419
+ transition: background var(--motion-duration-fast) var(--motion-easing-standard);
420
+ }
421
+ .switch input::after {
422
+ content: "";
423
+ position: absolute;
424
+ top: 2px; inset-inline-start: 2px;
425
+ width: 14px; height: 14px;
426
+ border-radius: var(--radius-full);
427
+ background: var(--color-white);
428
+ box-shadow: var(--shadow-xs);
429
+ transition: translate var(--motion-duration-fast) var(--motion-easing-standard);
430
+ }
431
+ .switch input:checked { background: var(--color-primary); }
432
+ .switch input:checked::after { translate: 14px 0; background: var(--color-primary-foreground); }
433
+ .switch input:disabled { opacity: .5; cursor: not-allowed; }
434
+
435
+ /* range */
436
+ .range { -webkit-appearance: none; appearance: none; width: 100%; height: 4px; border-radius: var(--radius-full); background: var(--color-secondary); }
437
+ .range::-webkit-slider-thumb {
438
+ -webkit-appearance: none;
439
+ width: var(--size-icon-md); height: var(--size-icon-md);
440
+ border-radius: var(--radius-full);
441
+ background: var(--color-card);
442
+ border: 1px solid var(--color-border-strong);
443
+ box-shadow: var(--shadow-sm);
444
+ cursor: grab;
445
+ }
446
+
447
+ /* ===========================================================================
448
+ Card
449
+ =========================================================================== */
450
+ .card {
451
+ position: relative;
452
+ background: var(--color-card);
453
+ color: var(--color-card-foreground);
454
+ border: 1px solid var(--color-border);
455
+ border-radius: var(--radius-surface);
456
+ box-shadow: var(--shadow-xs);
457
+ }
458
+ .card-header { padding: var(--spacing-lg) var(--spacing-lg) 0; display: grid; gap: var(--spacing-2xs); }
459
+ .card-body { padding: var(--spacing-lg); display: grid; gap: var(--spacing-md); }
460
+ .card-footer {
461
+ padding: var(--spacing-md) var(--spacing-lg);
462
+ border-top: 1px solid var(--color-border);
463
+ display: flex; align-items: center; justify-content: flex-end; gap: var(--spacing-sm);
464
+ background: var(--color-muted);
465
+ border-end-start-radius: var(--radius-surface);
466
+ border-end-end-radius: var(--radius-surface);
467
+ }
468
+ .card-header + .card-body { padding-block-start: var(--spacing-md); }
469
+ .card-interactive { cursor: pointer; transition: border-color var(--motion-duration-fast) var(--motion-easing-standard); }
470
+ .card-interactive:hover { border-color: var(--color-border-strong); box-shadow: var(--shadow-sm); }
471
+
472
+ /* stat / KPI tile */
473
+ .stat { display: grid; gap: var(--spacing-2xs); padding: var(--spacing-lg); }
474
+ .stat-label { font-size: var(--type-size-label); color: var(--color-muted-foreground); font-weight: var(--type-weight-regular); }
475
+ .stat-value {
476
+ font-size: var(--type-size-display);
477
+ font-weight: var(--type-weight-semibold);
478
+ line-height: var(--type-line-height-tight);
479
+ letter-spacing: var(--type-letter-spacing-display);
480
+ font-variant-numeric: tabular-nums;
481
+ }
482
+ .stat-foot { font-size: var(--type-size-label); color: var(--color-muted-foreground); margin-block-start: var(--spacing-sm); }
483
+ .stat-delta { display: inline-flex; align-items: center; gap: var(--spacing-xs); font-size: var(--type-size-metadata); font-weight: var(--type-weight-medium); }
484
+
485
+ /* ===========================================================================
486
+ Table
487
+ =========================================================================== */
488
+ .table-wrap {
489
+ position: relative;
490
+ border: 1px solid var(--color-border);
491
+ border-radius: var(--radius-surface);
492
+ background: var(--color-card);
493
+ }
494
+ /* Scrolling moves to the inner element so the corner marks, which sit just
495
+ outside the frame, are not clipped away. */
496
+ .table-wrap > .table-scroll { overflow: auto; border-radius: inherit; }
497
+ table.table { width: 100%; border-collapse: collapse; font-size: var(--type-size-body); }
498
+ .table thead th {
499
+ position: sticky; top: 0; z-index: 1;
500
+ background: var(--color-muted);
501
+ padding: var(--spacing-sm) var(--spacing-md);
502
+ border-bottom: 1px solid var(--color-border);
503
+ text-align: start;
504
+ font-size: var(--type-size-label);
505
+ font-weight: var(--type-weight-medium);
506
+ color: var(--color-muted-foreground);
507
+ white-space: nowrap;
508
+ }
509
+ .table tbody td {
510
+ padding: var(--spacing-sm) var(--spacing-md);
511
+ border-bottom: 1px solid var(--color-border);
512
+ vertical-align: middle;
513
+ height: var(--size-control-lg);
514
+ }
515
+ .table tbody tr:last-child td { border-bottom: 0; }
516
+ .table tbody tr:hover { background: var(--color-accent-surface); }
517
+ .table tbody tr[aria-selected="true"] { background: var(--color-selected); }
518
+ .table .num { text-align: end; font-variant-numeric: tabular-nums; }
519
+ .table .col-tight { width: 1%; white-space: nowrap; }
520
+ .th-sort { display: inline-flex; align-items: center; gap: var(--spacing-xs); cursor: pointer; background: none; border: 0; font: inherit; color: inherit; padding: 0; }
521
+ .table-toolbar { display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-md); border-bottom: 1px solid var(--color-border); }
522
+ .table-footer { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-md); padding: var(--spacing-sm) var(--spacing-md); border-top: 1px solid var(--color-border); font-size: var(--type-size-label); color: var(--color-muted-foreground); }
523
+
524
+ /* ===========================================================================
525
+ Tabs
526
+ =========================================================================== */
527
+ .tabs { display: flex; gap: var(--spacing-lg); border-bottom: 1px solid var(--color-border); }
528
+ .tabs button {
529
+ position: relative;
530
+ height: var(--size-control-lg);
531
+ padding: 0;
532
+ border: 0; background: none;
533
+ font: inherit;
534
+ font-size: var(--type-size-body);
535
+ font-weight: var(--type-weight-medium);
536
+ color: var(--color-muted-foreground);
537
+ cursor: pointer;
538
+ }
539
+ .tabs button:hover { color: var(--color-foreground); }
540
+ .tabs button[aria-selected="true"] { color: var(--color-foreground); }
541
+ .tabs button[aria-selected="true"]::after {
542
+ content: ""; position: absolute; inset-inline: 0; bottom: -1px; height: 2px;
543
+ background: var(--color-primary);
544
+ border-radius: var(--radius-full);
545
+ }
546
+
547
+ /* ===========================================================================
548
+ Alert / callout
549
+ =========================================================================== */
550
+ .alert {
551
+ display: flex;
552
+ gap: var(--spacing-md);
553
+ padding: var(--spacing-md);
554
+ border: 1px solid var(--color-border);
555
+ border-radius: var(--radius-lg);
556
+ background: var(--color-card);
557
+ font-size: var(--type-size-body);
558
+ }
559
+ .alert .icon { margin-block-start: 2px; }
560
+ .alert-title { font-weight: var(--type-weight-medium); }
561
+ .alert-body { color: var(--color-muted-foreground); font-size: var(--type-size-label); }
562
+ .alert-info { background: var(--color-info-surface); border-color: var(--color-info-border); }
563
+ .alert-info > .icon { color: var(--color-info); }
564
+ .alert-success { background: var(--color-success-surface); border-color: var(--color-success-border); }
565
+ .alert-success > .icon { color: var(--color-success); }
566
+ .alert-warning { background: var(--color-warning-surface); border-color: var(--color-warning-border); }
567
+ .alert-warning > .icon { color: var(--color-warning); }
568
+ .alert-danger { background: var(--color-danger-surface); border-color: var(--color-danger-border); }
569
+ .alert-danger > .icon { color: var(--color-danger); }
570
+
571
+ /* ===========================================================================
572
+ Dialog / drawer / overlay
573
+ =========================================================================== */
574
+ .overlay {
575
+ position: fixed; inset: 0; z-index: 50;
576
+ display: grid; place-items: center;
577
+ padding: var(--spacing-lg);
578
+ background: var(--color-overlay);
579
+ backdrop-filter: blur(2px);
580
+ }
581
+ .dialog {
582
+ position: relative;
583
+ width: 100%; max-width: 440px;
584
+ background: var(--color-popover);
585
+ color: var(--color-popover-foreground);
586
+ border: 1px solid var(--color-border);
587
+ border-radius: var(--radius-surface);
588
+ box-shadow: var(--shadow-lg);
589
+ animation: dialog-in var(--motion-duration-normal) var(--motion-easing-standard);
590
+ }
591
+ @keyframes dialog-in { from { opacity: 0; translate: 0 8px; scale: .98 } }
592
+ .dialog-header { padding: var(--spacing-lg) var(--spacing-lg) 0; display: grid; gap: var(--spacing-2xs); }
593
+ .dialog-body { padding: var(--spacing-lg); display: grid; gap: var(--spacing-lg); }
594
+ .dialog-header + .dialog-body { padding-block-start: var(--spacing-md); }
595
+ .dialog-footer { padding: var(--spacing-md) var(--spacing-lg); border-top: 1px solid var(--color-border); display: flex; justify-content: flex-end; gap: var(--spacing-sm); background: var(--color-muted); border-end-start-radius: var(--radius-surface); border-end-end-radius: var(--radius-surface); }
596
+
597
+ /* ===========================================================================
598
+ Menu / popover / tooltip / command palette
599
+ =========================================================================== */
600
+ .menu {
601
+ min-width: 200px;
602
+ padding: var(--spacing-xs);
603
+ background: var(--color-popover);
604
+ border: 1px solid var(--color-border);
605
+ border-radius: var(--radius-lg);
606
+ box-shadow: var(--shadow-lg);
607
+ }
608
+ .menu-item {
609
+ display: flex; align-items: center; gap: var(--spacing-sm);
610
+ width: 100%;
611
+ height: var(--size-control-sm);
612
+ padding-inline: var(--spacing-sm);
613
+ border: 0; border-radius: var(--radius-md);
614
+ background: none; color: var(--color-foreground);
615
+ font: inherit; font-size: var(--type-size-body);
616
+ text-align: start; text-decoration: none;
617
+ cursor: pointer;
618
+ }
619
+ .menu-item:hover { background: var(--color-accent-surface-hover); }
620
+ .menu-item .icon { color: var(--color-muted-foreground); }
621
+ .menu-item.is-danger { color: var(--color-danger); }
622
+ .menu-item.is-danger:hover { background: var(--color-danger-surface); }
623
+ .menu-item .kbd { margin-inline-start: auto; }
624
+ .menu-label { padding: var(--spacing-sm) var(--spacing-sm) var(--spacing-xs); }
625
+ .menu-separator { height: 1px; background: var(--color-border); margin: var(--spacing-xs) calc(var(--spacing-xs) * -1); }
626
+
627
+ .kbd, kbd {
628
+ display: inline-flex; align-items: center; justify-content: center;
629
+ min-width: 18px; height: 18px;
630
+ padding-inline: var(--spacing-xs);
631
+ border: 1px solid var(--color-border);
632
+ border-bottom-width: 2px;
633
+ border-radius: var(--radius-xs);
634
+ background: var(--color-muted);
635
+ color: var(--color-muted-foreground);
636
+ font-family: var(--font-mono);
637
+ font-size: var(--type-size-metadata);
638
+ line-height: 1;
639
+ }
640
+
641
+ .tooltip {
642
+ display: inline-block;
643
+ padding: var(--spacing-xs) var(--spacing-sm);
644
+ border-radius: var(--radius-md);
645
+ background: var(--color-primary);
646
+ color: var(--color-primary-foreground);
647
+ font-size: var(--type-size-label);
648
+ line-height: var(--type-line-height-tight);
649
+ box-shadow: var(--shadow-md);
650
+ }
651
+
652
+ .palette {
653
+ width: 100%; max-width: 560px;
654
+ background: var(--color-popover);
655
+ border: 1px solid var(--color-border);
656
+ border-radius: var(--radius-surface);
657
+ box-shadow: var(--shadow-lg);
658
+ overflow: hidden;
659
+ }
660
+ .palette-input {
661
+ display: flex; align-items: center; gap: var(--spacing-sm);
662
+ padding: var(--spacing-md) var(--spacing-lg);
663
+ border-bottom: 1px solid var(--color-border);
664
+ }
665
+ .palette-input input { flex: 1; border: 0; background: none; color: inherit; font: inherit; outline: none; }
666
+ .palette-list { padding: var(--spacing-xs); max-height: 280px; overflow-y: auto; }
667
+ .palette-list .menu-item[aria-selected="true"] { background: var(--color-selected); color: var(--color-selected-foreground); }
668
+
669
+ /* ===========================================================================
670
+ Toast
671
+ =========================================================================== */
672
+ .toast-region { position: fixed; inset-block-end: var(--spacing-lg); inset-inline-end: var(--spacing-lg); z-index: 60; display: grid; gap: var(--spacing-sm); }
673
+ .toast {
674
+ display: flex; align-items: flex-start; gap: var(--spacing-md);
675
+ width: 340px;
676
+ padding: var(--spacing-md);
677
+ background: var(--color-popover);
678
+ border: 1px solid var(--color-border);
679
+ border-radius: var(--radius-lg);
680
+ box-shadow: var(--shadow-lg);
681
+ animation: toast-in var(--motion-duration-normal) var(--motion-easing-standard);
682
+ }
683
+ @keyframes toast-in { from { opacity: 0; translate: 0 12px } }
684
+
685
+ /* ===========================================================================
686
+ Feedback: spinner, progress, skeleton, empty state
687
+ =========================================================================== */
688
+ .spinner {
689
+ width: var(--size-icon-md); height: var(--size-icon-md);
690
+ border: 2px solid var(--color-border-strong);
691
+ border-top-color: var(--color-foreground);
692
+ border-radius: var(--radius-full);
693
+ animation: spin 600ms linear infinite;
694
+ }
695
+ .spinner-lg { width: var(--size-icon-lg); height: var(--size-icon-lg); }
696
+
697
+ .progress { height: 6px; border-radius: var(--radius-full); background: var(--color-secondary); overflow: hidden; }
698
+ .progress > span { display: block; height: 100%; border-radius: inherit; background: var(--color-primary); }
699
+ .progress-accent > span { background: var(--color-accent-600); }
700
+
701
+ .skeleton { border-radius: var(--radius-md); background: var(--color-skeleton); animation: shimmer 1.4s ease-in-out infinite; }
702
+ @keyframes shimmer { 50% { opacity: .55 } }
703
+
704
+ .empty {
705
+ display: grid; justify-items: center; gap: var(--spacing-sm);
706
+ padding: var(--spacing-4xl) var(--spacing-xl);
707
+ text-align: center;
708
+ }
709
+ .empty-icon {
710
+ display: grid; place-items: center;
711
+ width: var(--size-control-lg); height: var(--size-control-lg);
712
+ border: 1px solid var(--color-border);
713
+ border-radius: var(--radius-lg);
714
+ background: var(--color-muted);
715
+ color: var(--color-muted-foreground);
716
+ margin-block-end: var(--spacing-xs);
717
+ }
718
+ .empty p { color: var(--color-muted-foreground); font-size: var(--type-size-label); max-width: 42ch; }
719
+
720
+ /* ===========================================================================
721
+ Navigation: breadcrumb, pagination, sidebar, header
722
+ =========================================================================== */
723
+ .breadcrumb { display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--type-size-label); color: var(--color-muted-foreground); }
724
+ .breadcrumb a { color: var(--color-muted-foreground); }
725
+ .breadcrumb a:hover { color: var(--color-foreground); text-decoration: none; }
726
+ .breadcrumb [aria-current="page"] { color: var(--color-foreground); font-weight: var(--type-weight-medium); }
727
+ .breadcrumb .sep { color: var(--color-border-strong); }
728
+
729
+ .pagination { display: flex; align-items: center; gap: var(--spacing-xs); }
730
+ .pagination .btn[aria-current="page"] { background: var(--color-secondary); }
731
+
732
+ .app-shell { display: grid; grid-template-columns: var(--size-sidebar) minmax(0, 1fr); min-height: 100vh; }
733
+ .sidebar {
734
+ display: flex; flex-direction: column;
735
+ min-width: 0;
736
+ position: sticky; top: 0;
737
+ height: 100vh;
738
+ background: var(--color-sidebar);
739
+ border-inline-end: 1px solid var(--color-sidebar-border);
740
+ }
741
+ .sidebar-header { padding: var(--spacing-md); display: grid; gap: var(--spacing-md); }
742
+ .sidebar-body { flex: 1; overflow-y: auto; padding: 0 var(--spacing-sm) var(--spacing-sm); display: grid; gap: var(--spacing-lg); align-content: start; }
743
+ .sidebar-footer { padding: var(--spacing-sm); border-top: 1px solid var(--color-sidebar-border); display: grid; gap: var(--spacing-2xs); }
744
+ .sidebar-footer .settings-row { padding-inline: var(--spacing-sm); } /* match .nav-item's inset */
745
+ .nav-group { display: grid; gap: var(--spacing-2xs); }
746
+ .nav-group > .t-caps { padding-inline: var(--spacing-sm); margin-block-end: var(--spacing-2xs); }
747
+ .nav-item {
748
+ display: flex; align-items: center; gap: var(--spacing-sm);
749
+ height: var(--size-control-sm);
750
+ padding-inline: var(--spacing-sm);
751
+ border: 0; border-radius: var(--radius-md);
752
+ background: none;
753
+ color: var(--color-muted-foreground);
754
+ font: inherit; font-size: var(--type-size-body);
755
+ text-align: start; text-decoration: none;
756
+ cursor: pointer;
757
+ width: 100%;
758
+ }
759
+ .nav-item .icon { color: var(--color-subtle-foreground); }
760
+ .nav-item:hover { background: var(--color-accent-surface-hover); color: var(--color-foreground); text-decoration: none; }
761
+ .nav-item[aria-current="page"], .nav-item.active {
762
+ background: var(--color-selected);
763
+ box-shadow: inset 2px 0 0 var(--color-primary);
764
+ color: var(--color-selected-foreground);
765
+ font-weight: var(--type-weight-medium);
766
+ }
767
+ .nav-item[aria-current="page"] .icon, .nav-item.active .icon { color: currentColor; }
768
+ .nav-item .badge { margin-inline-start: auto; }
769
+
770
+ .brand { display: flex; align-items: center; gap: var(--spacing-sm); min-width: 0; }
771
+ .brand-mark {
772
+ display: grid; place-items: center;
773
+ width: var(--size-control-sm); height: var(--size-control-sm);
774
+ flex: none;
775
+ border-radius: var(--radius-lg);
776
+ background: var(--color-primary);
777
+ color: var(--color-primary-foreground);
778
+ font-weight: var(--type-weight-bold);
779
+ font-size: var(--type-size-label);
780
+ }
781
+
782
+ .topbar {
783
+ display: flex; align-items: center; gap: var(--spacing-md);
784
+ height: var(--size-control-lg);
785
+ min-height: 52px;
786
+ padding-inline: var(--spacing-lg);
787
+ border-bottom: 1px solid var(--color-border);
788
+ background: var(--color-background);
789
+ position: sticky; top: 0; z-index: 20;
790
+ }
791
+
792
+ .avatar {
793
+ display: grid; place-items: center;
794
+ width: var(--size-control-sm); height: var(--size-control-sm);
795
+ flex: none;
796
+ border-radius: var(--radius-full);
797
+ background: var(--color-secondary);
798
+ color: var(--color-secondary-foreground);
799
+ font-size: var(--type-size-metadata);
800
+ font-weight: var(--type-weight-medium);
801
+ }
802
+
803
+ .page { padding: var(--spacing-xl); display: grid; gap: var(--spacing-xl); align-content: start; max-width: var(--size-content-max); }
804
+ .page-header { display: grid; gap: var(--spacing-2xs); }
805
+
806
+ /* ===========================================================================
807
+ Developer-oriented surfaces (Kiso: developer-oriented-interfaces pattern)
808
+ =========================================================================== */
809
+ code {
810
+ padding: 1px var(--spacing-xs);
811
+ border-radius: var(--radius-xs);
812
+ background: var(--color-muted);
813
+ border: 1px solid var(--color-border);
814
+ }
815
+ pre {
816
+ position: relative;
817
+ margin: 0;
818
+ padding: var(--spacing-md);
819
+ border: 1px solid var(--color-border);
820
+ border-radius: var(--radius-surface);
821
+ background: var(--color-muted);
822
+ overflow-x: auto;
823
+ font-size: var(--type-size-label);
824
+ line-height: var(--type-line-height-relaxed);
825
+ }
826
+ pre code { padding: 0; background: none; border: 0; }
827
+
828
+ .kv { display: grid; grid-template-columns: minmax(96px, auto) minmax(0, 1fr); gap: var(--spacing-sm) var(--spacing-lg); font-size: var(--type-size-body); }
829
+ .kv dt { color: var(--color-muted-foreground); font-size: var(--type-size-label); }
830
+ .kv dd { margin: 0; font-family: var(--font-mono); font-size: var(--type-size-label); overflow-wrap: anywhere; }
831
+
832
+ .logview {
833
+ position: relative;
834
+ background: var(--color-neutral-950);
835
+ color: var(--color-neutral-300);
836
+ border: 1px solid var(--color-border);
837
+ border-radius: var(--radius-surface);
838
+ padding: var(--spacing-md);
839
+ font-family: var(--font-mono);
840
+ font-size: var(--type-size-label);
841
+ line-height: var(--type-line-height-relaxed);
842
+ overflow: auto;
843
+ }
844
+ .logview .log-time { color: var(--color-neutral-600); margin-inline-end: var(--spacing-sm); }
845
+ .logview .log-warn { color: var(--color-warning-on-dark); }
846
+ .logview .log-error{ color: var(--color-danger-on-dark); }
847
+ .logview .log-info { color: var(--color-info-on-dark); }
848
+
849
+ /* Sparkline / bar chart placeholders, neutral-only per the brand rules. */
850
+ .bars { display: flex; align-items: flex-end; gap: 3px; height: 40px; }
851
+ .bars i { flex: 1; background: var(--color-border-strong); border-radius: var(--radius-xs); }
852
+ .bars i.on { background: var(--color-primary); }
853
+
854
+ /* ===========================================================================
855
+ Touch targets: the 44px rule applies to coarse pointers only.
856
+ This is the whole reason the desktop UI can be 36px.
857
+ =========================================================================== */
858
+ @media (pointer: coarse) {
859
+ .btn, .input, .select, .nav-item, .menu-item {
860
+ min-height: var(--size-touch-min);
861
+ }
862
+ .btn-icon { min-width: var(--size-touch-min); }
863
+ }
864
+
865
+ @media (max-width: 1023px) {
866
+ .app-shell { grid-template-columns: 1fr; }
867
+ .sidebar { display: none; }
868
+ }
869
+
870
+
871
+ /* ===========================================================================
872
+ Corner marks — the "blueprint" corner treatment
873
+ ---------------------------------------------------------------------------
874
+ An open registration mark on each corner: two 1px ticks, each lying along
875
+ the frame line it extends and stopping `--corner-mark-gap` short of it, so
876
+ the mark points at the corner without touching it.
877
+
878
+ Deliberately two ticks and not four. A full cross puts its other two arms
879
+ directly on top of the panel border, where they are invisible — the mark
880
+ read as a bracket anyway. Dropping them halves the paint and makes the
881
+ hollow centre explicit instead of accidental.
882
+
883
+ Eight 1px gradient bars on one pseudo-element. No markup, no images, and
884
+ `--corner-mark: 0` removes them entirely.
885
+ =========================================================================== */
886
+ .card::after,
887
+ .dialog::after,
888
+ .table-wrap::after,
889
+ .logview::after,
890
+ .marked::after,
891
+ pre::after {
892
+ content: "";
893
+ position: absolute;
894
+ /* Reach far enough out that a tick clears the frame by the gap. */
895
+ inset: calc(-1 * (var(--corner-mark-tick) + var(--corner-mark-gap)));
896
+ pointer-events: none;
897
+ opacity: var(--corner-mark);
898
+ background-repeat: no-repeat;
899
+ background-image:
900
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
901
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
902
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
903
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
904
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
905
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
906
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark)),
907
+ linear-gradient(var(--color-corner-mark), var(--color-corner-mark));
908
+ background-size:
909
+ var(--corner-mark-tick) 1px, 1px var(--corner-mark-tick),
910
+ var(--corner-mark-tick) 1px, 1px var(--corner-mark-tick),
911
+ var(--corner-mark-tick) 1px, 1px var(--corner-mark-tick),
912
+ var(--corner-mark-tick) 1px, 1px var(--corner-mark-tick);
913
+ /* The offset lands each tick exactly on the 1px frame line it extends. */
914
+ background-position:
915
+ left 0 top calc(var(--corner-mark-tick) + var(--corner-mark-gap)),
916
+ left calc(var(--corner-mark-tick) + var(--corner-mark-gap)) top 0,
917
+ right 0 top calc(var(--corner-mark-tick) + var(--corner-mark-gap)),
918
+ right calc(var(--corner-mark-tick) + var(--corner-mark-gap)) top 0,
919
+ left 0 bottom calc(var(--corner-mark-tick) + var(--corner-mark-gap)),
920
+ left calc(var(--corner-mark-tick) + var(--corner-mark-gap)) bottom 0,
921
+ right 0 bottom calc(var(--corner-mark-tick) + var(--corner-mark-gap)),
922
+ right calc(var(--corner-mark-tick) + var(--corner-mark-gap)) bottom 0;
923
+ }
924
+ .marked { position: relative; }
925
+
926
+ /* ===========================================================================
927
+ Hatch — "this region is not data"
928
+ ---------------------------------------------------------------------------
929
+ Companion to the corner marks. Three sanctioned uses, and no others:
930
+ .hatch on an empty state nothing here yet
931
+ .hatch on a chrome band this strip is title/controls, not content
932
+ .hatch on an unavailable pane the data does not exist right now
933
+
934
+ The stripe colour is one step off the surface it sits on, so text stays
935
+ legible on top of it. Do not raise the contrast to make it "read better" —
936
+ if it is loud enough to notice while reading, it is in the wrong place.
937
+ =========================================================================== */
938
+ .hatch {
939
+ background-image: repeating-linear-gradient(
940
+ var(--hatch-angle),
941
+ var(--color-hatch) 0,
942
+ var(--color-hatch) var(--hatch-line),
943
+ transparent var(--hatch-line),
944
+ transparent var(--hatch-period)
945
+ );
946
+ }
947
+ /* An empty region *is* the message, so empty states are the primary use. */
948
+ .empty.hatch { background-color: var(--color-card); }
949
+
950
+ /* A chrome band inside a panel — title, breadcrumb, controls. */
951
+ .band {
952
+ padding: var(--spacing-lg);
953
+ border-bottom: 1px solid var(--color-border);
954
+ }
955
+ .band > * { position: relative; }
956
+
957
+ /* ===========================================================================
958
+ Split pane (list-detail with a resizable divider)
959
+ =========================================================================== */
960
+ .split { display: flex; min-width: 0; }
961
+ .split > .pane { min-width: 0; overflow: auto; }
962
+ .splitter { flex: none; width: 1px; background: var(--color-border); cursor: col-resize; position: relative; }
963
+ .splitter::after { content: ""; position: absolute; inset-block: 0; inset-inline: -3px; }
964
+ .splitter:hover, .splitter.dragging { background: var(--color-ring); }
965
+
966
+ /* Chart frame: neutral only, gridlines at border strength. */
967
+ .chart { width: 100%; height: 200px; display: block; overflow: visible; color: var(--color-primary); }
968
+ .chart .grid { stroke: var(--color-border); stroke-width: 1; }
969
+ .chart .line { fill: none; stroke: var(--color-primary); stroke-width: 1.5; }
970
+ .chart .area { fill: url(#chart-fill); }
971
+ .chart-axis { display: flex; justify-content: space-between; font-size: var(--type-size-metadata); color: var(--color-subtle-foreground); margin-block-start: var(--spacing-sm); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momoi-labs/kiso",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Kiso design-system contracts and generated design tokens",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -15,6 +15,7 @@
15
15
  ],
16
16
  "exports": {
17
17
  "./tokens.css": "./tokens/build/tokens.css",
18
+ "./ui.css": "./kiso/ui.css",
18
19
  "./tokens.json": "./tokens/build/tokens.json",
19
20
  "./tokens.scss": "./tokens/build/tokens.scss",
20
21
  "./tokens.d.ts": "./tokens/build/tokens.d.ts",