@momoi-labs/kiso 0.7.1 → 0.9.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.
@@ -44,23 +44,32 @@ Import `tokens.css`; application CSS should need no raw color values.
44
44
 
45
45
  ## Semantic colors
46
46
 
47
+ The dark theme draws its surfaces from a dedicated warm dark ramp
48
+ (`color.dark.*`), not from the tail of the light ramp: one hue family for both
49
+ themes, with layer spacing that lets the sidebar sink below the canvas and the
50
+ card lift above it. The accent is split into two roles. The **ink**
51
+ (`accent.base`, flowing into `link` and `focus`) is the text-eligible lilac for
52
+ links, focus rings, and active indicators. The **fill** (`primary` and its
53
+ hover and surface tints) is the deep violet behind `primary-foreground`,
54
+ never text on dark.
55
+
47
56
  | Role | Meaning and use | Dark primitive | Light primitive |
48
57
  | --- | --- | --- | --- |
49
- | `background` | Application canvas; never text. | `neutral.900` | `neutral.200` |
50
- | `surface` | Cards, panels, and table rows. | `neutral.800` | `neutral.100` |
51
- | `elevated-surface` | Menus, popovers, and dialogs. | `neutral.700` | `white` |
52
- | `foreground` | Primary text and content that must carry the strongest hierarchy. | `neutral.100` | `neutral.900` |
53
- | `muted-foreground` | Secondary text and labels. It remains normal-text eligible. | `neutral.400` | `neutral.600` |
54
- | `subtle-foreground` | Placeholders, timestamps, and non-essential hints; large text only, never body copy. | `neutral.500` | `neutral.500` |
55
- | `border` | Dividers and control outlines; never text. | `neutral.600` | `neutral.300` |
56
- | `primary` | The main interactive action: links, active states, and primary controls. | `accent.base` | `accent.base` |
58
+ | `background` | Application canvas; never text. | `dark.900` | `neutral.200` |
59
+ | `surface` | Cards, panels, and table rows. | `dark.800` | `neutral.100` |
60
+ | `elevated-surface` | Menus, popovers, and dialogs. | `dark.700` | `white` |
61
+ | `foreground` | Primary text and content that must carry the strongest hierarchy. | `dark.100` | `neutral.900` |
62
+ | `muted-foreground` | Secondary text and labels. It remains normal-text eligible. | `dark.400` | `neutral.600` |
63
+ | `subtle-foreground` | Placeholders, timestamps, and non-essential hints; large text only, never body copy. | `dark.450` | `neutral.500` |
64
+ | `border` | Dividers and control outlines; never text. | `dark.600` | `neutral.300` |
65
+ | `primary` | The primary fill: primary buttons, solid badges, checked controls, the brand mark. Not text-eligible on dark. | `#684bb5` | `#5b3fc4` |
57
66
  | `accent` | Secondary emphasis and highlights, not the page's main action. | `accent.300` | `accent.800` |
58
67
  | `success` | Positive or completed state. | `status.success` | `status.success` |
59
68
  | `warning` | Caution or a condition needing attention. | `status.warning` | `status.warning` |
60
69
  | `danger` | Error or destructive action. | `status.danger` | `status.danger` |
61
70
  | `info` | Neutral informational state. | `status.info` | `status.info` |
62
- | `focus` | Keyboard focus ring; never text. | `accent.300` | `accent.base` |
63
- | `disabled` | Disabled text and controls only. | `neutral.600` | `neutral.400` |
71
+ | `focus` | Keyboard focus ring; never text. The light ink. | `accent.base` | `accent.base` |
72
+ | `disabled` | Disabled text and controls only. | `dark.600` | `neutral.400` |
64
73
 
65
74
  ### Fills and their foregrounds
66
75
 
@@ -70,46 +79,48 @@ outline — which is how a violet design system ends up rendering grey.
70
79
 
71
80
  | Role | Meaning and use | Dark primitive | Light primitive |
72
81
  | --- | --- | --- | --- |
73
- | `primary-foreground` | Text and icons on a `primary` fill. | `neutral.900` | `white` |
74
- | `primary-hover` | `primary` fill on hover. | `accent.300` | `accent.800` |
75
- | `danger-foreground` | Text and icons on a `danger` fill. | `neutral.900` | `white` |
76
- | `secondary` | Neutral button fill, range track, count badge; never text. | `neutral.700` | `neutral.300` |
82
+ | `primary-foreground` | Text and icons on a `primary` fill. | near-white `#f7f5fe` | `white` |
83
+ | `primary-hover` | `primary` fill on hover. | `#795dc9` | `accent.800` |
84
+ | `danger-foreground` | Text and icons on a `danger` fill. | `dark.950` | `white` |
85
+ | `secondary` | Neutral button fill, range track, count badge; never text. | `dark.700` | `neutral.300` |
77
86
  | `secondary-foreground` | Text on a `secondary` fill. | `foreground` | `foreground` |
78
- | `secondary-hover` | `secondary` fill on hover; never text. | `neutral.600` | `neutral.400` |
79
- | `selected` | Selected row, active nav item, highlighted result; never text. One step past `accent-surface-hover`, so selection and hover stay apart. | `accent.800` | `accent.300` |
87
+ | `secondary-hover` | `secondary` fill on hover; never text. | `dark.600` | `neutral.400` |
88
+ | `selected` | Selected row, active nav item, highlighted result; never text. One step past `accent-surface-hover`, so selection and hover stay apart. | `#49406d` | `accent.300` |
80
89
  | `selected-foreground` | Text on a `selected` fill. | `foreground` | `foreground` |
81
90
 
82
- `primary-foreground` and `danger-foreground` invert with their fill: near-black
83
- on the light lilac of dark theme, white on the deep violet of light theme. Both
84
- are gated at 4.5:1 **against their own fill**, not against a surface.
91
+ `primary-foreground` is near-white (`#f7f5fe`) on the deep violet fill in both
92
+ themes; the fill no longer flips to a light lilac on dark, so the foreground
93
+ no longer inverts. `danger-foreground` still inverts with its fill: white on
94
+ the light-theme red, near-black on the dark-theme salmon. Both are gated at
95
+ 4.5:1 **against their own fill**, not against a surface.
85
96
 
86
97
  ### Component surfaces
87
98
 
88
99
  | Role | Meaning and use | Dark primitive | Light primitive |
89
100
  | --- | --- | --- | --- |
90
- | `card` | Panel and card fill. Alias of `surface`. | `neutral.800` | `neutral.100` |
101
+ | `card` | Panel and card fill. Alias of `surface`. | `dark.800` | `neutral.100` |
91
102
  | `card-foreground` | Text on `card`. Alias of `foreground`. | `foreground` | `foreground` |
92
- | `popover` | Menus, dialogs, palette. Alias of `elevated-surface`. | `neutral.700` | `white` |
103
+ | `popover` | Menus, dialogs, palette. Alias of `elevated-surface`. | `dark.700` | `white` |
93
104
  | `popover-foreground` | Text on `popover`. Alias of `foreground`. | `foreground` | `foreground` |
94
- | `muted` | Recessed fill one step off `card`: table headers, card footers, segmented tracks. | `neutral.700` | `neutral.200` |
95
- | `sidebar` | Application shell navigation column. | `neutral.950` | `neutral.100` |
105
+ | `muted` | Recessed fill one step off `card`: table headers, card footers, segmented tracks. | `dark.700` | `neutral.200` |
106
+ | `sidebar` | Application shell navigation column. | `dark.950` | `neutral.100` |
96
107
  | `sidebar-border` | Divider between sidebar and content. Alias of `border`. | `border` | `border` |
97
- | `disabled-surface` | Fill of a disabled control. | `neutral.800` | `neutral.200` |
98
- | `skeleton` | Loading placeholder fill. | `neutral.700` | `neutral.300` |
108
+ | `disabled-surface` | Fill of a disabled control. | `dark.800` | `neutral.200` |
109
+ | `skeleton` | Loading placeholder fill. | `dark.700` | `neutral.300` |
99
110
  | `overlay` | Scrim behind a modal layer. | `black` at 60% | `black` at 40% |
100
111
 
101
112
  ### Lines, tints, and state
102
113
 
103
114
  | Role | Meaning and use | Dark primitive | Light primitive |
104
115
  | --- | --- | --- | --- |
105
- | `border-strong` | Hover outlines, switch tracks, gridlines, corner marks; never text. | `neutral.500` | `neutral.500` |
116
+ | `border-strong` | Hover outlines, switch tracks, gridlines, corner marks; never text. | `dark.450` | `neutral.500` |
106
117
  | `input` | Form control outline at rest. Alias of `border`. | `border` | `border` |
107
118
  | `corner-mark` | Corner registration marks. Alias of `border-strong`. | `border-strong` | `border-strong` |
108
- | `hatch` | Hatch stripe over a region that is not data. | `neutral.700` | `neutral.300` |
109
- | `accent-surface` | Faintest accent tint: row hover, ghost hover; never text. | `accent.950` | `accent.50` |
110
- | `accent-surface-hover` | Accent tint one step stronger; never text. | `accent.900` | `accent.200` |
119
+ | `hatch` | Hatch stripe over a region that is not data. | `dark.700` | `neutral.300` |
120
+ | `accent-surface` | Faintest accent tint: row hover, ghost hover; never text. | `#2d2843` | `accent.50` |
121
+ | `accent-surface-hover` | Accent tint one step stronger; never text. | `#3a3356` | `accent.200` |
111
122
  | `ring` | Focus ring, active drag handle. Alias of `focus`. | `focus` | `focus` |
112
- | `link` | Inline and standalone links. Alias of `primary`. | `primary` | `primary` |
123
+ | `link` | Inline and standalone links. The light ink: alias of `accent.base`, text-eligible where the `primary` fill is not. | `accent.base` | `accent.base` |
113
124
  | `shadow-hairline` | Shadow colour for a resting control's contact line. | `black` at 30% | `black` at 5% |
114
125
  | `shadow-contact` | Shadow colour for separated and floating layers. | `black` at 40% | `black` at 8% |
115
126
 
@@ -132,23 +143,30 @@ terminals — which cannot follow `color-scheme`, so their text cannot either.
132
143
 
133
144
  Use `foreground` for default reading, `muted-foreground` when content is
134
145
  secondary but still needs normal-text contrast, and `subtle-foreground` only
135
- for large or non-essential supporting copy. Use `primary` for the action that
136
- drives the current task; use `accent` to draw secondary attention without
137
- creating another primary action.
146
+ for large or non-essential supporting copy. Use `primary` for the fill of the
147
+ action that drives the current task; use `link` for anything that reads as a
148
+ link or an active indicator, because the fill is not text-eligible on dark.
149
+ Use `accent` to draw secondary attention without creating another primary
150
+ action.
138
151
 
139
152
  The semantic aliases deliberately point at different primitives by theme.
140
153
  Status primitives and `accent.base` are themselves mode-aware, so the same
141
154
  semantic role preserves its meaning and contrast rather than preserving a
142
- literal color.
155
+ literal color. `border-strong` and `subtle-foreground` sit on `dark.450` on
156
+ dark because `dark.500` misses the 3:1 non-text and large-text gates on
157
+ `dark.800` and `dark.700`; they share a step on dark exactly as they share
158
+ `neutral.500` on light.
143
159
 
144
160
  ## AA gate
145
161
 
146
162
  `scripts/check-contrast.mjs` is the build-time AA gate. In both dark and light
147
163
  themes it resolves the semantic aliases and checks:
148
164
 
149
- - `foreground`, `muted-foreground`, `primary`, `accent`, `success`, `warning`,
165
+ - `foreground`, `muted-foreground`, `link`, `accent`, `success`, `warning`,
150
166
  `danger`, and `info` at **4.5:1** or better against `background`, `surface`,
151
- and `elevated-surface`;
167
+ and `elevated-surface`. `link` is gated, not `primary`: since the ink/fill
168
+ split, `primary` is the deep fill and is not text-eligible on dark, while
169
+ the ink that links and indicators actually draw with is `link`;
152
170
  - `subtle-foreground` at **3:1** or better against those surfaces, restricting
153
171
  it to large text and non-essential metadata;
154
172
  - `border-strong` and `corner-mark` at **3:1** or better against those
package/kiso/ui.css CHANGED
@@ -362,6 +362,117 @@ a:hover, .link:hover { text-decoration: underline; }
362
362
  display: flex; align-items: center; gap: var(--spacing-xs);
363
363
  }
364
364
 
365
+ /* chip input: several structured values in one field */
366
+ .chip-input { position: relative; display: grid; gap: var(--spacing-xs); }
367
+ .chip-input-box {
368
+ display: flex; flex-wrap: wrap; align-items: center; gap: var(--spacing-xs);
369
+ width: 100%;
370
+ min-height: var(--size-control-md);
371
+ padding: var(--spacing-xs);
372
+ border: 1px solid var(--color-input);
373
+ border-radius: var(--radius-md);
374
+ background: var(--color-card);
375
+ box-shadow: var(--shadow-xs);
376
+ cursor: text;
377
+ transition: border-color var(--motion-duration-fast) var(--motion-easing-standard);
378
+ }
379
+ .chip-input-box:hover { border-color: var(--color-border-strong); }
380
+ .chip-input-box:focus-within { outline: 2px solid var(--color-ring); outline-offset: 2px; }
381
+ .chip-input-box[aria-invalid="true"] { border-color: var(--color-danger); }
382
+ .chip-input-box[aria-invalid="true"]:focus-within { outline-color: var(--color-danger); }
383
+ .chip-input-box[data-disabled="true"] {
384
+ background: var(--color-disabled-surface);
385
+ color: var(--color-disabled);
386
+ cursor: not-allowed;
387
+ }
388
+ .chip-input-field {
389
+ flex: 1 1 12ch; min-width: 12ch;
390
+ height: var(--size-control-sm);
391
+ padding-inline: var(--spacing-xs);
392
+ border: 0; background: none; color: inherit;
393
+ font: inherit; font-size: var(--type-size-body);
394
+ outline: none;
395
+ }
396
+ .chip-input-field::placeholder { color: var(--color-subtle-foreground); }
397
+ .chip-input-list {
398
+ position: absolute; inset-inline: 0; top: 100%; z-index: 40;
399
+ margin-block-start: var(--spacing-xs);
400
+ max-height: 240px; overflow-y: auto;
401
+ }
402
+ .chip-input-list .menu-item[aria-selected="true"] { background: var(--color-selected); color: var(--color-selected-foreground); }
403
+ .chip-input-list .menu-item .muted { margin-inline-start: auto; }
404
+
405
+ .chip {
406
+ display: inline-flex; align-items: stretch;
407
+ max-width: 100%;
408
+ min-height: var(--size-control-sm);
409
+ padding-inline-start: var(--spacing-sm);
410
+ padding-inline-end: var(--spacing-xs);
411
+ border: 1px solid transparent;
412
+ border-radius: var(--radius-sm);
413
+ background: var(--color-secondary);
414
+ color: var(--color-secondary-foreground);
415
+ font-size: var(--type-size-label);
416
+ line-height: 1;
417
+ }
418
+ .chip:has(.is-editing) { background: var(--color-card); border-color: var(--color-primary); }
419
+ .chip[data-state="invalid"] { background: var(--color-danger-surface); border-color: var(--color-danger-border); color: var(--color-danger); }
420
+ /* The chip reads left to right like a call: what installs it, what it is,
421
+ which version. Segments are divided the way .btn-group divides buttons. */
422
+ .chip-scope, .chip-name {
423
+ display: inline-flex; align-items: center;
424
+ padding-inline-end: var(--spacing-sm);
425
+ font-family: var(--font-mono); font-size: var(--type-size-metadata);
426
+ }
427
+ .chip-scope { color: var(--color-muted-foreground); }
428
+ .chip-scope + .chip-name {
429
+ padding-inline-start: var(--spacing-sm);
430
+ border-inline-start: 1px solid var(--color-border);
431
+ }
432
+ /* The version and each option are the segments people press, so they carry the
433
+ raised fill of a grouped button rather than waiting for a hover to admit
434
+ they are one. Every option gets its own rule, and a list keeps its brackets,
435
+ so name=value and name=[a, b] read as the same kind of fact. */
436
+ .chip-value, .chip-option {
437
+ display: inline-flex; align-items: center;
438
+ padding-inline: var(--spacing-sm);
439
+ border: 0; border-inline-start: 1px solid var(--color-border);
440
+ border-radius: 0;
441
+ background: var(--color-card);
442
+ font-family: var(--font-mono); font-size: var(--type-size-metadata);
443
+ cursor: pointer;
444
+ }
445
+ .chip-value { color: var(--color-link); }
446
+ .chip-option { color: var(--color-foreground); }
447
+ .chip-value:hover, .chip-option:hover { background: var(--color-accent-surface); }
448
+ .chip-option-name { color: var(--color-muted-foreground); }
449
+ .chip-option-mark { color: var(--color-subtle-foreground); }
450
+ .chip-option[data-empty="true"] { color: var(--color-subtle-foreground); }
451
+ .chip-value:last-child, .chip-option:last-child {
452
+ border-start-end-radius: var(--radius-xs);
453
+ border-end-end-radius: var(--radius-xs);
454
+ }
455
+ .chip-value.is-editing, .chip-option.is-editing {
456
+ min-width: 4ch;
457
+ background: var(--color-accent-surface);
458
+ outline: none;
459
+ }
460
+
461
+ .chip-action {
462
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-2xs);
463
+ align-self: center;
464
+ margin-inline-start: var(--spacing-xs);
465
+ min-width: var(--size-icon-sm); height: var(--size-icon-md);
466
+ padding-inline: var(--spacing-2xs);
467
+ border: 0; border-radius: var(--radius-xs);
468
+ background: none; color: var(--color-muted-foreground);
469
+ font: inherit; font-size: var(--type-size-metadata);
470
+ cursor: pointer;
471
+ }
472
+ .chip-action:hover { background: var(--color-accent-surface-hover); color: var(--color-foreground); }
473
+ .chip-action[aria-expanded="true"] { background: var(--color-accent-surface); color: var(--color-foreground); }
474
+ .chip-action .icon { width: var(--size-icon-sm); height: var(--size-icon-sm); }
475
+
365
476
  /* checkbox / radio */
366
477
  .check {
367
478
  display: inline-flex;
@@ -731,6 +842,7 @@ table.table { width: 100%; border-collapse: collapse; font-size: var(--type-size
731
842
  .pagination .btn[aria-current="page"] { background: var(--color-secondary); }
732
843
 
733
844
  .app-shell { display: grid; grid-template-columns: var(--size-sidebar) minmax(0, 1fr); min-height: 100vh; }
845
+ .app-shell[data-layout="topbar"] { grid-template-columns: minmax(0, 1fr); }
734
846
  .sidebar {
735
847
  display: flex; flex-direction: column;
736
848
  min-width: 0;
@@ -778,7 +890,7 @@ table.table { width: 100%; border-collapse: collapse; font-size: var(--type-size
778
890
  inset-block: 2px;
779
891
  width: 2px;
780
892
  border-radius: var(--radius-full);
781
- background: var(--color-primary);
893
+ background: var(--color-link); /* the ink, not the primary fill: on dark the fill misses 3:1 against the sidebar */
782
894
  }
783
895
  .nav-item[aria-current="page"] .icon, .nav-item.active .icon { color: currentColor; }
784
896
  .nav-item .badge { margin-inline-start: auto; }
@@ -912,6 +1024,10 @@ pre code { display: block; overflow-x: auto; padding: 0; background: none; borde
912
1024
  min-height: var(--size-touch-min);
913
1025
  }
914
1026
  .btn-icon { min-width: var(--size-touch-min); }
1027
+ /* The chip grows with its segments, so the floor goes on the chip and the
1028
+ segments stretch to it. */
1029
+ .chip, .chip-input-field { min-height: var(--size-touch-min); }
1030
+ .chip-action { min-width: var(--size-touch-min); height: var(--size-touch-min); }
915
1031
  }
916
1032
 
917
1033
  @media (max-width: 1023px) {
@@ -1015,9 +1131,24 @@ pre::after {
1015
1131
  .splitter::after { content: ""; position: absolute; inset-block: 0; inset-inline: -3px; }
1016
1132
  .splitter:hover, .splitter.dragging { background: var(--color-ring); }
1017
1133
 
1018
- /* Chart frame: neutral only, gridlines at border strength. */
1134
+ /* Chart frame: deliberate presentation, one featured series in primary,
1135
+ gridlines at border strength. The area is the line color at low opacity, a
1136
+ flat token fill: no document-level gradient id for a component to forget
1137
+ to define. Supporting, incidental series belong in `.sparkline` instead. */
1019
1138
  .chart { width: 100%; height: 200px; display: block; overflow: visible; color: var(--color-primary); }
1020
1139
  .chart .grid { stroke: var(--color-border); stroke-width: 1; }
1021
- .chart .line { fill: none; stroke: var(--color-primary); stroke-width: 1.5; }
1022
- .chart .area { fill: url(#chart-fill); }
1140
+ .chart .line { fill: none; stroke: currentColor; stroke-width: 1.5; }
1141
+ .chart .area { fill: currentColor; fill-opacity: 0.12; }
1023
1142
  .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); }
1143
+
1144
+ /* Sparkline — one metric series at cell size (issue #84). No axis, no grid,
1145
+ no legend, no tooltip: the number beside the line carries the precision,
1146
+ the line carries the shape. Recharts draws the path; the frame owns only
1147
+ display, width, and the color tokens. Neutral is the voice of an
1148
+ incidental series in a table cell, `--color-border-strong` like `.bars`;
1149
+ `.primary` marks the series a tile is about, so at most one sibling
1150
+ raises its voice. Fewer than two samples draws nothing — a flat rule
1151
+ across a cell reads as a border, not as a measurement. */
1152
+ .sparkline { display: block; width: 100%; color: var(--color-border-strong); }
1153
+ .sparkline.primary { color: var(--color-primary); }
1154
+ .sparkline svg { display: block; overflow: visible; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momoi-labs/kiso",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "Kiso design-system contracts and generated design tokens",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,65 +25,77 @@
25
25
  --color-accent-800: #46309e;
26
26
  --color-accent-900: #2e2170;
27
27
  --color-accent-950: #211741;
28
- --color-accent-base: light-dark(#5b3fc4, #cbbdf7);
29
- --color-status-success: light-dark(#0c6b43, #4ed69a);
30
- --color-status-warning: light-dark(#7a5000, #e8c05a);
31
- --color-status-danger: light-dark(#ad2f29, #f4867f);
32
- --color-status-info: light-dark(#15559e, #8fc9f5);
33
- --color-background: light-dark(var(--color-neutral-200), var(--color-neutral-900));
34
- --color-surface: light-dark(var(--color-neutral-100), var(--color-neutral-800));
35
- --color-elevated-surface: light-dark(var(--color-white), var(--color-neutral-700));
36
- --color-foreground: light-dark(var(--color-neutral-900), var(--color-neutral-100));
37
- --color-muted-foreground: light-dark(var(--color-neutral-600), var(--color-neutral-400));
38
- --color-subtle-foreground: var(--color-neutral-500);
39
- --color-border: light-dark(var(--color-neutral-300), var(--color-neutral-600));
40
- --color-primary: var(--color-accent-base);
28
+ --color-accent-base: light-dark(#5b3fc4, #a793f4);
29
+ --color-status-success: light-dark(#0c6b43, #5bd295);
30
+ --color-status-warning: light-dark(#7a5000, #efc05b);
31
+ --color-status-danger: light-dark(#ad2f29, #f5807f);
32
+ --color-status-info: light-dark(#15559e, #75c4fa);
33
+ --color-dark-50: #f8f6f3;
34
+ --color-dark-100: #eeece9;
35
+ --color-dark-200: #d9d7d3;
36
+ --color-dark-300: #c6c4c0;
37
+ --color-dark-400: #aba9a5;
38
+ --color-dark-450: #94928e;
39
+ --color-dark-500: #6b6965;
40
+ --color-dark-600: #494744;
41
+ --color-dark-700: #3a3834;
42
+ --color-dark-800: #2d2b28;
43
+ --color-dark-900: #211f1c;
44
+ --color-dark-950: #151411;
45
+ --color-background: light-dark(var(--color-neutral-200), var(--color-dark-900));
46
+ --color-surface: light-dark(var(--color-neutral-100), var(--color-dark-800));
47
+ --color-elevated-surface: light-dark(var(--color-white), var(--color-dark-700));
48
+ --color-foreground: light-dark(var(--color-neutral-900), var(--color-dark-100));
49
+ --color-muted-foreground: light-dark(var(--color-neutral-600), var(--color-dark-400));
50
+ --color-subtle-foreground: light-dark(var(--color-neutral-500), var(--color-dark-450));
51
+ --color-border: light-dark(var(--color-neutral-300), var(--color-dark-600));
52
+ --color-primary: light-dark(#5b3fc4, #684bb5);
41
53
  --color-accent: light-dark(var(--color-accent-800), var(--color-accent-300));
42
54
  --color-success: var(--color-status-success);
43
55
  --color-warning: var(--color-status-warning);
44
56
  --color-danger: var(--color-status-danger);
45
57
  --color-info: var(--color-status-info);
46
- --color-focus: light-dark(var(--color-accent-base), var(--color-accent-300));
47
- --color-disabled: light-dark(var(--color-neutral-400), var(--color-neutral-600));
48
- --color-card: light-dark(var(--color-neutral-100), var(--color-neutral-800));
58
+ --color-focus: var(--color-accent-base);
59
+ --color-disabled: light-dark(var(--color-neutral-400), var(--color-dark-600));
60
+ --color-card: light-dark(var(--color-neutral-100), var(--color-dark-800));
49
61
  --color-card-foreground: var(--color-foreground);
50
- --color-popover: light-dark(var(--color-white), var(--color-neutral-700));
62
+ --color-popover: light-dark(var(--color-white), var(--color-dark-700));
51
63
  --color-popover-foreground: var(--color-foreground);
52
- --color-muted: light-dark(var(--color-neutral-200), var(--color-neutral-700));
53
- --color-sidebar: light-dark(var(--color-neutral-100), var(--color-neutral-950));
64
+ --color-muted: light-dark(var(--color-neutral-200), var(--color-dark-700));
65
+ --color-sidebar: light-dark(var(--color-neutral-100), var(--color-dark-950));
54
66
  --color-sidebar-border: var(--color-border);
55
- --color-border-strong: var(--color-neutral-500);
67
+ --color-border-strong: light-dark(var(--color-neutral-500), var(--color-dark-450));
56
68
  --color-input: var(--color-border);
57
- --color-primary-foreground: light-dark(var(--color-white), var(--color-neutral-900));
58
- --color-primary-hover: light-dark(var(--color-accent-800), var(--color-accent-300));
59
- --color-link: var(--color-primary);
60
- --color-accent-surface: light-dark(var(--color-accent-50), var(--color-accent-950));
61
- --color-accent-surface-hover: light-dark(var(--color-accent-200), var(--color-accent-900));
62
- --color-selected: light-dark(var(--color-accent-300), var(--color-accent-800));
69
+ --color-primary-foreground: light-dark(var(--color-white), #f7f5fe);
70
+ --color-primary-hover: light-dark(var(--color-accent-800), #795dc9);
71
+ --color-link: var(--color-accent-base);
72
+ --color-accent-surface: light-dark(var(--color-accent-50), #2d2843);
73
+ --color-accent-surface-hover: light-dark(var(--color-accent-200), #3a3356);
74
+ --color-selected: light-dark(var(--color-accent-300), #49406d);
63
75
  --color-selected-foreground: var(--color-foreground);
64
- --color-secondary: light-dark(var(--color-neutral-300), var(--color-neutral-700));
76
+ --color-secondary: light-dark(var(--color-neutral-300), var(--color-dark-700));
65
77
  --color-secondary-foreground: var(--color-foreground);
66
- --color-secondary-hover: light-dark(var(--color-neutral-400), var(--color-neutral-600));
78
+ --color-secondary-hover: light-dark(var(--color-neutral-400), var(--color-dark-600));
67
79
  --color-ring: var(--color-focus);
68
- --color-disabled-surface: light-dark(var(--color-neutral-200), var(--color-neutral-800));
69
- --color-skeleton: light-dark(var(--color-neutral-300), var(--color-neutral-700));
80
+ --color-disabled-surface: light-dark(var(--color-neutral-200), var(--color-dark-800));
81
+ --color-skeleton: light-dark(var(--color-neutral-300), var(--color-dark-700));
70
82
  --color-overlay: light-dark(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.6));
71
- --color-hatch: light-dark(var(--color-neutral-300), var(--color-neutral-700));
83
+ --color-hatch: light-dark(var(--color-neutral-300), var(--color-dark-700));
72
84
  --color-corner-mark: var(--color-border-strong);
73
85
  --color-shadow-hairline: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.3));
74
86
  --color-shadow-contact: light-dark(rgba(0, 0, 0, 0.08), rgba(0, 0, 0, 0.4));
75
- --color-success-surface: light-dark(rgba(12, 107, 67, 0.1), rgba(78, 214, 154, 0.12));
76
- --color-success-border: light-dark(rgba(12, 107, 67, 0.3), rgba(78, 214, 154, 0.35));
77
- --color-warning-surface: light-dark(rgba(122, 80, 0, 0.1), rgba(232, 192, 90, 0.12));
78
- --color-warning-border: light-dark(rgba(122, 80, 0, 0.3), rgba(232, 192, 90, 0.35));
79
- --color-danger-surface: light-dark(rgba(173, 47, 41, 0.1), rgba(244, 134, 127, 0.12));
80
- --color-danger-border: light-dark(rgba(173, 47, 41, 0.3), rgba(244, 134, 127, 0.35));
81
- --color-info-surface: light-dark(rgba(21, 85, 158, 0.1), rgba(143, 201, 245, 0.12));
82
- --color-info-border: light-dark(rgba(21, 85, 158, 0.3), rgba(143, 201, 245, 0.35));
83
- --color-danger-foreground: light-dark(var(--color-white), var(--color-neutral-900));
84
- --color-warning-on-dark: #e8c05a;
85
- --color-danger-on-dark: #f4867f;
86
- --color-info-on-dark: #8fc9f5;
87
+ --color-success-surface: light-dark(rgba(12, 107, 67, 0.1), rgba(91, 210, 149, 0.12));
88
+ --color-success-border: light-dark(rgba(12, 107, 67, 0.3), rgba(91, 210, 149, 0.35));
89
+ --color-warning-surface: light-dark(rgba(122, 80, 0, 0.1), rgba(239, 192, 91, 0.12));
90
+ --color-warning-border: light-dark(rgba(122, 80, 0, 0.3), rgba(239, 192, 91, 0.35));
91
+ --color-danger-surface: light-dark(rgba(173, 47, 41, 0.1), rgba(245, 128, 127, 0.12));
92
+ --color-danger-border: light-dark(rgba(173, 47, 41, 0.3), rgba(245, 128, 127, 0.35));
93
+ --color-info-surface: light-dark(rgba(21, 85, 158, 0.1), rgba(117, 196, 250, 0.12));
94
+ --color-info-border: light-dark(rgba(21, 85, 158, 0.3), rgba(117, 196, 250, 0.35));
95
+ --color-danger-foreground: light-dark(var(--color-white), var(--color-dark-950));
96
+ --color-warning-on-dark: #efc05b;
97
+ --color-danger-on-dark: #f5807f;
98
+ --color-info-on-dark: #75c4fa;
87
99
  --font-heading: Inter, system-ui, sans-serif;
88
100
  --font-body: Inter, system-ui, sans-serif;
89
101
  --font-mono: JetBrains Mono, ui-monospace, monospace;
@@ -17,7 +17,7 @@ export const colorNeutral600: string;
17
17
  export const colorNeutral700: string;
18
18
  export const colorNeutral800: string;
19
19
  export const colorNeutral900: string;
20
- /** Darkest neutral. Sidebar and log surfaces that must sit below the app canvas. */
20
+ /** Darkest neutral. Retained for compatibility; the dark theme's sidebar is dark.950. */
21
21
  export const colorNeutral950: string;
22
22
  /** Lightest lilac. Light-theme tinted surface (row hover, ghost hover). */
23
23
  export const colorAccent50: string;
@@ -30,18 +30,40 @@ export const colorAccent600: string;
30
30
  export const colorAccent700: string;
31
31
  export const colorAccent800: string;
32
32
  export const colorAccent900: string;
33
- /** Darkest lilac. Dark-theme tinted surface, the counterpart to accent.50. */
33
+ /** Darkest lilac. Retained ramp step; the dark accent tints are dedicated literals on the semantic layer. */
34
34
  export const colorAccent950: string;
35
- /** Base accent primitive. Airy lilac on dark (10.03:1) and deeper violet on light (6.36:1) — calibrated up from the marketing site accent, which fails AA in both themes. */
35
+ /** Base accent primitive and the theme ink: deeper violet on light (6.36:1 on background), light lilac on dark (6.36:1 on background, 5.5:1 on surface). Flows into link and focus; the deep fill role lives on semantic.primary. */
36
36
  export const colorAccentBase: string;
37
37
  /** Success status primitive. */
38
38
  export const colorStatusSuccess: string;
39
39
  /** Warning status primitive. */
40
40
  export const colorStatusWarning: string;
41
- /** Danger status primitive. */
41
+ /** Danger status primitive. The dark step sits at 4.6:1 on elevated-surface; a darker red missed AA there. */
42
42
  export const colorStatusDanger: string;
43
43
  /** Info status primitive. */
44
44
  export const colorStatusInfo: string;
45
+ /** Lightest dark-ramp step. */
46
+ export const colorDark50: string;
47
+ /** Foreground on dark. Text-eligible: passes WCAG AA 4.5:1 on every dark surface. */
48
+ export const colorDark100: string;
49
+ export const colorDark200: string;
50
+ export const colorDark300: string;
51
+ /** Muted foreground on dark. */
52
+ export const colorDark400: string;
53
+ /** Half step. Strong lines and subtle text share it on dark (border-strong, subtle-foreground), as neutral.500 does for both roles on light: dark.500 misses the 3:1 non-text gate on dark.800 and dark.700. */
54
+ export const colorDark450: string;
55
+ /** Free step between the 450 split and the border pair. Not referenced by the semantic layer: 2.6:1 on dark.800 fails the 3:1 non-text gate. */
56
+ export const colorDark500: string;
57
+ /** Borders and inputs on dark. */
58
+ export const colorDark600: string;
59
+ /** Elevated surfaces, muted and secondary fills on dark. */
60
+ export const colorDark700: string;
61
+ /** Surface and card on dark. */
62
+ export const colorDark800: string;
63
+ /** Background on dark. */
64
+ export const colorDark900: string;
65
+ /** Darkest step. Sidebar on dark, and danger-foreground on a danger fill. */
66
+ export const colorDark950: string;
45
67
  /** App canvas. Surface role, never text. */
46
68
  export const semanticBackground: string;
47
69
  /** Cards, panels, table rows. Surface role. */
@@ -52,11 +74,11 @@ export const semanticElevatedSurface: string;
52
74
  export const semanticForeground: string;
53
75
  /** Secondary text, labels. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
54
76
  export const semanticMutedForeground: string;
55
- /** Placeholders, timestamps, hints. Large text and non-essential metadata only (>=3:1). Never body copy. */
77
+ /** Placeholders, timestamps, hints. Large text and non-essential metadata only (>=3:1). Never body copy. On dark this is dark.450, because dark.500 misses 3:1 on the dark surfaces. */
56
78
  export const semanticSubtleForeground: string;
57
79
  /** Dividers and input outlines. Non-text role. */
58
80
  export const semanticBorder: string;
59
- /** Primary interactive colour: links, active state, primary button fill. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
81
+ /** Primary fill: primary buttons, solid badges, checked controls, the brand mark. Deep violet in both themes; on dark it is a dedicated fill and is not text-eligible, so ink roles (link, focus) take accent.base instead. */
60
82
  export const semanticPrimary: string;
61
83
  /** Emphasis and highlight. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
62
84
  export const semanticAccent: string;
@@ -68,7 +90,7 @@ export const semanticWarning: string;
68
90
  export const semanticDanger: string;
69
91
  /** Neutral informational state. Text-eligible: passes WCAG AA 4.5:1 on background, surface and elevated-surface in both themes. */
70
92
  export const semanticInfo: string;
71
- /** Focus ring. Non-text role. >=3:1 against background. */
93
+ /** Focus ring. Non-text role. >=3:1 against background. The light ink: alias of accent.base in both themes. */
72
94
  export const semanticFocus: string;
73
95
  /** Disabled text and controls. Exempt from contrast minimums per WCAG 1.4.3. */
74
96
  export const semanticDisabled: string;
@@ -86,15 +108,15 @@ export const semanticMuted: string;
86
108
  export const semanticSidebar: string;
87
109
  /** Divider between sidebar and content. */
88
110
  export const semanticSidebarBorder: string;
89
- /** Emphasised line: hover outlines, switch tracks, chart gridlines, corner marks. Non-text role, >=3:1 on background, surface and elevated-surface in both themes. */
111
+ /** Emphasised line: hover outlines, switch tracks, chart gridlines, corner marks. Non-text role, >=3:1 on background, surface and elevated-surface in both themes. On dark this is dark.450, sharing the subtle-text step as neutral.500 does on light. */
90
112
  export const semanticBorderStrong: string;
91
113
  /** Form control outline at rest. */
92
114
  export const semanticInput: string;
93
- /** Text and icons on a primary fill. Inverts with the fill: white on the light-theme violet, near-black on the dark-theme lilac. Passes AA on primary in both themes. */
115
+ /** Text and icons on a primary fill. Near-white on the deep violet fill in both themes. Passes AA on primary in both themes. */
94
116
  export const semanticPrimaryForeground: string;
95
- /** Primary fill on hover — one ramp step away from primary, in the direction that keeps primary-foreground legible. */
117
+ /** Primary fill on hover - one step brighter than primary, keeping primary-foreground legible. */
96
118
  export const semanticPrimaryHover: string;
97
- /** Inline and standalone links. */
119
+ /** Inline and standalone links. The light ink: alias of accent.base in both themes, so links stay text-eligible where the primary fill is not. */
98
120
  export const semanticLink: string;
99
121
  /** Faintest accent tint: row hover, ghost-button hover. Non-text role. */
100
122
  export const semanticAccentSurface: string;
@@ -142,7 +164,7 @@ export const semanticDangerBorder: string;
142
164
  export const semanticInfoSurface: string;
143
165
  /** Info outline for badges and callouts. Non-text role. */
144
166
  export const semanticInfoBorder: string;
145
- /** Text and icons on a danger fill. Inverts with the fill, like primary-foreground. */
167
+ /** Text and icons on a danger fill. Inverts with the fill, like primary-foreground: white on the light-theme red, near-black on the dark-theme salmon. */
146
168
  export const semanticDangerForeground: string;
147
169
  /** Warning on a permanently dark surface (log view, terminal). Theme-invariant on purpose: the surface does not follow color-scheme, so neither can the text. */
148
170
  export const semanticWarningOnDark: string;