@apliteni/apliteni-ui 0.34.3 → 0.36.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.34.3",
3
+ "version": "0.36.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -30,6 +30,11 @@ import { DataTable, Modal, Button } from '@apliteni/apliteni-ui/react';
30
30
 
31
31
  Components: `DataTable`, `Pagination`, `StatBand`, `Modal`, `Drawer`, `CommandPalette`, `Dropdown`, `BackLink`, `Button`, `Badge`, `Card`, `Icon`.
32
32
 
33
+ `Button` accepts `size="xs" | "sm" | "md" | "lg"` (default `md`). Use `xs`
34
+ for a small inline control: `<Button size="xs" variant="ghost" icon="copy" iconOnly>Copy</Button>`.
35
+ Its glyph is 13px and its icon-only target is 24×24px; labelled xs buttons use
36
+ `--text-xs`. The other sizes keep 16px glyphs.
37
+
33
38
  `CommandPalette` renders the kit's `commandPalette()` markup, class for class, and imports the
34
39
  kit's ranking rather than repeating it — so a palette a server rendered and the same palette
35
40
  after a keystroke put the same row first. Three differences from the vanilla one. Two are
@@ -96,6 +101,11 @@ each other on the same page.
96
101
 
97
102
  ## Dropdown
98
103
 
104
+ Use `badge: { text: 'Archivado', tone: 'state' }` for off, unset, disabled or archived
105
+ states, including translated labels. Explicit `tone: 'neutral'` uses body ink. When tone
106
+ is omitted, exact English off/unset/disabled/archive/archived labels fall back to state
107
+ ink and Live to live ink; other labels use body ink. This also applies to vanilla `dropdown()`.
108
+
99
109
  `Dropdown` renders the kit's `dropdown()` markup, class for class, and carries
100
110
  `wireDropdown()`'s keyboard: the arrows open it onto the first row or the selected one and
101
111
  then walk the ring, stepping over a disabled row and wrapping at both ends; Home and End go
@@ -98,4 +98,5 @@
98
98
  }
99
99
  .rx-caret {
100
100
  margin-left: 2px;
101
+ color: var(--muted);
101
102
  }
@@ -7,7 +7,7 @@ declare function Icon({ name }: {
7
7
 
8
8
  type ButtonProps = {
9
9
  variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
10
- size?: 'sm' | 'md' | 'lg';
10
+ size?: 'xs' | 'sm' | 'md' | 'lg';
11
11
  icon?: string;
12
12
  iconRight?: string;
13
13
  iconOnly?: boolean;
@@ -1064,7 +1064,8 @@ var Glyph2 = ({ name, className, hidden }) => /* @__PURE__ */ jsx12(
1064
1064
  function RowBadge({ badge }) {
1065
1065
  const text2 = typeof badge === "string" ? badge : badge.text;
1066
1066
  const given = typeof badge === "string" ? "" : badge.tone || "";
1067
- const tone = given || (/^live$/i.test(String(text2)) ? "live" : "neutral");
1067
+ let tone = given || (/^live$/i.test(String(text2)) ? "live" : "neutral");
1068
+ if (!given && tone === "neutral" && /^(?:off|unset|disabled|archive|archived)$/i.test(text2)) tone = "state";
1068
1069
  return /* @__PURE__ */ jsx12("span", { className: `ui-dropdown__badge is-${tone}`, children: text2 });
1069
1070
  }
1070
1071
  var keyOf = (it) => it.value != null ? `v:${it.value}` : `l:${it.label}`;
@@ -22,7 +22,8 @@ function ddBadge(badge) {
22
22
  if (!badge) return '';
23
23
  const text = typeof badge === 'string' ? badge : badge.text;
24
24
  let tone = typeof badge === 'string' ? '' : (badge.tone || '');
25
- if (!tone) tone = /^live$/i.test(text) ? 'live' : 'neutral';
25
+ if (!tone) tone = /^live$/i.test(text) ? 'live'
26
+ : /^(?:off|unset|disabled|archive|archived)$/i.test(text) ? 'state' : 'neutral';
26
27
  return `<span class="${cx('ui-dropdown__badge', `is-${tone}`)}">${esc(text)}</span>`;
27
28
  }
28
29
 
@@ -30,7 +30,7 @@
30
30
  and outranks a bare class: it would paint this in the host's link colour.
31
31
  (0,2,0) wins without !important — the argument `.ui-nav .ui-nav__item` makes
32
32
  in nav.css. */
33
- .ui-back[href] { color: var(--dim); }
33
+ .ui-back[href] { color: var(--text); }
34
34
  .ui-back[href]:hover { color: var(--strong); background: var(--surface-2); }
35
35
  .ui-back:focus-visible { outline: none; box-shadow: var(--ring); }
36
36
 
@@ -14,7 +14,7 @@
14
14
  font-weight: var(--weight-semibold);
15
15
  padding: 3px 9px;
16
16
  border-radius: var(--radius-pill);
17
- color: var(--muted);
17
+ color: var(--text);
18
18
  background: var(--surface-2);
19
19
  white-space: nowrap;
20
20
  }
@@ -26,18 +26,18 @@
26
26
  .ui-badge--info { color: var(--chip-info-ink); background: var(--chip-info-fill); }
27
27
  .ui-badge--warn { color: var(--chip-warn-ink); background: var(--chip-warn-fill); }
28
28
  .ui-badge--danger { color: var(--chip-danger-ink); background: var(--chip-danger-fill); }
29
- .ui-badge--archive { color: var(--muted); background: var(--surface); }
29
+ .ui-badge--archive { /* muted-ink: state — archived record */ color: var(--muted); background: var(--surface); }
30
30
 
31
31
  /* Semantic status variants — for record/row status in data tables
32
32
  (paid / verified / pending / failed …). Same chip, mapped to meaning. */
33
33
  .ui-badge--success { color: var(--chip-success-ink); background: var(--chip-success-fill); }
34
34
  .ui-badge--pending { color: var(--chip-warn-ink); background: var(--chip-warn-fill); }
35
- .ui-badge--neutral { color: var(--muted); background: var(--surface-2); }
35
+ .ui-badge--neutral { color: var(--text); background: var(--surface-2); }
36
36
 
37
37
  /* The two neutral chips are the exception: their light fill is already in the
38
- surface scale (--surface-3), and their ink is plain --muted. */
39
- :root[data-theme="light"] .ui-badge--neutral,
40
- :root[data-theme="light"] .ui-badge--archive { color: var(--muted); background: var(--surface-3); }
38
+ surface scale (--surface-3), and only archive uses muted state ink. */
39
+ :root[data-theme="light"] .ui-badge--neutral { background: var(--surface-3); }
40
+ :root[data-theme="light"] .ui-badge--archive { /* muted-ink: state — archived record */ color: var(--muted); background: var(--surface-3); }
41
41
 
42
42
  /* Pill: same shape, sentence case, a touch larger — a metadata chip */
43
43
  .ui-pill {
@@ -46,7 +46,7 @@
46
46
  gap: 7px;
47
47
  /* rank: chip */
48
48
  font-size: var(--text-xs);
49
- color: var(--muted);
49
+ color: var(--text);
50
50
  background: var(--surface-2);
51
51
  border-radius: var(--radius-pill);
52
52
  padding: 4px 11px;
@@ -120,7 +120,7 @@ strong {
120
120
  /* rank: label */
121
121
  font-size: var(--text-sm);
122
122
  font-weight: var(--weight-medium);
123
- color: var(--muted);
123
+ color: var(--text);
124
124
  }
125
125
 
126
126
  /* Sensible default size for inline icons that a parent rule doesn't size.
@@ -1,6 +1,6 @@
1
1
  /* ============================================================================
2
2
  * Button — .ui-btn (variants: primary / secondary / ghost / danger)
3
- * (sizes: sm / md / lg, [icon], [disabled], [aria-busy])
3
+ * (sizes: xs / sm / md / lg, [icon], [disabled], [aria-busy])
4
4
  * ========================================================================== */
5
5
 
6
6
  .ui-btn {
@@ -56,7 +56,7 @@
56
56
  .ui-btn--ghost {
57
57
  background: transparent;
58
58
  border-color: transparent;
59
- color: var(--dim);
59
+ color: var(--text);
60
60
  }
61
61
  .ui-btn--ghost:hover {
62
62
  background: var(--surface-2);
@@ -64,7 +64,7 @@
64
64
  color: var(--strong);
65
65
  }
66
66
 
67
- .ui-btn--danger { color: var(--muted); }
67
+ .ui-btn--danger { color: var(--text); }
68
68
  .ui-btn--danger:hover {
69
69
  color: var(--pink);
70
70
  border-color: var(--pink);
@@ -72,11 +72,15 @@
72
72
  }
73
73
 
74
74
  /* -- Sizes -------------------------------------------------------------- */
75
+ /* why: docs/specification.md#extra-small-buttons */
76
+ .ui-btn--xs { --btn-pad-y: 4.5px; --btn-pad-x: 8px; --btn-font: var(--text-xs); min-width: 24px; min-height: 24px; }
77
+ .ui-btn--xs svg { width: 13px; height: 13px; stroke-width: 2.8; }
75
78
  .ui-btn--sm { --btn-pad-y: 6px; --btn-pad-x: 12px; --btn-font: calc(var(--text-sm) - 0.5px); }
76
79
  .ui-btn--lg { --btn-pad-y: 13px; --btn-pad-x: 24px; --btn-font: var(--text-md); border-radius: var(--radius-md); }
77
80
 
78
81
  /* Icon-only */
79
82
  .ui-btn--icon { padding: 9px; border-radius: var(--radius-sm); }
83
+ .ui-btn--icon.ui-btn--xs { padding: 4.5px; }
80
84
  .ui-btn--icon.ui-btn--sm { padding: 6px; }
81
85
 
82
86
  .ui-btn--block { width: 100%; }
@@ -91,7 +95,7 @@
91
95
  .ui-btn[aria-disabled="true"] {
92
96
  background: var(--disabled-surface);
93
97
  border-color: var(--disabled-border);
94
- color: var(--disabled-ink);
98
+ /* muted-ink: state — unavailable control */ color: var(--disabled-ink);
95
99
  cursor: not-allowed;
96
100
  pointer-events: none;
97
101
  }
@@ -100,12 +104,12 @@
100
104
  .ui-btn--ghost[aria-disabled="true"] {
101
105
  background: transparent;
102
106
  border-color: transparent;
103
- color: var(--disabled-ink-bare);
107
+ /* muted-ink: state — unavailable control */ color: var(--disabled-ink-bare);
104
108
  }
105
109
  /* With no box, the ink is read on whatever is behind it, so the ghost takes an ink
106
110
  set for the dullest ground: in dark 7.49 / 6.31 / 7.00 / 5.44 on --bg / --surface /
107
- --surface-2 / --surface-3, in light 5.70 / 6.17 / 5.20 / 5.39. The enabled ghost's
108
- --dim reads about 1.5 times that in both themes. #273 cut this ink to rescue two
111
+ --surface-2 / --surface-3, in light 5.70 / 6.17 / 5.20 / 5.39. The enabled ghost now uses body
112
+ ink, keeping its words readable without changing the disabled pair. #273 cut this ink to rescue two
109
113
  dark pairs from under #220's floor; #295 re-picked --muted against the elevation
110
114
  ladder and lifted both clear, so it is the margin now rather than the rescue.
111
115
 
@@ -139,6 +143,7 @@
139
143
  transform-origin: center;
140
144
  animation: ui-bars-in var(--dur-fast) var(--ease) both;
141
145
  }
146
+ .ui-btn--xs .ui-btn__bars { left: 4px; right: 4px; bottom: 2px; height: 2px; }
142
147
  .ui-btn--sm .ui-btn__bars { left: 7px; right: 7px; bottom: 4px; }
143
148
  .ui-btn--lg .ui-btn__bars { left: 14px; right: 14px; bottom: 7px; }
144
149
  .ui-btn__bars i {
@@ -10,9 +10,9 @@
10
10
  background: var(--surface-2);
11
11
  font-size: calc(var(--text-sm) + 0.5px);
12
12
  line-height: 1.55;
13
- color: var(--dim);
13
+ color: var(--text);
14
14
  }
15
- .ui-callout__icon { width: 18px; height: 18px; flex: none; margin-top: 1px; color: var(--muted); }
15
+ .ui-callout__icon { width: 18px; height: 18px; flex: none; margin-top: 1px; /* muted-ink: glyph — nonverbal callout icon */ color: var(--muted); }
16
16
  .ui-callout__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 2.1; } /* 2.1 of a 24 box drawn at 18px = 1.58 CSS px, which is what buys the 3:1 graphic bar rather than the 4.5:1 text one. docs/specification.md#icons-and-glyphs */
17
17
  .ui-callout b { color: var(--text); font-weight: var(--weight-semibold); }
18
18
  .ui-callout code { font-family: var(--font-mono); font-size: 0.92em; color: var(--text); }
@@ -61,7 +61,7 @@
61
61
  .ui-toast--danger { --toast-accent: var(--pink); --toast-action-ink: var(--chip-danger-ink); --toast-glow: var(--glow-pink); --toast-on: var(--danger-contrast); --toast-solid: var(--signal-solid-danger); }
62
62
  .ui-toast--warn { --toast-accent: var(--amber); --toast-action-ink: var(--chip-warn-ink); --toast-glow: color-mix(in srgb, var(--amber) 14%, transparent); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-warn); }
63
63
  .ui-toast--info { --toast-accent: var(--cyan); --toast-action-ink: var(--chip-info-ink); --toast-glow: var(--glow-cyan); --toast-on: var(--signal-contrast); --toast-solid: var(--signal-solid-info); }
64
- .ui-toast--neutral { --toast-accent: var(--muted); --toast-action-ink: var(--muted); --toast-glow: var(--surface-2); --toast-on: var(--signal-solid-ink); --toast-solid: var(--signal-solid-neutral); }
64
+ .ui-toast--neutral { --toast-accent: var(--muted); --toast-action-ink: var(--text); --toast-glow: var(--surface-2); --toast-on: var(--signal-solid-ink); --toast-solid: var(--signal-solid-neutral); }
65
65
 
66
66
  /* left status marker (soft + outline; solid is already a full fill) */
67
67
  .ui-toast--soft::before,
@@ -74,7 +74,7 @@
74
74
  .ui-toast__icon svg { width: 13px; height: 13px; stroke: currentColor; fill: none; stroke-width: 2.8; } /* 2.8 of a 24 box drawn at 13px = 1.52 CSS px. The circle has no room for a bigger glyph, so the weight comes out of the stroke. docs/specification.md#icons-and-glyphs */
75
75
  .ui-toast__body { flex: 1; min-width: 0; }
76
76
  .ui-toast__title { font-weight: var(--weight-medium); color: var(--strong); }
77
- .ui-toast__text { color: var(--dim); }
77
+ .ui-toast__text { color: var(--text); }
78
78
 
79
79
  /* trailing action ("Undo" / "Retry") — status-coloured TEXT, so it takes the
80
80
  text-grade ink rather than the accent.
@@ -87,7 +87,7 @@
87
87
  .ui-toast__action { flex: none; align-self: center; background: none; border: 0; padding: 4px 9px; border-radius: var(--radius-xs); font: inherit; font-weight: var(--weight-medium); color: var(--toast-action-ink); cursor: pointer; }
88
88
  .ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); }
89
89
 
90
- .ui-toast__close { flex: none; align-self: flex-start; background: none; border: 0; color: var(--muted); cursor: pointer; padding: 2px; display: grid; place-items: center; position: relative; font: inherit; }
90
+ .ui-toast__close { flex: none; align-self: flex-start; background: none; border: 0; /* muted-ink: glyph — dismiss icon */ color: var(--muted); cursor: pointer; padding: 2px; display: grid; place-items: center; position: relative; font: inherit; }
91
91
  .ui-toast__close svg { width: 15px; height: 15px; stroke: currentColor; fill: none; stroke-width: 2.5; }
92
92
  /* The target, not the ink. WCAG 2.5.8 measures what a pointer can land on, and
93
93
  the drawn button is 19x19 — a 15px glyph in 2px of padding. Growing the box
@@ -142,14 +142,14 @@
142
142
  (--chip-success-ink in light). The opacity: 0.82 this replaced measured 4.19
143
143
  there, and 2.25 on the dark danger fill — the pair was correct and the
144
144
  dilution then spent it. */
145
- .ui-toast--solid .ui-toast__text { color: color-mix(in srgb, var(--toast-ink) 90%, transparent); }
145
+ .ui-toast--solid .ui-toast__text { color: var(--toast-ink); }
146
146
  .ui-toast--solid .ui-toast__icon { background: color-mix(in srgb, var(--toast-ink) 22%, transparent); color: var(--toast-ink); }
147
147
  .ui-toast--solid .ui-toast__action { color: var(--toast-ink); }
148
148
  /* Hover darkens the FILL rather than lightening the ink. Washing the ink over
149
149
  itself was the one surface #149 did not reach, and it worked against the pair
150
150
  it was drawn from: it pulled the fill towards the ink it has to clear. */
151
151
  .ui-toast--solid .ui-toast__action:hover { background: color-mix(in srgb, var(--signal-contrast) 14%, transparent); }
152
- .ui-toast--solid .ui-toast__close { color: color-mix(in srgb, var(--toast-ink) 68%, transparent); }
152
+ .ui-toast--solid .ui-toast__close { /* muted-ink: glyph — dismiss icon on solid toast */ color: color-mix(in srgb, var(--toast-ink) 68%, transparent); }
153
153
  .ui-toast--solid .ui-toast__close:hover { color: var(--toast-ink); }
154
154
  .ui-toast--solid .ui-toast__timer { background: color-mix(in srgb, var(--toast-ink) 55%, transparent); }
155
155
 
@@ -191,4 +191,4 @@
191
191
  }
192
192
  .ui-success__check svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 2.4; }
193
193
  .ui-success__title { color: var(--strong); font-size: var(--text-lg); font-weight: var(--weight-semibold); margin-bottom: 5px; }
194
- .ui-success__sub { color: var(--dim); font-size: var(--text-base); }
194
+ .ui-success__sub { color: var(--text); font-size: var(--text-base); }
@@ -64,7 +64,7 @@
64
64
  margin: 0 0 5px;
65
65
  }
66
66
  .ui-card__sub {
67
- color: var(--dim);
67
+ color: var(--text);
68
68
  font-size: var(--text-base);
69
69
  line-height: var(--leading-snug);
70
70
  margin-bottom: var(--space-5);
@@ -80,7 +80,7 @@
80
80
  }
81
81
  .ui-card__row + .ui-card__row { border-top: 1px solid var(--border); }
82
82
  .ui-card__row .lab { color: var(--strong); font-size: var(--text-md); font-weight: var(--weight-medium); }
83
- .ui-card__row .hint { color: var(--dim); font-size: calc(var(--text-sm) + 0.5px); margin-top: 3px; }
83
+ .ui-card__row .hint { color: var(--text); font-size: calc(var(--text-sm) + 0.5px); margin-top: 3px; }
84
84
 
85
85
  /* Icon chip that leads a card header */
86
86
  .ui-card__icon {
@@ -18,7 +18,7 @@
18
18
  /* rank: label */
19
19
  font-size: var(--text-sm);
20
20
  font-weight: var(--weight-medium);
21
- color: var(--muted);
21
+ color: var(--text);
22
22
  }
23
23
  .ui-snippet pre {
24
24
  margin: 0;
@@ -33,7 +33,7 @@
33
33
  .ui-snippet__copy {
34
34
  background: none;
35
35
  border: 0;
36
- color: var(--muted);
36
+ color: var(--text);
37
37
  cursor: pointer;
38
38
  font-family: var(--font-sans);
39
39
  font-size: calc(var(--text-xs) + 1px);
@@ -57,7 +57,7 @@
57
57
  .ui-snippet pre .f { color: var(--cyan); }
58
58
  .ui-snippet pre .s { color: var(--green); }
59
59
  .ui-snippet pre .u { color: var(--cyan); }
60
- .ui-snippet pre .c { color: var(--muted); }
60
+ .ui-snippet pre .c { color: var(--text); font-style: italic; }
61
61
 
62
62
  /* Reveal: a live secret/token, tinted green */
63
63
  .ui-snippet--reveal {
@@ -99,7 +99,7 @@
99
99
  }
100
100
  .ui-cmdk__search-ic {
101
101
  display: inline-flex;
102
- color: var(--muted);
102
+ /* muted-ink: glyph — search icon */ color: var(--muted);
103
103
  flex: none;
104
104
  }
105
105
  /* 2.2 of a 24 box drawn at 17px = 1.56 CSS px, which is what keeps the mark a
@@ -130,7 +130,7 @@
130
130
  already carries one, and the caret says where the typing goes. */
131
131
  outline: none;
132
132
  }
133
- .ui-cmdk__input::placeholder { color: var(--muted); }
133
+ .ui-cmdk__input::placeholder { /* muted-ink: placeholder — empty command search */ color: var(--muted); }
134
134
 
135
135
  /* -- The list ------------------------------------------------------------- */
136
136
 
@@ -149,7 +149,7 @@
149
149
  padding: var(--space-2) var(--space-3) var(--space-1);
150
150
  font-size: var(--text-sm);
151
151
  font-weight: var(--weight-medium);
152
- color: var(--muted);
152
+ color: var(--text);
153
153
  }
154
154
 
155
155
  /* Row — [icon] [label + description] [badge] [keys]. A <div role="option">, so
@@ -183,7 +183,7 @@
183
183
 
184
184
  .ui-cmdk__ic {
185
185
  display: inline-flex;
186
- color: var(--dim);
186
+ /* muted-ink: glyph — result icon */ color: var(--dim);
187
187
  flex: none;
188
188
  }
189
189
  /* 2.3 of a 24 box drawn at 16px = 1.53 CSS px. Same arithmetic as the dropdown's
@@ -208,10 +208,10 @@
208
208
  }
209
209
  .ui-cmdk__desc {
210
210
  font-size: var(--text-sm);
211
- /* --dim and not --muted: a row's note is how two rows called INV-4812 and
211
+ /* Body ink: a row's note is how two rows called INV-4812 and
212
212
  INV-4809 are told apart, which is not something a reader may skip.
213
- why: docs/specification.md#colour-and-contrast */
214
- color: var(--dim);
213
+ why: docs/specification.md#text-ink */
214
+ color: var(--text);
215
215
  white-space: nowrap;
216
216
  overflow: hidden;
217
217
  text-overflow: ellipsis;
@@ -224,7 +224,7 @@
224
224
  border-radius: var(--radius-pill);
225
225
  border: 1px solid var(--border);
226
226
  font-size: var(--text-xs);
227
- color: var(--dim);
227
+ color: var(--text);
228
228
  }
229
229
 
230
230
  /* Destructive rows take the kit's danger ink, never the accent, and stay quiet
@@ -232,8 +232,8 @@
232
232
  writes with :hover. A palette row is reached with an arrow key far more often
233
233
  than with a pointer, so `.is-active` carries the signal beside :hover: it is
234
234
  the same moment, and it is the row Enter would run.
235
- why: docs/specification.md#colour-and-contrast */
236
- .ui-cmdk__item.is-danger .ui-cmdk__label { color: var(--muted); }
235
+ why: docs/specification.md#text-ink */
236
+ .ui-cmdk__item.is-danger .ui-cmdk__label { color: var(--text); }
237
237
  .ui-cmdk__item.is-danger:hover .ui-cmdk__label,
238
238
  .ui-cmdk__item.is-danger.is-active .ui-cmdk__label { color: var(--pink); }
239
239
  .ui-cmdk__item.is-danger.is-active .ui-cmdk__ic { color: var(--pink); }
@@ -242,11 +242,11 @@
242
242
  for a control with no box of its own: --disabled-surface IS --surface-2,
243
243
  which is the panel behind it, so a disabled row paints no box a reader can
244
244
  see and its label is read on whatever the panel is.
245
- why: docs/specification.md#colour-and-contrast */
246
- .ui-cmdk__item.is-disabled { cursor: default; color: var(--disabled-ink-bare); }
245
+ why: docs/specification.md#text-ink */
246
+ .ui-cmdk__item.is-disabled { cursor: default; /* muted-ink: state — unavailable control */ color: var(--disabled-ink-bare); }
247
247
  .ui-cmdk__item.is-disabled .ui-cmdk__label,
248
248
  .ui-cmdk__item.is-disabled .ui-cmdk__desc,
249
- .ui-cmdk__item.is-disabled .ui-cmdk__ic { color: var(--disabled-ink-bare); }
249
+ .ui-cmdk__item.is-disabled .ui-cmdk__ic { /* muted-ink: state — unavailable control */ color: var(--disabled-ink-bare); }
250
250
 
251
251
  /* -- Keys, empty state, legend -------------------------------------------- */
252
252
 
@@ -262,7 +262,7 @@
262
262
  font-family: var(--font-sans);
263
263
  font-size: var(--text-xs);
264
264
  line-height: 1.5;
265
- color: var(--dim);
265
+ color: var(--text);
266
266
  text-align: center;
267
267
  }
268
268
 
@@ -271,7 +271,7 @@
271
271
  padding: var(--space-6) var(--space-4) var(--space-8);
272
272
  text-align: center;
273
273
  font-size: var(--text-base);
274
- color: var(--dim);
274
+ color: var(--text);
275
275
  }
276
276
 
277
277
  .ui-cmdk__foot {
@@ -281,7 +281,7 @@
281
281
  padding: var(--space-2) var(--space-4);
282
282
  border-top: 1px solid var(--border);
283
283
  font-size: var(--text-xs);
284
- color: var(--muted);
284
+ color: var(--text);
285
285
  }
286
286
  .ui-cmdk__foot span { display: inline-flex; align-items: center; gap: 5px; }
287
287
 
@@ -105,7 +105,7 @@
105
105
  font-size: var(--text-sm);
106
106
  font-weight: var(--weight-normal);
107
107
  line-height: 1.55;
108
- color: var(--muted);
108
+ color: var(--text);
109
109
  }
110
110
  .ui-confirm__acts {
111
111
  display: flex;
@@ -159,7 +159,7 @@
159
159
  width: 30px; height: 30px;
160
160
  flex: none;
161
161
  cursor: pointer;
162
- color: var(--muted);
162
+ /* muted-ink: glyph — dismiss icon */ color: var(--muted);
163
163
  background: transparent;
164
164
  border: 1px solid transparent;
165
165
  border-radius: var(--radius-sm);
@@ -232,7 +232,7 @@
232
232
  padding-block: var(--space-1);
233
233
  overflow-wrap: anywhere;
234
234
  }
235
- .ui-drawer__row dt { color: var(--muted); }
235
+ .ui-drawer__row dt { color: var(--text); }
236
236
  .ui-drawer__row dd { color: var(--text); font-variant-numeric: tabular-nums; }
237
237
  .ui-drawer__rows + * { margin-top: var(--space-3); }
238
238
 
@@ -25,7 +25,7 @@
25
25
  }
26
26
  .ui-dropdown__trigger:hover { border-color: var(--accent); }
27
27
  .ui-dropdown__trigger:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--accent); }
28
- .ui-dropdown__pre { color: var(--muted); }
28
+ .ui-dropdown__pre { color: var(--text); }
29
29
  .ui-dropdown__value { color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
30
30
 
31
31
  /* Chevron — border caret that flips when the container is .open. Its ink is an L
@@ -156,8 +156,8 @@
156
156
  .ui-dropdown__item:focus-visible { outline: none; background: var(--surface-3); box-shadow: var(--ring); }
157
157
  .ui-dropdown__main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
158
158
  .ui-dropdown__label { font-family: var(--font-sans); font-size: calc(var(--text-sm) - 0.5px); color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
159
- .ui-dropdown__desc { font-size: var(--text-xs); color: var(--muted); line-height: 1.35; }
160
- .ui-dropdown__ic { display: inline-flex; flex: none; color: var(--muted); }
159
+ .ui-dropdown__desc { font-size: var(--text-xs); color: var(--text); line-height: 1.35; }
160
+ .ui-dropdown__ic { display: inline-flex; flex: none; /* muted-ink: glyph — option icon */ color: var(--muted); }
161
161
  .ui-dropdown__ic svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 2.1; }
162
162
 
163
163
  /* Trailing selected tick (listbox variant) */
@@ -178,7 +178,7 @@
178
178
  border-radius: var(--radius-pill);
179
179
  margin-left: auto;
180
180
  flex: none;
181
- color: var(--muted);
181
+ color: var(--text);
182
182
  /* A chip is the top step. why: docs/specification.md#elevation */
183
183
  background: var(--surface-3);
184
184
  }
@@ -190,13 +190,13 @@
190
190
  .ui-dropdown__badge.is-accent { color: var(--accent); background: var(--surface-3); }
191
191
 
192
192
  /* Disabled + danger rows */
193
- .ui-dropdown__item.is-disabled { color: var(--disabled-ink); cursor: not-allowed; pointer-events: none; }
193
+ .ui-dropdown__item.is-disabled { /* muted-ink: state — unavailable control */ color: var(--disabled-ink); cursor: not-allowed; pointer-events: none; }
194
194
  .ui-dropdown__item.is-disabled .ui-dropdown__label,
195
195
  .ui-dropdown__item.is-disabled .ui-dropdown__desc { color: inherit; }
196
- /* Quiet at rest, --pink on the way to being pressed. Both states, not hover alone:
196
+ /* Body ink at rest, --pink on the way to being pressed. Both states, not hover alone:
197
197
  the ring says where the reader is, not that this row ends something.
198
198
  why: docs/specification.md#a-dropdown-row-is-a-div-a-link-or-a-button */
199
- .ui-dropdown__item.is-danger .ui-dropdown__label { color: var(--muted); }
199
+ .ui-dropdown__item.is-danger .ui-dropdown__label { color: var(--text); }
200
200
  .ui-dropdown__item.is-danger:hover .ui-dropdown__label,
201
201
  .ui-dropdown__item.is-danger:focus-visible .ui-dropdown__label { color: var(--pink); }
202
202
 
@@ -207,7 +207,7 @@
207
207
  /* rank: label */
208
208
  font-size: var(--text-sm);
209
209
  font-weight: var(--weight-medium);
210
- color: var(--muted);
210
+ color: var(--text);
211
211
  padding: 7px 12px 4px;
212
212
  }
213
213
 
@@ -244,7 +244,7 @@
244
244
  position: absolute;
245
245
  left: 11px;
246
246
  display: inline-flex;
247
- color: var(--muted);
247
+ /* muted-ink: glyph — search icon */ color: var(--muted);
248
248
  pointer-events: none;
249
249
  }
250
250
  /* 16px at 2.4 paints 1.6px, over the 1.5px floor stories/glyph-stroke.test.js holds. */
@@ -273,7 +273,7 @@
273
273
  why: docs/specification.md#elevation */
274
274
  :root[data-theme="light"] .ui-dropdown__search-input { background: var(--bg); }
275
275
 
276
- .ui-dropdown__search-input::placeholder { color: var(--muted); }
276
+ .ui-dropdown__search-input::placeholder { /* muted-ink: placeholder — empty search field */ color: var(--muted); }
277
277
  .ui-dropdown__search-input:hover { border-color: var(--border-strong); }
278
278
  .ui-dropdown__search-input:focus-visible { outline: none; border-color: var(--accent); box-shadow: var(--ring); }
279
279
 
@@ -291,4 +291,6 @@
291
291
  .ui-dropdown__none { display: flex; flex-direction: column; gap: 3px; padding: 14px 12px 10px; text-align: center; }
292
292
  .ui-dropdown__none:empty { display: none; }
293
293
  .ui-dropdown__none-title { font-size: calc(var(--text-sm) - 0.5px); font-weight: 600; color: var(--strong); }
294
- .ui-dropdown__none-hint { font-size: var(--text-xs); color: var(--muted); line-height: 1.35; }
294
+ .ui-dropdown__none-hint { font-size: var(--text-xs); color: var(--text); line-height: 1.35; }
295
+
296
+ .ui-dropdown__badge.is-state { /* muted-ink: state — off, unset, disabled or archived badge */ color: var(--muted); }
@@ -22,7 +22,7 @@
22
22
  text-align: center;
23
23
  gap: 8px;
24
24
  padding: 48px 24px;
25
- color: var(--muted);
25
+ color: var(--text);
26
26
  }
27
27
 
28
28
  /* Duotone illustration slot. */
@@ -77,7 +77,7 @@
77
77
  width: 32px;
78
78
  height: 32px;
79
79
  margin: 0 auto 14px;
80
- color: var(--muted);
80
+ /* muted-ink: glyph — empty-state illustration */ color: var(--muted);
81
81
  opacity: 0.6;
82
82
  }
83
83
  .ui-empty__icon svg {
@@ -76,30 +76,30 @@
76
76
  .ui-fbc__head { display: flex; align-items: center; gap: 12px; padding: 17px 20px 0; }
77
77
  .ui-fbc__chip { display: inline-flex; align-items: center; gap: 8px; color: var(--accent); font-size: var(--text-sm); font-weight: var(--weight-semibold); letter-spacing: .01em; white-space: nowrap; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
78
78
  .ui-fbc__chip svg { width: 14px; height: 14px; flex: none; opacity: .85; stroke-width: 2.7; }
79
- .ui-fbc__x { margin-left: auto; background: var(--surface-2); color: var(--dim); border: 0; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; flex: none; font: inherit; }
79
+ .ui-fbc__x { margin-left: auto; background: var(--surface-2); /* muted-ink: glyph — dismiss icon */ color: var(--dim); border: 0; width: 28px; height: 28px; border-radius: 8px; cursor: pointer; display: grid; place-items: center; flex: none; font: inherit; }
80
80
  .ui-fbc__x:hover { color: var(--strong); }
81
81
  .ui-fbc__x svg { width: 14px; height: 14px; stroke-width: 2.7; }
82
82
  .ui-fbc__quote { margin: 14px 20px 0; background: var(--surface-2); border-radius: 12px; padding: 12px 15px; display: flex; gap: 11px; }
83
83
  .ui-fbc__qm { color: var(--accent); font-size: var(--text-xl); font-weight: var(--weight-bold); line-height: .9; flex: none; opacity: .55; }
84
- .ui-fbc__quote q { quotes: none; color: var(--dim); font-style: italic; font-size: calc(var(--text-sm) + 0.5px); line-height: 1.55; }
84
+ .ui-fbc__quote q { quotes: none; color: var(--text); font-style: italic; font-size: calc(var(--text-sm) + 0.5px); line-height: 1.55; }
85
85
  .ui-fbc__body { padding: 14px 20px 0; }
86
86
  .ui-fbcomposer textarea {
87
87
  width: 100%; min-height: 96px; resize: vertical; font-family: var(--font-sans); font-size: var(--text-base); line-height: 1.55;
88
88
  color: var(--strong); background: var(--surface-2); border: 0; border-radius: 12px; padding: 13px 15px; transition: box-shadow var(--dur-fast) var(--ease);
89
89
  }
90
- .ui-fbcomposer textarea::placeholder { color: var(--muted); }
90
+ .ui-fbcomposer textarea::placeholder { /* muted-ink: placeholder — empty feedback field */ color: var(--muted); }
91
91
  .ui-fbcomposer textarea:focus { outline: none; box-shadow: 0 0 0 2px var(--accent); }
92
92
  .ui-fbc__foot { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 16px 20px 18px; }
93
93
 
94
94
  .ui-fbbtn { font-family: var(--font-sans); font-size: calc(var(--text-sm) + 0.5px); font-weight: var(--weight-semibold); border-radius: 10px; padding: 9px 17px; cursor: pointer; border: 0; transition: filter var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
95
- .ui-fbbtn.ghost { background: transparent; color: var(--dim); }
95
+ .ui-fbbtn.ghost { background: transparent; color: var(--text); }
96
96
  .ui-fbbtn.ghost:hover { color: var(--strong); background: var(--surface-2); }
97
97
  .ui-fbbtn.primary { background: var(--accent-strong); color: var(--accent-contrast); display: inline-flex; align-items: center; gap: 9px; }
98
98
  .ui-fbbtn.primary:hover { filter: brightness(1.08); }
99
99
  /* Same flat pair as .ui-btn:disabled, for the same reason (#220). */
100
100
  .ui-fbbtn.primary:disabled {
101
101
  background: var(--disabled-surface);
102
- color: var(--disabled-ink);
102
+ /* muted-ink: state — unavailable control */ color: var(--disabled-ink);
103
103
  cursor: default;
104
104
  filter: none;
105
105
  }
@@ -115,7 +115,7 @@
115
115
  .ui-fbck-m { fill: none; stroke: var(--green); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 118; stroke-dashoffset: 118; animation: uiFbCkDraw .6s cubic-bezier(.5,0,.3,1) .1s forwards; /* motion: choreographed — the mark draws while the spring is still settling */ }
116
116
  .ui-fbck-s { fill: none; stroke: var(--sheen); stroke-width: 9; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 15 400; stroke-dashoffset: 133; filter: drop-shadow(0 0 8px var(--green)) drop-shadow(0 0 15px var(--green)); opacity: 0; animation: uiFbCkComet .6s cubic-bezier(.5,0,.3,1) .1s forwards; /* motion: choreographed — the comet rides the mark, so it shares its duration and delay exactly */ }
117
117
  .ui-fbc__done h4 { color: var(--strong); font-size: calc(var(--text-lg) + 1px); font-weight: var(--weight-semibold); letter-spacing: -.01em; margin-bottom: 7px; }
118
- .ui-fbc__done p { color: var(--dim); font-size: calc(var(--text-sm) + 0.5px); line-height: 1.55; max-width: var(--prose-caption); margin: 0 auto; }
118
+ .ui-fbc__done p { color: var(--text); font-size: calc(var(--text-sm) + 0.5px); line-height: 1.55; max-width: var(--prose-caption); margin: 0 auto; }
119
119
  .ui-fbc__done p b { color: var(--text); font-weight: var(--weight-semibold); }
120
120
  .ui-fbc__done .row { margin-top: 18px; display: flex; gap: 10px; justify-content: center; }
121
121
  @keyframes uiFbCkDraw { to { stroke-dashoffset: 0; } }
@@ -9,10 +9,10 @@
9
9
  .ui-footer {
10
10
  --footer-surface: transparent;
11
11
  --footer-border: var(--border);
12
- --footer-link: var(--muted);
12
+ --footer-link: var(--text);
13
13
  --footer-link-hover: var(--strong);
14
14
  --footer-title: var(--strong);
15
- --footer-text: var(--dim);
15
+ --footer-text: var(--text);
16
16
 
17
17
  background: var(--footer-surface);
18
18
  border-top: 1px solid var(--footer-border);
@@ -13,7 +13,7 @@
13
13
  /* Required marker. Decoration only — the control carries `required`, which is
14
14
  what assistive technology reads, so this asterisk is aria-hidden. */
15
15
  .ui-field__req { color: var(--pink); margin-left: 4px; }
16
- .ui-field__hint { color: var(--muted); font-size: var(--text-sm); margin-top: 7px; line-height: 1.5; }
16
+ .ui-field__hint { color: var(--text); font-size: var(--text-sm); margin-top: 7px; line-height: 1.5; }
17
17
  .ui-field__error { color: var(--pink); font-size: var(--text-sm); margin-top: 7px; display: flex; align-items: center; gap: 6px; }
18
18
  .ui-field__error svg { width: 16px; height: 16px; flex: none; stroke-width: 2.4; }
19
19
 
@@ -45,7 +45,7 @@
45
45
  }
46
46
 
47
47
  .ui-input::placeholder,
48
- .ui-textarea::placeholder { color: var(--muted); opacity: 0.9; }
48
+ .ui-textarea::placeholder { /* muted-ink: placeholder — empty form field */ color: var(--muted); opacity: 0.9; }
49
49
 
50
50
  /* The value a reader typed is still the value; it goes quiet, it does not fade
51
51
  into the field under it the way an opacity would take both down together. #220 */
@@ -59,7 +59,7 @@
59
59
  .ui-textarea:disabled {
60
60
  background: var(--disabled-surface);
61
61
  border-color: var(--disabled-border);
62
- color: var(--disabled-ink);
62
+ /* muted-ink: state — unavailable control */ color: var(--disabled-ink);
63
63
  cursor: not-allowed;
64
64
  }
65
65
 
@@ -75,7 +75,7 @@
75
75
  left: 14px;
76
76
  width: 17px;
77
77
  height: 17px;
78
- color: var(--muted);
78
+ /* muted-ink: glyph — field icon */ color: var(--muted);
79
79
  pointer-events: none;
80
80
  }
81
81
  .ui-input-group__icon svg { width: 100%; height: 100%; stroke: currentColor; fill: none; stroke-width: 2.2; }
@@ -163,7 +163,7 @@
163
163
  .ui-app__who b { display: block; color: var(--strong); font-weight: var(--weight-semibold); }
164
164
  /* nowrap is what makes the ellipsis fire — without it the address wraps, and
165
165
  the rail column is max-content, so a long one widens the rail instead. */
166
- .ui-app__who span { display: block; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
166
+ .ui-app__who span { display: block; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
167
167
 
168
168
  /* -- the reader's menu ---------------------------------------------------- */
169
169
  /* The account block is a dropdown() trigger, so it takes the rail's own row skin
@@ -198,7 +198,7 @@
198
198
  `text-overflow` would have nothing to fire against — the inert pair
199
199
  .ui-app__who's own comment warns about. `anywhere` keeps an address with no
200
200
  break in it from widening the panel instead. */
201
- .ui-app__user-panel .ui-dropdown__head span { display: block; color: var(--muted); font-size: var(--text-xs); overflow-wrap: anywhere; }
201
+ .ui-app__user-panel .ui-dropdown__head span { display: block; color: var(--text); font-size: var(--text-xs); overflow-wrap: anywhere; }
202
202
 
203
203
  /* The toggle's cell, at the far end of the brand row: the auto margin puts it there
204
204
  with or without a wordmark beside it, and the offset below rides it back onto the
@@ -221,7 +221,7 @@
221
221
  row under it. The glyph box carries the line instead, in both states.
222
222
  why: docs/specification.md#the-page-shell */
223
223
  .ui-app__fold .ui-nav__ic { height: 1lh; }
224
- .ui-app .ui-app__fold { color: var(--dim); }
224
+ .ui-app .ui-app__fold { color: var(--text); }
225
225
  .ui-app .ui-app__fold:hover,
226
226
  .ui-app .ui-app__fold:focus-visible { color: var(--strong); }
227
227
  /* The width's own clock, not the words', so the mark and the closing edge arrive
@@ -351,7 +351,7 @@
351
351
  font: inherit;
352
352
  font-size: var(--text-sm);
353
353
  text-align: start;
354
- color: var(--muted);
354
+ color: var(--text);
355
355
  /* --surface, not the sunken --surface-2 every other field in the kit takes:
356
356
  those sit on a card and this one sits on the band, and the band IS --bg, the
357
357
  bottom of the ladder — so a sunken fill has nowhere to go but down into its
@@ -402,7 +402,7 @@
402
402
  letter-spacing: var(--tracking-tight);
403
403
  margin-bottom: 8px;
404
404
  }
405
- .ui-app__sub { color: var(--dim); font-size: calc(var(--text-base) + 0.5px); line-height: 1.6; max-width: var(--prose-body); margin-bottom: 32px; }
405
+ .ui-app__sub { color: var(--text); font-size: calc(var(--text-base) + 0.5px); line-height: 1.6; max-width: var(--prose-body); margin-bottom: 32px; }
406
406
  .ui-app__body { display: flex; flex-direction: column; gap: var(--space-5); }
407
407
  /* A flex child's min-width is its content by default, so one wide block — a
408
408
  ledger, a code sample — pushes the whole page sideways instead of scrolling
@@ -509,10 +509,10 @@
509
509
  .ui-auth__brand { display: inline-flex; align-items: center; gap: 10px; margin-bottom: 26px; }
510
510
  .ui-auth__brand span { font-size: calc(var(--text-base) + 0.5px); font-weight: 600; color: var(--strong); }
511
511
  .ui-auth__title { color: var(--strong); font-size: calc(var(--text-xl) + 2px); font-weight: 600; letter-spacing: -0.01em; margin-bottom: 7px; }
512
- .ui-auth__sub { color: var(--dim); font-size: var(--text-base); line-height: 1.55; margin-bottom: 26px; }
513
- .ui-auth__foot { margin-top: 24px; text-align: center; font-size: var(--text-sm); color: var(--muted); }
512
+ .ui-auth__sub { color: var(--text); font-size: var(--text-base); line-height: 1.55; margin-bottom: 26px; }
513
+ .ui-auth__foot { margin-top: 24px; text-align: center; font-size: var(--text-sm); color: var(--text); }
514
514
  .ui-auth__foot a { color: var(--accent); }
515
- .ui-divider { display: flex; align-items: center; gap: 14px; margin: 22px 0; color: var(--muted); font-size: calc(var(--text-xs) + 1px); }
515
+ .ui-divider { display: flex; align-items: center; gap: 14px; margin: 22px 0; color: var(--text); font-size: calc(var(--text-xs) + 1px); }
516
516
  .ui-divider::before, .ui-divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }
517
517
 
518
518
  /* App scope grant list (OAuth consent) */
@@ -528,7 +528,7 @@
528
528
  }
529
529
  .ui-scope__icon svg { width: 21px; height: 21px; stroke: currentColor; fill: none; stroke-width: 1.8; }
530
530
  .ui-scope__t { color: var(--strong); font-size: calc(var(--text-base) - 0.5px); font-weight: 500; }
531
- .ui-scope__d { color: var(--dim); font-size: var(--text-sm); margin-top: 2px; line-height: 1.5; }
531
+ .ui-scope__d { color: var(--text); font-size: var(--text-sm); margin-top: 2px; line-height: 1.5; }
532
532
 
533
533
  /* -- Landing hero -------------------------------------------------------- */
534
534
  .ui-hero {
@@ -567,7 +567,7 @@
567
567
  }
568
568
  .ui-hero__sub {
569
569
  font-size: clamp(16px, 2.1vw, 20px);
570
- color: var(--dim);
570
+ color: var(--text);
571
571
  max-width: var(--prose-lede);
572
572
  margin: 0 auto 34px;
573
573
  line-height: 1.6;
@@ -595,8 +595,8 @@
595
595
  }
596
596
  .ui-feature__icon svg { width: 25px; height: 25px; stroke: currentColor; fill: none; stroke-width: 1.8; }
597
597
  .ui-feature h3 { color: var(--strong); font-size: calc(var(--text-lg) - 1px); font-weight: 600; margin-bottom: 7px; }
598
- .ui-feature p { color: var(--dim); font-size: var(--text-base); line-height: 1.6; }
598
+ .ui-feature p { color: var(--text); font-size: var(--text-base); line-height: 1.6; }
599
599
 
600
600
  .ui-section-head { text-align: center; max-width: var(--panel-lg); margin: 0 auto clamp(40px, 6vh, 64px); }
601
601
  .ui-section-head h2 { color: var(--strong); font-size: clamp(28px, 4vw, 40px); font-weight: 700; letter-spacing: -0.02em; margin-bottom: 14px; }
602
- .ui-section-head p { color: var(--dim); font-size: calc(var(--text-lg) - 1px); line-height: 1.6; }
602
+ .ui-section-head p { color: var(--text); font-size: calc(var(--text-lg) - 1px); line-height: 1.6; }
@@ -89,7 +89,7 @@
89
89
  text-align: center;
90
90
  gap: 8px;
91
91
  padding: 48px 24px;
92
- color: var(--muted);
92
+ color: var(--text);
93
93
  }
94
94
  .ui-denied__seal {
95
95
  width: 44px;
@@ -98,7 +98,7 @@
98
98
  border-radius: 50%;
99
99
  display: grid;
100
100
  place-items: center;
101
- color: var(--dim);
101
+ /* muted-ink: glyph — permission seal */ color: var(--dim);
102
102
  background: color-mix(in srgb, var(--text) 7%, transparent);
103
103
  }
104
104
  .ui-denied__seal svg {
@@ -41,7 +41,7 @@
41
41
  /* rank: label */
42
42
  font-size: var(--text-sm);
43
43
  font-weight: var(--weight-medium);
44
- color: var(--muted);
44
+ color: var(--text);
45
45
  padding: 4px 12px 8px;
46
46
  /* Goes on the labels' clock — a section heading is words, and the fold takes
47
47
  every word on the rail together. */
@@ -105,16 +105,16 @@
105
105
  transition: opacity var(--dur-fast) var(--ease);
106
106
  }
107
107
 
108
- /* Destructive row (sign out, revoke). Quiet at rest, --pink once a pointer or
108
+ /* Destructive row (sign out, revoke). Body ink at rest, --pink once a pointer or
109
109
  the keyboard reaches it — as .ui-btn--danger and .ui-dropdown__item do. */
110
- .ui-nav__item.is-danger { color: var(--muted); }
110
+ .ui-nav__item.is-danger { color: var(--text); }
111
111
  .ui-nav__item.is-danger:hover { background: var(--glow-pink); color: var(--pink); }
112
112
  /* Tabbing onto it is the same "about to click this" the pointer means. The ring
113
113
  says where you are; only this says what the row does. */
114
114
  .ui-nav__item.is-danger:focus-visible { background: var(--glow-pink); color: var(--pink); }
115
- /* A row that is off. No opacity: the label was already --muted and the fade
116
- only pulled it toward the rail behind it (#220). */
117
- .ui-nav__item.is-disabled { color: var(--disabled-ink); cursor: default; pointer-events: none; }
115
+ /* An unavailable row uses disabled ink directly; opacity would also fade its
116
+ background and pull its label toward the rail behind it (#220). */
117
+ .ui-nav__item.is-disabled { /* muted-ink: state — unavailable control */ color: var(--disabled-ink); cursor: default; pointer-events: none; }
118
118
 
119
119
  /* The current row under the pointer. It rests on --surface-3, which is also what
120
120
  a resting row hovers to, so the one row with no hover response was the row the
@@ -141,7 +141,7 @@
141
141
  font-weight: 600;
142
142
  line-height: 1;
143
143
  background: var(--surface-3);
144
- color: var(--dim);
144
+ color: var(--text);
145
145
  /* The count fades with the words. It is never lost: sidebarNav() spells it
146
146
  into the row's aria-label, so "Pending 3" is what a reader hears at either
147
147
  width. */
@@ -267,7 +267,7 @@
267
267
  align-items: center;
268
268
  gap: 8px;
269
269
  padding: 11px 14px;
270
- color: var(--dim);
270
+ color: var(--text);
271
271
  text-decoration: none;
272
272
  font-size: calc(var(--text-base) - 0.5px);
273
273
  font-weight: 500;
@@ -300,7 +300,7 @@
300
300
  color: var(--accent);
301
301
  }
302
302
 
303
- .ui-nav__tab.is-disabled { color: var(--disabled-ink); cursor: default; }
303
+ .ui-nav__tab.is-disabled { /* muted-ink: state — unavailable control */ color: var(--disabled-ink); cursor: default; }
304
304
  .ui-nav__tab .ui-nav__badge { height: 18px; }
305
305
 
306
306
  /* -- Breadcrumbs --------------------------------------------------------- */
@@ -328,7 +328,7 @@
328
328
  /* Carries the block's class as well as its own, for the reason .ui-nav__item
329
329
  does above: a linked crumb is an <a>, and a host stylesheet's `a:link` is
330
330
  (0,1,1), which outranks a bare (0,1,0) class. */
331
- .ui-nav .ui-nav__crumb { color: var(--dim); }
331
+ .ui-nav .ui-nav__crumb { color: var(--text); }
332
332
  a.ui-nav__crumb:hover { color: var(--strong); background: var(--surface-2); }
333
333
  a.ui-nav__crumb:focus-visible { outline: none; box-shadow: var(--ring); }
334
334
  .ui-nav__crumb.is-current { color: var(--strong); font-weight: 500; }
@@ -20,7 +20,7 @@
20
20
  target group in one place as the row count changes width. */
21
21
  .ui-pager__status {
22
22
  margin: 0 auto 0 0;
23
- color: var(--muted);
23
+ color: var(--text);
24
24
  font-variant-numeric: tabular-nums;
25
25
  }
26
26
 
@@ -29,7 +29,7 @@
29
29
  align-items: center;
30
30
  gap: var(--space-2);
31
31
  }
32
- .ui-pager__size-label { color: var(--muted); }
32
+ .ui-pager__size-label { color: var(--text); }
33
33
 
34
34
  /* .ui-select is a form field sized for a form — full width, 12px/15px padding.
35
35
  In a pager it sits in a row of sm buttons, so it takes their scale. Two things
@@ -77,14 +77,14 @@
77
77
 
78
78
  .ui-pager__gap {
79
79
  padding: 0 var(--space-1);
80
- color: var(--muted);
80
+ /* muted-ink: glyph — ellipsis for omitted page controls */ color: var(--muted);
81
81
  }
82
82
 
83
83
  .ui-pager__jump {
84
84
  display: inline-flex;
85
85
  align-items: center;
86
86
  gap: var(--space-2);
87
- color: var(--muted);
87
+ color: var(--text);
88
88
  padding: 0 var(--space-1);
89
89
  }
90
90
  /* Wide enough for four digits and the spinner a number input draws beside them;
@@ -120,6 +120,6 @@
120
120
  discovered.
121
121
  why: docs/specification.md#pagination */
122
122
  a.ui-pager__step,
123
- a.ui-pager__page { color: var(--dim); }
123
+ a.ui-pager__page { color: var(--text); }
124
124
  a.ui-pager__step:hover,
125
125
  a.ui-pager__page:hover { color: var(--strong); }
@@ -15,7 +15,7 @@
15
15
  font-size: calc(var(--text-sm) + 0.5px);
16
16
  font-weight: var(--weight-semibold);
17
17
  letter-spacing: 0.02em;
18
- color: var(--dim);
18
+ color: var(--text);
19
19
  background: none;
20
20
  border: 0;
21
21
  padding: 7px 18px;
@@ -23,7 +23,7 @@
23
23
  cursor: pointer;
24
24
  transition: color var(--dur-med) var(--ease), background var(--dur-med) var(--ease);
25
25
  }
26
- .ui-seg button:hover { color: var(--text); }
26
+ .ui-seg button:hover { color: var(--strong); }
27
27
  /* aria-pressed is what segmented() emits now; aria-selected is kept so markup
28
28
  copied from an older release keeps painting its active pill. */
29
29
  .ui-seg button.is-active,
@@ -27,7 +27,7 @@
27
27
  figure's parts are held close, so the label keeps the band's tighter line.
28
28
  It sits outside the rank table for that one reason, and claims no rank. */
29
29
  .ui-stat__label {
30
- color: var(--dim);
30
+ color: var(--text);
31
31
  font-size: var(--text-sm);
32
32
  font-weight: var(--weight-medium);
33
33
  line-height: var(--leading-snug);
@@ -55,15 +55,15 @@
55
55
  align-items: center;
56
56
  gap: 2px var(--space-1);
57
57
  margin-top: var(--space-2);
58
- color: var(--dim);
58
+ color: var(--text);
59
59
  font-size: var(--text-sm);
60
60
  font-weight: var(--weight-medium);
61
61
  line-height: var(--leading-snug);
62
62
  }
63
63
  .ui-stat__delta svg { width: 14px; height: 14px; flex: none; stroke-width: 2.7; } /* 2.7 of a 24 box drawn at 14px = 1.58 CSS px. docs/specification.md#icons-and-glyphs */
64
64
  .ui-stat__change { font-variant-numeric: tabular-nums; white-space: nowrap; }
65
- .ui-stat__basis { color: var(--muted); font-weight: var(--weight-normal); }
66
- .ui-stat__delta--none { color: var(--muted); font-weight: var(--weight-normal); }
65
+ .ui-stat__basis { color: var(--text); font-weight: var(--weight-normal); }
66
+ .ui-stat__delta--none { color: var(--text); font-weight: var(--weight-normal); }
67
67
 
68
68
  /* Tone is set by the caller and says good or bad news. Direction is the arrow's
69
69
  job, so an unset tone keeps the change neutral whichever way it points. */
@@ -72,7 +72,7 @@
72
72
 
73
73
  /* The trend is the caller's <svg>. margin-top: auto seats it on the bottom of
74
74
  the figure, so trends line up across figures whose text wraps differently. */
75
- .ui-stat__trend { margin-top: auto; padding-top: var(--space-4); color: var(--muted); }
75
+ .ui-stat__trend { margin-top: auto; padding-top: var(--space-4); /* muted-ink: glyph — caller-supplied sparkline */ color: var(--muted); }
76
76
  .ui-stat__trend svg { display: block; width: 100%; height: 32px; overflow: visible; }
77
77
  .ui-stat--good .ui-stat__trend { color: var(--chip-success-ink); }
78
78
  .ui-stat--bad .ui-stat__trend { color: var(--chip-danger-ink); }
@@ -82,7 +82,7 @@
82
82
  margin is under it because it leads the row. */
83
83
  .ui-stats__basis {
84
84
  margin: 0 0 var(--space-5);
85
- color: var(--muted);
85
+ color: var(--text);
86
86
  font-size: var(--text-xs);
87
87
  line-height: var(--leading-snug);
88
88
  }
@@ -64,7 +64,7 @@
64
64
  letter-spacing: -.02em; line-height: 1.2; margin: 0;
65
65
  }
66
66
  .ui-sx__body {
67
- color: var(--dim); font-size: var(--text-base); line-height: 1.6; margin: 9px 0 0; max-width: var(--prose-caption);
67
+ color: var(--text); font-size: var(--text-base); line-height: 1.6; margin: 9px 0 0; max-width: var(--prose-caption);
68
68
  }
69
69
  .ui-sx__actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; }
70
70
 
@@ -166,8 +166,8 @@
166
166
  animation: ui-sx-sweep var(--sx-secs, 5s) linear forwards; /* motion: ambient — the caller sets the countdown; the ring just spends it */
167
167
  }
168
168
  @keyframes ui-sx-sweep { from { --sx-a: 360deg; } to { --sx-a: 0deg; } }
169
- .ui-sx__count-text { color: var(--muted); font-size: var(--text-sm); }
170
- .ui-sx__count-text b { color: var(--dim); font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
169
+ .ui-sx__count-text { color: var(--text); font-size: var(--text-sm); }
170
+ .ui-sx__count-text b { color: var(--text); font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; }
171
171
 
172
172
  /* ---- Reduced motion — static check, no burst / confetti / sweep ---------- */
173
173
  @media (prefers-reduced-motion: reduce) {
@@ -13,14 +13,14 @@
13
13
  /* rank: label */
14
14
  font-size: var(--text-sm);
15
15
  font-weight: var(--weight-medium);
16
- color: var(--muted);
16
+ color: var(--text);
17
17
  padding: 0 var(--space-4) var(--space-3) 0;
18
18
  border-bottom: 1px solid var(--border);
19
19
  }
20
20
  .ui-table td {
21
21
  padding: var(--space-4) var(--space-4) var(--space-4) 0;
22
22
  border-bottom: 1px solid var(--border);
23
- color: var(--dim);
23
+ color: var(--text);
24
24
  vertical-align: middle;
25
25
  white-space: nowrap;
26
26
  }
@@ -28,7 +28,7 @@
28
28
  .ui-table td.ui-table__title { color: var(--strong); font-weight: var(--weight-medium); width: 99%; white-space: normal; }
29
29
  .ui-table th:last-child,
30
30
  .ui-table td.ui-table__act { padding-right: 0; text-align: right; }
31
- .ui-table tr.is-dead td { color: var(--muted); }
31
+ .ui-table tr.is-dead td { /* muted-ink: state — revoked row */ color: var(--muted); }
32
32
  .ui-table tr.is-dead td.ui-table__title { text-decoration: line-through; }
33
33
  .ui-table tbody tr { transition: background var(--dur-fast) var(--ease); }
34
34
 
@@ -79,7 +79,7 @@
79
79
  .ui-table__num { text-align: right; font-variant-numeric: tabular-nums; }
80
80
  .ui-table th.ui-table__num { text-align: right; }
81
81
  .ui-table__num--strong { color: var(--strong); font-weight: var(--weight-semibold); }
82
- .ui-table__code { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--muted); }
82
+ .ui-table__code { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text); }
83
83
 
84
84
  /* The empty state (.ui-empty) — shown when a table/list has no rows — now
85
85
  lives in styles/empty.css as a standalone component (illustration support). */
@@ -14,13 +14,13 @@
14
14
  border: 0;
15
15
  cursor: pointer;
16
16
  font: 600 calc(var(--text-base) - 0.5px)/1 var(--font-sans);
17
- color: var(--dim);
17
+ color: var(--text);
18
18
  padding: 11px 14px;
19
19
  border-bottom: 2px solid transparent;
20
20
  margin-bottom: -1px;
21
21
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
22
22
  }
23
- .ui-tabs__tab:hover { color: var(--text); }
23
+ .ui-tabs__tab:hover { color: var(--strong); }
24
24
  .ui-tabs__tab.is-active { color: var(--strong); border-bottom-color: var(--accent); }
25
25
  .ui-tabs__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 7px 7px 0 0; }
26
26
 
@@ -57,11 +57,11 @@
57
57
 
58
58
  .ui-tip.is-open { opacity: 1; visibility: visible; }
59
59
 
60
- .ui-tip__label { color: var(--muted); }
60
+ .ui-tip__label { color: var(--text); }
61
61
  .ui-tip__value {
62
62
  font-size: var(--text-sm);
63
63
  font-weight: var(--weight-semibold);
64
64
  color: var(--strong);
65
65
  font-variant-numeric: tabular-nums;
66
66
  }
67
- .ui-tip__detail { color: var(--dim); font-variant-numeric: tabular-nums; }
67
+ .ui-tip__detail { color: var(--text); font-variant-numeric: tabular-nums; }
@@ -60,12 +60,12 @@
60
60
  font-size: calc(var(--text-xs) + 1px);
61
61
  font-weight: 600;
62
62
  letter-spacing: 0.04em;
63
- color: var(--muted);
63
+ color: var(--text);
64
64
  padding: 4px 13px;
65
65
  border-radius: 24px;
66
66
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
67
67
  }
68
- .dtsw a:hover { color: var(--text); }
68
+ .dtsw a:hover { color: var(--strong); }
69
69
  .dtsw a.cur { color: var(--strong); background: var(--surface); }
70
70
 
71
71
  /* Theme toggle — a single icon-only switch (no label). */
@@ -107,7 +107,7 @@
107
107
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
108
108
  }
109
109
  .vsw__btn:hover { border-color: var(--accent); }
110
- .vsw__btn .lbl { color: var(--muted); }
110
+ .vsw__btn .lbl { color: var(--text); }
111
111
  .vsw__btn .cur { color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
112
112
  .vsw__btn .car {
113
113
  width: 8px; height: 8px;
@@ -184,7 +184,7 @@
184
184
  why: docs/specification.md#elevation */
185
185
  .vopt:hover { background: var(--surface-3); }
186
186
  .vopt .vname { font-family: var(--font-sans); font-size: calc(var(--text-sm) - 0.5px); color: var(--strong); font-weight: 600; letter-spacing: 0.01em; }
187
- .vopt .vmeta { font-size: var(--text-xs); color: var(--muted); margin-top: 2px; }
187
+ .vopt .vmeta { font-size: var(--text-xs); color: var(--text); margin-top: 2px; }
188
188
  .vopt .vbadge {
189
189
  /* rank: chip */
190
190
  font-size: var(--text-xs);
@@ -196,7 +196,7 @@
196
196
  margin-top: 1px;
197
197
  }
198
198
  .vbadge.live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
199
- .vbadge.arch { color: var(--muted); background: var(--surface-3); }
199
+ .vbadge.arch { /* muted-ink: state — archived version */ color: var(--muted); background: var(--surface-3); }
200
200
  /* The switcher keeps its glyph and drops its label where the shell folds — the
201
201
  same step the rail folds at: docs/specification.md#breakpoints. */
202
202
  @media (max-width: 720px) { .vsw__btn .lbl { display: none; } }
@@ -256,11 +256,11 @@
256
256
  .amenu .ahead .avatar { cursor: default; }
257
257
  .amenu .ahead .aw { min-width: 0; display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
258
258
  .amenu .ahead .anm { color: var(--strong); font-size: var(--text-sm); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
259
- .amenu .ahead .aem { color: var(--muted); font-size: calc(var(--text-xs) + 0.5px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
259
+ .amenu .ahead .aem { color: var(--text); font-size: calc(var(--text-xs) + 0.5px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
260
260
  .amenu a { display: flex; align-items: center; gap: 11px; padding: 10px 15px; color: var(--text); text-decoration: none; font-family: var(--font-sans); font-size: var(--text-sm); cursor: pointer; }
261
261
  .amenu a:hover { background: var(--surface-3); }
262
262
  .amenu a.cur { color: var(--accent); }
263
263
  .amenu a svg { width: 19px; height: 19px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; opacity: 0.85; }
264
264
  .amenu .asep { height: 1px; background: var(--border); margin: 5px 0; }
265
- .amenu a.aout { color: var(--muted); }
265
+ .amenu a.aout { color: var(--text); }
266
266
  .amenu a.aout:hover { color: var(--pink); }