@apliteni/apliteni-ui 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -151,6 +151,13 @@ Each accent re-points only the accent family (`--accent`, `--purple*`, `--glow-p
151
151
  stay put — so **every accent works in both themes** and every component follows with no
152
152
  component-level change.
153
153
 
154
+ Focus uses a surface-coloured gap, a solid band and a decorative glow. Tune
155
+ `--ring-width`, `--ring-color`, `--ring-gap-width` and `--ring-gap` at the root;
156
+ `--ring` remains the composed shadow. Kit surfaces recompose it to match their
157
+ background, so an ancestor's custom `--ring` must also be applied on those surfaces.
158
+ The page shell inherits root overrides; it does not introduce another composition.
159
+ See [the focus-ring contract](docs/specification.md#the-focus-ring).
160
+
154
161
  Both attributes are overrides, not requirements: with neither present the kit paints dark
155
162
  Nebula, and `data-accent` alone paints that accent on the dark theme. An absent `data-theme`
156
163
  is *not* "follow the system" — the kit ships no `prefers-color-scheme` rule, so a host that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apliteni/apliteni-ui",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "workspaces": [
5
5
  "react"
6
6
  ],
package/react/README.md CHANGED
@@ -101,6 +101,11 @@ each other on the same page.
101
101
 
102
102
  ## Dropdown
103
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
+
104
109
  `Dropdown` renders the kit's `dropdown()` markup, class for class, and carries
105
110
  `wireDropdown()`'s keyboard: the arrows open it onto the first row or the selected one and
106
111
  then walk the ring, stepping over a disabled row and wrapping at both ends; Home and End go
@@ -44,6 +44,7 @@
44
44
  .rx-modal {
45
45
  width: min(480px, 92vw);
46
46
  background: var(--bg-elevated);
47
+ --ring-gap: var(--bg-elevated);
47
48
  border: 1px solid var(--border-strong);
48
49
  box-shadow: inset 0 0 0 1px var(--elev-edge, var(--border)), var(--elev-drop);
49
50
  border-radius: var(--radius-xl);
@@ -92,10 +93,11 @@
92
93
  color: inherit;
93
94
  }
94
95
  .rx-sort:focus-visible {
95
- outline: 2px solid var(--accent);
96
- outline-offset: 2px;
96
+ outline: 2px solid transparent;
97
+ box-shadow: var(--ring);
97
98
  border-radius: 4px;
98
99
  }
99
100
  .rx-caret {
100
101
  margin-left: 2px;
102
+ color: var(--muted);
101
103
  }
@@ -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,9 +30,9 @@
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); }
34
- .ui-back[href]:hover { color: var(--strong); background: var(--surface-2); }
35
- .ui-back:focus-visible { outline: none; box-shadow: var(--ring); }
33
+ .ui-back[href] { color: var(--text); }
34
+ .ui-back[href]:hover { color: var(--strong); background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
35
+ .ui-back:focus-visible { outline: 2px solid transparent; box-shadow: var(--ring); }
36
36
 
37
37
  /* 16px, with the stroke raised to match: the glyph set draws at 1.7 on a 24-unit
38
38
  box, which is 1.13 CSS px at this size and under the kit's 1.5 for a stroked
@@ -14,30 +14,30 @@
14
14
  font-weight: var(--weight-semibold);
15
15
  padding: 3px 9px;
16
16
  border-radius: var(--radius-pill);
17
- color: var(--muted);
18
- background: var(--surface-2);
17
+ color: var(--text);
18
+ background: var(--surface-2); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
19
19
  white-space: nowrap;
20
20
  }
21
21
  /* Status chips take the --chip-* pairs, so the light app's solid fills live in
22
22
  tokens.css rather than in a theme override down here: on white the glow-tints
23
23
  wash out and the ink has to deepen to clear AA, and that is a token decision. */
24
- .ui-badge--live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
25
- .ui-badge--soon { color: var(--purple-mid); background: var(--glow-purple); }
26
- .ui-badge--info { color: var(--chip-info-ink); background: var(--chip-info-fill); }
24
+ .ui-badge--live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
25
+ .ui-badge--soon { color: var(--purple-mid); background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
26
+ .ui-badge--info { color: var(--chip-info-ink); background: var(--chip-info-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
27
27
  .ui-badge--warn { color: var(--chip-warn-ink); background: var(--chip-warn-fill); }
28
- .ui-badge--danger { color: var(--chip-danger-ink); background: var(--chip-danger-fill); }
29
- .ui-badge--archive { color: var(--muted); background: var(--surface); }
28
+ .ui-badge--danger { color: var(--chip-danger-ink); background: var(--chip-danger-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
29
+ .ui-badge--archive { /* muted-ink: state — archived record */ color: var(--muted); background: var(--surface); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
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
- .ui-badge--success { color: var(--chip-success-ink); background: var(--chip-success-fill); }
33
+ .ui-badge--success { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
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); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
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); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
40
+ :root[data-theme="light"] .ui-badge--archive { /* muted-ink: state — archived record */ color: var(--muted); background: var(--surface-3); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
41
41
 
42
42
  /* Pill: same shape, sentence case, a touch larger — a metadata chip */
43
43
  .ui-pill {
@@ -46,8 +46,8 @@
46
46
  gap: 7px;
47
47
  /* rank: chip */
48
48
  font-size: var(--text-xs);
49
- color: var(--muted);
50
- background: var(--surface-2);
49
+ color: var(--text);
50
+ background: var(--surface-2); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */
51
51
  border-radius: var(--radius-pill);
52
52
  padding: 4px 11px;
53
53
  font-weight: var(--weight-semibold);
@@ -55,8 +55,8 @@
55
55
  /* The live pill is the same status paint as the live badge, so it takes the
56
56
  same pair. On its own it was --green on --glow-green, which is 3.97:1 over
57
57
  white — under AA, while the badge beside it cleared at 4.64:1. See issue #131. */
58
- .ui-pill--live { color: var(--chip-success-ink); background: var(--chip-success-fill); }
59
- .ui-pill--soon { color: var(--purple-mid); background: var(--glow-purple); }
58
+ .ui-pill--live { color: var(--chip-success-ink); background: var(--chip-success-fill); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
59
+ .ui-pill--soon { color: var(--purple-mid); background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
60
60
 
61
61
  /* Status dot */
62
62
  .ui-dot {
@@ -15,7 +15,7 @@ html {
15
15
  }
16
16
 
17
17
  body {
18
- background: var(--bg);
18
+ background: var(--bg); /* ring-gap: inherit — page background inherits root overrides. */
19
19
  color: var(--text);
20
20
  font-family: var(--font-sans);
21
21
  font-weight: var(--weight-normal);
@@ -27,7 +27,7 @@ body {
27
27
  }
28
28
 
29
29
  ::selection {
30
- background: color-mix(in srgb, var(--accent) 30%, transparent);
30
+ background: color-mix(in srgb, var(--accent) 30%, transparent); /* ring-gap: inherit — selection paint is not a focus container. */
31
31
  color: var(--strong);
32
32
  }
33
33
 
@@ -87,7 +87,7 @@ strong {
87
87
 
88
88
  /* Spotlight — a soft radial from the top-centre (auth cards, focused heroes). */
89
89
  .ui-bg-spotlight::before {
90
- background: radial-gradient(620px 340px at 50% -12%, var(--glow-purple), transparent 70%);
90
+ background: radial-gradient(620px 340px at 50% -12%, var(--glow-purple), transparent 70%); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
91
91
  }
92
92
  /* Grid — a faint token-coloured grid, masked to fade at the edges. */
93
93
  .ui-bg-grid::before {
@@ -107,7 +107,7 @@ strong {
107
107
  }
108
108
  /* Accent wash — a gentle top-down tint in the current accent. */
109
109
  .ui-bg-wash::before {
110
- background: linear-gradient(180deg, var(--glow-purple), transparent 45%);
110
+ background: linear-gradient(180deg, var(--glow-purple), transparent 45%); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
111
111
  }
112
112
 
113
113
  /* Layout helpers used by the example apps */
@@ -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.
@@ -142,6 +142,6 @@ svg:where(:not([width]):not([height])) {
142
142
  .ui-input:focus-visible,
143
143
  .ui-seg button:focus-visible,
144
144
  .ui-switch:focus-visible {
145
- outline: none;
145
+ outline: 2px solid transparent;
146
146
  box-shadow: var(--ring);
147
147
  }
@@ -20,7 +20,7 @@
20
20
  padding: var(--btn-pad-y) var(--btn-pad-x);
21
21
  cursor: pointer;
22
22
  border: 1px solid var(--border);
23
- background: var(--surface-2);
23
+ background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
24
24
  color: var(--text);
25
25
  white-space: nowrap;
26
26
  transition: background var(--dur-fast) var(--ease),
@@ -32,7 +32,7 @@
32
32
 
33
33
  .ui-btn:hover {
34
34
  border-color: var(--accent);
35
- background: color-mix(in srgb, var(--accent) 14%, var(--surface-2));
35
+ background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
36
36
  color: var(--strong);
37
37
  }
38
38
  .ui-btn:active { transform: translateY(1px); }
@@ -56,19 +56,19 @@
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
- background: var(--surface-2);
62
+ background: var(--surface-2); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
63
63
  border-color: transparent;
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);
71
- background: color-mix(in srgb, var(--pink) 10%, transparent);
71
+ background: color-mix(in srgb, var(--pink) 10%, transparent); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
72
72
  }
73
73
 
74
74
  /* -- Sizes -------------------------------------------------------------- */
@@ -93,9 +93,9 @@
93
93
  makes this read as inert; the quiet ink is the rest. #220 */
94
94
  .ui-btn:disabled,
95
95
  .ui-btn[aria-disabled="true"] {
96
- background: var(--disabled-surface);
96
+ background: var(--disabled-surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */
97
97
  border-color: var(--disabled-border);
98
- color: var(--disabled-ink);
98
+ /* muted-ink: state — unavailable control */ color: var(--disabled-ink);
99
99
  cursor: not-allowed;
100
100
  pointer-events: none;
101
101
  }
@@ -104,12 +104,12 @@
104
104
  .ui-btn--ghost[aria-disabled="true"] {
105
105
  background: transparent;
106
106
  border-color: transparent;
107
- color: var(--disabled-ink-bare);
107
+ /* muted-ink: state — unavailable control */ color: var(--disabled-ink-bare);
108
108
  }
109
109
  /* With no box, the ink is read on whatever is behind it, so the ghost takes an ink
110
110
  set for the dullest ground: in dark 7.49 / 6.31 / 7.00 / 5.44 on --bg / --surface /
111
- --surface-2 / --surface-3, in light 5.70 / 6.17 / 5.20 / 5.39. The enabled ghost's
112
- --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
113
113
  dark pairs from under #220's floor; #295 re-picked --muted against the elevation
114
114
  ladder and lifted both clear, so it is the margin now rather than the rescue.
115
115
 
@@ -137,7 +137,7 @@
137
137
  left: 10px; right: 10px; bottom: 5px;
138
138
  height: 3px;
139
139
  border-radius: 999px;
140
- background: color-mix(in srgb, var(--accent) 22%, transparent);
140
+ background: color-mix(in srgb, var(--accent) 22%, transparent); /* ring-gap: inherit — decorative paint has no nested focus target. */
141
141
  overflow: hidden;
142
142
  /* Clean entrance: grow + fade in on state change, once. */
143
143
  transform-origin: center;
@@ -156,7 +156,7 @@
156
156
  /* On the filled primary the surface IS the accent, so shimmer in the contrast
157
157
  colour instead — otherwise the bars vanish into the button. */
158
158
  .ui-btn--primary .ui-btn__bars {
159
- background: color-mix(in srgb, var(--accent-contrast) 26%, transparent);
159
+ background: color-mix(in srgb, var(--accent-contrast) 26%, transparent); /* ring-gap: inherit — decorative paint has no nested focus target. */
160
160
  }
161
161
  .ui-btn--primary .ui-btn__bars i {
162
162
  background: linear-gradient(90deg,
@@ -7,23 +7,23 @@
7
7
  gap: 12px;
8
8
  padding: 15px 17px;
9
9
  border-radius: var(--radius-md);
10
- background: var(--surface-2);
10
+ background: var(--surface-2); --ring-gap: 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); }
19
19
 
20
- .ui-callout--info { background: var(--glow-cyan); }
20
+ .ui-callout--info { --ring-gap: inherit; background: var(--glow-cyan); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
21
21
  .ui-callout--info .ui-callout__icon { color: var(--chip-info-ink); } /* the chip inks, not the raw signals: in light the raw signal cleared the graphic bar by a tenth on its own wash and read as a smudge, and a stroke is read the way text is. The two are the same value in dark by construction, so only light moves. #206 */
22
- .ui-callout--success { background: var(--glow-green); }
22
+ .ui-callout--success { --ring-gap: inherit; background: var(--glow-green); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
23
23
  .ui-callout--success .ui-callout__icon { color: var(--chip-success-ink); }
24
- .ui-callout--warn { background: color-mix(in srgb, var(--amber) 13%, transparent); }
24
+ .ui-callout--warn { --ring-gap: inherit; background: color-mix(in srgb, var(--amber) 13%, transparent); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
25
25
  .ui-callout--warn .ui-callout__icon { color: var(--chip-warn-ink); }
26
- .ui-callout--danger { background: var(--glow-pink); }
26
+ .ui-callout--danger { --ring-gap: inherit; background: var(--glow-pink); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */ }
27
27
  .ui-callout--danger .ui-callout__icon { color: var(--chip-danger-ink); }
28
28
 
29
29
  /* ----------------------------------------------------------------------------
@@ -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.
@@ -85,9 +85,9 @@
85
85
  the ink is dark and darkens it where the ink is bright — the hovered pair is
86
86
  at or above the resting pair in both. */
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
- .ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); }
88
+ .ui-toast__action:hover { background: color-mix(in srgb, var(--bg) 55%, transparent); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
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
@@ -117,7 +117,7 @@
117
117
  /* style: soft — tinted surface. The shadow used to be its edge, so it takes the
118
118
  status-tinted hairline the outline style carries, at half the weight. #295 */
119
119
  .ui-toast--soft {
120
- background: var(--toast-glow);
120
+ background: var(--toast-glow); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
121
121
  border: 1px solid color-mix(in srgb, var(--toast-accent) 22%, transparent);
122
122
  --elev-edge: color-mix(in srgb, var(--toast-accent) 11%, transparent);
123
123
  }
@@ -130,8 +130,8 @@
130
130
  clear each other — and four of the ten status x theme combinations did not.
131
131
  Gated for all ten in stories/signal-contrast.test.js. See #149. */
132
132
  .ui-toast--solid {
133
- --toast-ink: var(--signal-solid-ink);
134
- background: var(--toast-solid);
133
+ --toast-ink: var(--signal-solid-ink); --ring-color: var(--toast-ink);
134
+ background: var(--toast-solid); --ring-gap: var(--toast-solid);
135
135
  color: var(--toast-ink);
136
136
  /* No inner line: a solid toast has no outer one either. It keeps the drop. */
137
137
  --elev-edge: transparent;
@@ -142,19 +142,19 @@
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); }
146
- .ui-toast--solid .ui-toast__icon { background: color-mix(in srgb, var(--toast-ink) 22%, transparent); color: var(--toast-ink); }
145
+ .ui-toast--solid .ui-toast__text { color: var(--toast-ink); }
146
+ .ui-toast--solid .ui-toast__icon { background: color-mix(in srgb, var(--toast-ink) 22%, transparent); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ 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
- .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); }
151
+ .ui-toast--solid .ui-toast__action:hover { background: color-mix(in srgb, var(--signal-contrast) 14%, transparent); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
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
- .ui-toast--solid .ui-toast__timer { background: color-mix(in srgb, var(--toast-ink) 55%, transparent); }
154
+ .ui-toast--solid .ui-toast__timer { background: color-mix(in srgb, var(--toast-ink) 55%, transparent); /* ring-gap: inherit — decorative or text-only surface has no nested focus target. */ }
155
155
 
156
156
  /* style: outline — elevated card with a status-tinted border */
157
- .ui-toast--outline { background: var(--bg-elevated); border: 1px solid color-mix(in srgb, var(--toast-accent) 40%, transparent); --elev-edge: color-mix(in srgb, var(--toast-accent) 20%, transparent); }
157
+ .ui-toast--outline { background: var(--bg-elevated); --ring-gap: var(--bg-elevated); border: 1px solid color-mix(in srgb, var(--toast-accent) 40%, transparent); --elev-edge: color-mix(in srgb, var(--toast-accent) 20%, transparent); }
158
158
 
159
159
  /* compact — single line, no body text */
160
160
  .ui-toast--compact { align-items: center; padding-top: 10px; padding-bottom: 10px; }
@@ -178,7 +178,7 @@
178
178
  border-radius: var(--radius-lg);
179
179
  padding: 30px;
180
180
  text-align: center;
181
- background: var(--glow-green);
181
+ background: var(--glow-green); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
182
182
  }
183
183
  .ui-success__check {
184
184
  width: 48px; height: 48px;
@@ -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); }
@@ -6,7 +6,7 @@
6
6
  /* Flat since #284 and edged in both themes since #295: the surface is the step, the
7
7
  line is what makes it legible. why: docs/specification.md#elevation */
8
8
  .ui-card {
9
- background: var(--surface);
9
+ background: var(--surface); --ring-gap: var(--surface);
10
10
  border: 1px solid var(--border);
11
11
  border-radius: var(--radius-xl);
12
12
  padding: var(--space-6) 26px;
@@ -28,11 +28,11 @@
28
28
  /* An accent-tinted card, e.g. a highlighted plan / success surface. The accent edge is
29
29
  the card's own hairline re-coloured: an inset ring over it would draw two. #295 */
30
30
  .ui-card--accent {
31
- background: color-mix(in srgb, var(--accent) 9%, var(--surface));
31
+ background: color-mix(in srgb, var(--accent) 9%, var(--surface)); --ring-gap: color-mix(in srgb, var(--accent) 9%, var(--surface));
32
32
  border-color: color-mix(in srgb, var(--accent) 22%, transparent);
33
33
  }
34
34
  .ui-card--live {
35
- background: color-mix(in srgb, var(--green) 10%, var(--surface));
35
+ background: color-mix(in srgb, var(--green) 10%, var(--surface)); --ring-gap: color-mix(in srgb, var(--green) 10%, var(--surface));
36
36
  }
37
37
 
38
38
  /* Interactive card (link / button semantics) */
@@ -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 {
@@ -89,7 +89,7 @@
89
89
  border-radius: var(--radius-md);
90
90
  display: grid;
91
91
  place-items: center;
92
- background: var(--glow-purple);
92
+ background: var(--glow-purple); /* ring-gap: inherit — translucent wash retains the opaque containing gap. */
93
93
  color: var(--accent);
94
94
  flex: none;
95
95
  }
@@ -4,7 +4,7 @@
4
4
  * ========================================================================== */
5
5
 
6
6
  .ui-snippet {
7
- background: var(--surface-2);
7
+ background: var(--surface-2); --ring-gap: var(--surface-2);
8
8
  border-radius: var(--radius-md);
9
9
  overflow: hidden;
10
10
  }
@@ -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,11 +57,11 @@
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 {
64
- background: color-mix(in srgb, var(--green) 10%, var(--surface));
64
+ background: color-mix(in srgb, var(--green) 10%, var(--surface)); --ring-gap: color-mix(in srgb, var(--green) 10%, var(--surface));
65
65
  }
66
66
  .ui-snippet--reveal .ui-snippet__bar span { color: var(--green); }
67
67
  .ui-snippet--reveal pre { color: var(--green); word-break: break-all; white-space: pre-wrap; }
@@ -70,7 +70,7 @@
70
70
  .ui-code {
71
71
  font-family: var(--font-mono);
72
72
  font-size: 0.9em;
73
- background: var(--surface-2);
73
+ background: var(--surface-2); --ring-gap: var(--surface-2);
74
74
  padding: 2px 6px;
75
75
  border-radius: var(--radius-xs);
76
76
  color: var(--text);