@codenhub/styles 0.3.0 → 0.5.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.
Files changed (55) hide show
  1. package/README.md +1 -1
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/cyber.css +1 -0
  4. package/dist/aesthetics/glass.css +1 -1
  5. package/dist/aesthetics/index.css +1 -1
  6. package/dist/aesthetics/neobrutalism.css +1 -1
  7. package/dist/aesthetics/pixel.css +1 -1
  8. package/dist/aesthetics/sketch.css +1 -0
  9. package/dist/components.css +1 -1
  10. package/dist/index.css +1 -1
  11. package/dist/native.css +1 -1
  12. package/dist/palette.css +1 -1
  13. package/dist/theme.css +1 -1
  14. package/dist/tw/aesthetics/chunky-tile.css +198 -111
  15. package/dist/tw/aesthetics/cyber.css +227 -0
  16. package/dist/tw/aesthetics/glass.css +134 -47
  17. package/dist/tw/aesthetics/index.css +2 -0
  18. package/dist/tw/aesthetics/neobrutalism.css +149 -49
  19. package/dist/tw/aesthetics/pixel.css +151 -63
  20. package/dist/tw/aesthetics/sketch.css +326 -0
  21. package/dist/tw/box.css +107 -35
  22. package/dist/tw/components/button.css +40 -19
  23. package/dist/tw/components/feedback.css +38 -15
  24. package/dist/tw/components/form.css +161 -154
  25. package/dist/tw/components/index.css +15 -3
  26. package/dist/tw/components/loader.css +34 -25
  27. package/dist/tw/components/surface.css +28 -71
  28. package/dist/tw/components/tooltip.css +20 -10
  29. package/dist/tw/corner.css +26 -0
  30. package/dist/tw/elevation.css +8 -9
  31. package/dist/tw/intent.css +17 -9
  32. package/dist/tw/native.css +45 -39
  33. package/dist/tw/palette.css +452 -446
  34. package/dist/tw/presentation.css +9 -4
  35. package/dist/tw/surface.css +62 -0
  36. package/dist/tw/theme.css +106 -75
  37. package/dist/tw/utilities/content.css +25 -16
  38. package/dist/tw/utilities/layout.css +2 -2
  39. package/docs/accessibility.md +16 -16
  40. package/docs/changelog/0.4.0.md +12 -0
  41. package/docs/changelog/0.5.0.md +38 -0
  42. package/docs/changelog/index.md +2 -0
  43. package/docs/concepts.md +5 -5
  44. package/docs/index.md +3 -3
  45. package/docs/setup.md +23 -3
  46. package/docs/usage/aesthetics.md +119 -2
  47. package/docs/usage/buttons.md +13 -13
  48. package/docs/usage/composing.md +11 -11
  49. package/docs/usage/content-and-layout.md +13 -13
  50. package/docs/usage/customizing.md +97 -44
  51. package/docs/usage/feedback.md +1 -1
  52. package/docs/usage/forms.md +11 -11
  53. package/docs/usage/theming.md +1 -1
  54. package/llms-full.txt +359 -122
  55. package/package.json +22 -2
package/dist/tw/box.css CHANGED
@@ -1,5 +1,3 @@
1
- @reference "tailwindcss";
2
-
3
1
  /* How a box is painted, in one place: intent supplies the colour, presentation
4
2
  how much of it, the aesthetic the material, and this composes the three. It
5
3
  composes here, not on a container, because a custom property resolves its
@@ -16,14 +14,15 @@
16
14
 
17
15
  `--_fill-cap` is the matching slot for a component that reinterprets what a
18
16
  presentation means for itself rather than merely bounding it -- `.card.soft`
19
- uses it to rest at 0% regardless of what `.soft` asks for. It has to be a
20
- separate term rather than a rewrite of `--ui-fill`: `presentation.css`'s
21
- `.soft` is unlayered CSS and `@utility card`'s own rule compiles into
22
- Tailwind's `utilities` layer, and an unlayered declaration always wins over
23
- a layered one for the same property, at any specificity -- so `.card.soft`
24
- writing `--ui-fill: 0%` directly was silently losing to `.soft`'s `12%`
25
- every time. `--_fill-cap` is a token `.soft` never touches, so nothing
26
- contests it.
17
+ uses it to rest at 0% regardless of what `.soft` asks for. It is a
18
+ separate term rather than a write of `--ui-fill`, for two reasons. A
19
+ component writing `--ui-fill` from `utilities` would beat a consumer's own
20
+ `[--ui-fill:...]` utility, which the layer map reserves for state
21
+ (docs/internal/model.md#cascade-layers); a private competes with nothing a
22
+ consumer writes. And `--ui-fill` cascades by design, so a neutral panel
23
+ writing `0%` would turn every badge and button inside it ghost, where a
24
+ cap bounds the component and stops there (below). A state that means the
25
+ fill it names -- `:checked` -- writes `--ui-fill` itself.
27
26
 
28
27
  Restated here at `100%` -- a no-op -- for the same reason `--_d-ground`
29
28
  is restated `transparent` below rather than left to its `var()` fallback:
@@ -68,14 +67,20 @@
68
67
  a capped intent -- rather than being bent so neutral's contrast is the ink,
69
68
  which rendered a checked neutral checkbox black-on-black. */
70
69
  --_fg: color-mix(in oklab, var(--intent-contrast) var(--_on-fill), var(--intent-strong));
71
- /* P3: the line blends toward the fill by the fill amount, so a filled box has
72
- a seamless edge. Blend first, amount after (`--_edge-amount` below) -- the
73
- other order lands `.edgeless` on the fill colour, which the border box then
74
- paints as a second translucent coat: a ring around an element that asked for
75
- no edge. The blend runs toward `--_bg`, not raw `--intent-color`, so under
76
- an aesthetic that thins the fill a translucent box does not keep an opaque
77
- ring. */
78
- --_line: color-mix(in oklab, var(--_bg) var(--ui-fill, var(--_d-fill)), var(--intent-border));
70
+ /* P3: the line fades out as the fill fills in, so a filled box has a seamless
71
+ edge. It fades toward `transparent`, not toward the plate: the plate already
72
+ runs under the border (nothing here clips the background to the padding
73
+ box), so a line blended toward `--_bg` painted the plate a second time over
74
+ itself. On an opaque plate that is invisible; on a translucent one -- a
75
+ neutral fill, capped at 20%, or any fill `--ui-bg-alpha` thins -- it drew a
76
+ ring around the box, 1.54:1 against its own plate in light. Faded toward
77
+ nothing, the border shows the one coat beneath it.
78
+
79
+ Blend first, amount after (`--_edge-amount` below) -- the other order lands
80
+ `.edgeless` on the fill colour, which the border box then paints as a second
81
+ coat: a ring around an element that asked for no edge. See
82
+ docs/internal/model.md#presentation. */
83
+ --_line: color-mix(in oklab, transparent var(--ui-fill, var(--_d-fill)), var(--intent-border));
79
84
  /* How much line there is, as an input rather than a result. A component that
80
85
  must keep its line whatever the edge class says raises `--_edge-floor`
81
86
  instead of rewriting `--_edge`, and the composition above still runs.
@@ -95,14 +100,30 @@
95
100
  draws. Zero lengths are written `0px` because `calc(0 * 1)` yields a number
96
101
  and a shadow position needs a length. */
97
102
  --_elevation: var(--ui-elevation, var(--_d-elevation, 1));
98
- --_sx: calc(var(--ui-shadow-x, var(--_d-shadow-x, 0px)) * var(--_elevation));
103
+
104
+ /* The size step, a unitless multiplier the size and padding modifiers publish
105
+ as two privates -- `.sm`/`.p-sm` 0.75, `.p-xs` 0.5, `.lg`/`.p-lg` 1.25 --
106
+ so that when both sit on one element the tighter wins: `.lg.p-xs` is
107
+ small. An unset step takes the other's value, so a lone `.lg` still grows.
108
+ Restated `initial` here, before any modifier, because a custom property
109
+ inherits and a `.card.p-xs` would otherwise shrink every button inside it;
110
+ the modifier rules outrank this one on specificity. `--ui-scale` is the
111
+ public override, undeclared by the package. See docs/internal/model.md#material-tokens. */
112
+ --_scale-size: initial;
113
+ --_scale-pad: initial;
114
+ --_scale: var(--ui-scale, min(var(--_scale-size, var(--_scale-pad, 1)), var(--_scale-pad, var(--_scale-size, 1))));
115
+ /* Depth follows the step down and not up: a 4px slab under a 20px button
116
+ swallows it, while a press that travels the aesthetic's whole depth has to
117
+ land flat on a `.lg` button too. */
118
+ --_depth: calc(var(--_elevation) * min(1, var(--_scale)));
119
+ --_sx: calc(var(--ui-shadow-x, var(--_d-shadow-x, 0px)) * var(--_depth));
99
120
  /* Three slots, most specific first: the aesthetic, then a component that draws
100
121
  its own depth, then the plain step `.raised`/`.floating` supply on a page
101
122
  with no aesthetic. The order lets a raised card under `.neobrutalism` keep
102
123
  the slab. Nothing declares the last slot until something asks for depth, so
103
124
  a plain unmodified page has no shadow anywhere. */
104
- --_sy: calc(var(--ui-shadow-y, var(--_d-shadow-y, var(--elevation-y, 0px))) * var(--_elevation));
105
- --_sb: calc(var(--ui-shadow-blur, var(--_d-shadow-blur, var(--elevation-blur, 0px))) * var(--_elevation));
125
+ --_sy: calc(var(--ui-shadow-y, var(--_d-shadow-y, var(--elevation-y, 0px))) * var(--_depth));
126
+ --_sb: calc(var(--ui-shadow-blur, var(--_d-shadow-blur, var(--elevation-blur, 0px))) * var(--_depth));
106
127
  /* Spread is deliberately not scaled. An aesthetic that draws its edge as an
107
128
  inset ring spends spread on it, and scaling that by elevation would erase
108
129
  the edge of every component the registry rests at zero. Elevation is depth:
@@ -130,13 +151,23 @@
130
151
  --_edge-shadow-color: var(--ui-shadow-edge) var(--_edge);
131
152
  --_shadow-color: var(--_edge-shadow-color, var(--_depth-color));
132
153
  --_shadow-parts: var(--ui-shadow-inset,) var(--_sx) var(--_sy) var(--_sb) var(--_ss) var(--_shadow-color);
154
+ /* A shadow that is not depth: light in the element's own intent colour, with
155
+ no offset. Outside the depth layer, and not scaled by elevation, so `.flat`
156
+ leaves it alone and a field resting at zero still takes it. Present only
157
+ when `--ui-halo-ink` is declared: undeclared, the whole layer, leading
158
+ comma included, is invalid at computed-value time and `var()` drops it, so
159
+ no empty layer sits in every box's shadow. */
160
+ --_halo-layer:
161
+ ,
162
+ 0 0 var(--ui-halo-blur, 0px) var(--ui-halo-spread, 0px)
163
+ color-mix(in oklab, var(--intent-border) var(--ui-halo-ink), transparent);
133
164
  /* Indirection so a component can substitute a complete value of its own
134
165
  without restating the parts -- `surface` does, for the multi-layer case.
135
166
  There is deliberately no `--ui-shadow` wrapper: a complete value has nothing
136
167
  for the elevation multiplier to reach, so `.flat` would silently stop working
137
168
  on anything that set one. `--ui-surface-shadow` is that escape hatch narrowed
138
169
  to the single case that needs it. */
139
- --_shadow: var(--_shadow-parts);
170
+ --_shadow: var(--_shadow-parts) var(--_halo-layer,);
140
171
  --_focus-stack: var(--_shadow), inset 0 0 0 var(--ui-focus-inset) var(--focus-ring);
141
172
 
142
173
  /* Every box sizes itself as `min-height`/`min-width: var(--control-height)`
@@ -151,17 +182,44 @@
151
182
  box-sizing: border-box;
152
183
  background-color: var(--_bg);
153
184
  color: var(--_fg);
154
- border: var(--_width) solid var(--_edge);
155
- border-radius: var(--ui-radius, var(--radius-control));
185
+ /* The line's style is material: `solid`, `dashed`, `dotted`, or `double`,
186
+ which needs a width of 3px or more to draw two lines. It keeps the line's
187
+ colour, so its contrast, whatever it draws. */
188
+ border: var(--_width) var(--ui-line-style, solid) var(--_edge);
189
+ /* The corner is the aesthetic's length at scale 1 times the size step, so a
190
+ small button takes a small corner and a cut stays a true 45 degrees. Each
191
+ corner then multiplies by its own `--ui-corner-tl`/`-tr`/`-br`/`-bl`, 0 or
192
+ 1, which is how `.cut-diagonal` and a bevelling aesthetic's diagonal draw
193
+ two corners and leave two square. An explicit `--ui-radius` still wins,
194
+ taken as written. */
195
+ --_corner: calc(var(--ui-corner, var(--radius-control)) * var(--_scale));
196
+ border-radius: var(
197
+ --ui-radius,
198
+ calc(var(--_corner) * var(--ui-corner-tl, 1)) calc(var(--_corner) * var(--ui-corner-tr, 1))
199
+ calc(var(--_corner) * var(--ui-corner-br, 1)) calc(var(--_corner) * var(--ui-corner-bl, 1))
200
+ );
201
+ /* What the radius draws: a round arc by default, a straight cut under an
202
+ aesthetic that bevels (`.cyber`). `round` is the property's initial value,
203
+ so with no aesthetic in scope this is a no-op, and an engine without
204
+ `corner-shape` drops the line at parse time. A component that overrides the
205
+ radius after `@apply box` keeps this, so it bevels by the same amount it
206
+ rounds. See model.md#material-tokens. */
207
+ corner-shape: var(--ui-corner-shape, round);
156
208
  box-shadow: var(--_shadow);
157
209
  clip-path: var(--ui-clip, none);
210
+ /* The press travel `box-active` writes; `none` at rest, because any other
211
+ `translate` makes the box a stacking context and a containing block.
212
+ Restated here so a pressed card does not carry the boxes inside it. */
213
+ --_press: none;
214
+ translate: var(--_press);
158
215
  transition:
159
216
  background-color var(--motion-duration-normal) var(--motion-ease),
160
217
  border-color var(--motion-duration-normal) var(--motion-ease),
161
218
  box-shadow var(--motion-duration-normal) var(--motion-ease),
162
219
  color var(--motion-duration-normal) var(--motion-ease),
163
220
  opacity var(--motion-duration-normal) var(--motion-ease),
164
- transform var(--motion-duration-normal) var(--motion-ease);
221
+ transform var(--motion-duration-normal) var(--motion-ease),
222
+ translate var(--motion-duration-normal) var(--motion-ease);
165
223
 
166
224
  /* An aesthetic that clips cannot show the global focus outline, so it declares
167
225
  `--ui-focus-inset` and gets an inset layer instead. With no such aesthetic
@@ -197,14 +255,13 @@
197
255
  @utility box-hover {
198
256
  &:not(:disabled, .disabled, [aria-disabled="true"], [data-disabled]):hover {
199
257
  --_bg: color-mix(in oklab, var(--intent-hover) var(--_hover-fill), var(--_d-ground, transparent));
200
- /* P3 again, against the hover tone. Only the ink end has to move: the fill
201
- end is `--_bg`, which the line above already redefined, so the resting
202
- formula follows the hover on its own. Without this the edge blends toward
203
- the resting ink -- a ring of the colour the box just left. */
204
- --_line: color-mix(in oklab, var(--_bg) var(--ui-fill, var(--_d-fill)), var(--intent-hover));
205
- --_sx: calc(var(--ui-hover-shadow-x, var(--ui-shadow-x, var(--_d-shadow-x, 0px))) * var(--_elevation));
258
+ /* P3 again, against the hover tone: the line still fades as the fill fills
259
+ in, but toward the hover ink. Without this the edge keeps the resting ink
260
+ -- a ring of the colour the box just left. */
261
+ --_line: color-mix(in oklab, transparent var(--ui-fill, var(--_d-fill)), var(--intent-hover));
262
+ --_sx: calc(var(--ui-hover-shadow-x, var(--ui-shadow-x, var(--_d-shadow-x, 0px))) * var(--_depth));
206
263
  --_sy: calc(
207
- var(--ui-hover-shadow-y, var(--ui-shadow-y, var(--_d-shadow-y, var(--elevation-y, 0px)))) * var(--_elevation)
264
+ var(--ui-hover-shadow-y, var(--ui-shadow-y, var(--_d-shadow-y, var(--elevation-y, 0px)))) * var(--_depth)
208
265
  );
209
266
 
210
267
  transform: var(--ui-hover-transform, none);
@@ -217,11 +274,26 @@
217
274
  aesthetic's -- neobrutalism travels into its offset shadow, chunky-tile drops
218
275
  onto its bar -- and with no aesthetic it is the base `scale(0.97)` from
219
276
  `theme.css`, dropped under reduced motion. The `--ui-active-shadow-*` slots
220
- stay no-ops until an aesthetic sets them. */
277
+ stay no-ops until an aesthetic sets them.
278
+
279
+ A press that travels into the element's own depth is a translation, given as
280
+ `--ui-active-translate-x`/`-y` and scaled by `--_depth` like the shadow it
281
+ travels into, so a `.p-xs` button that sits on half the slab moves half as
282
+ far, a `.floating` one twice as far, and a `.flat` one, with no slab to land
283
+ on, not at all. It is the independent `translate` property, so it composes
284
+ with `--ui-active-transform` rather than replacing it.
285
+
286
+ The press writes only `--_press`, and `box` declares `translate` from it.
287
+ A `translate` declaration here sorts this rule ahead of `box-hover`'s -- the
288
+ property ranks before `transform` in Tailwind's order -- and the hovered
289
+ `transform: none` then beats every press. */
221
290
  @utility box-active {
222
291
  &:active:not(:disabled, .disabled, [aria-disabled="true"], [data-disabled]) {
223
- --_sx: calc(var(--ui-active-shadow-x, var(--ui-shadow-x, var(--_d-shadow-x, 0px))) * var(--_elevation));
224
- --_sy: calc(var(--ui-active-shadow-y, var(--ui-shadow-y, var(--_d-shadow-y, 0px))) * var(--_elevation));
292
+ --_sx: calc(var(--ui-active-shadow-x, var(--ui-shadow-x, var(--_d-shadow-x, 0px))) * var(--_depth));
293
+ --_sy: calc(var(--ui-active-shadow-y, var(--ui-shadow-y, var(--_d-shadow-y, 0px))) * var(--_depth));
294
+
295
+ --_press: calc(var(--ui-active-translate-x, 0px) * var(--_depth))
296
+ calc(var(--ui-active-translate-y, 0px) * var(--_depth));
225
297
 
226
298
  transform: var(--ui-active-transform, none);
227
299
  }
@@ -14,16 +14,34 @@
14
14
  --_d-border: 0%;
15
15
  --_d-elevation: 1;
16
16
 
17
- @apply box box-hover box-active relative inline-flex w-fit cursor-pointer items-center justify-center gap-2 px-4 py-2 font-semibold whitespace-nowrap no-underline select-none;
17
+ @apply box box-hover box-active relative inline-flex w-fit cursor-pointer items-center justify-center gap-2 px-4 py-2 whitespace-nowrap no-underline select-none;
18
18
 
19
19
  min-height: var(--control-height);
20
+ /* The label is material, shared with `.badge`: weight, tracking, case, and a
21
+ text shadow, set by an aesthetic as tokens (`.chunky-tile`'s heavier,
22
+ tracked label), and a consumer's own `font-*`, `tracking-*`, `uppercase`, or
23
+ text-shadow utility still beats the aesthetic. Tracking has no fallback on
24
+ purpose: undefined, the declaration is invalid at computed-value time and
25
+ `letter-spacing` inherits, which is what it did before the slot existed.
26
+ Case and shadow fall back to `none`, which is what a button already does:
27
+ the user agent stops both inheriting into one. The shadow is written
28
+ without a colour, so it takes the label's own and cannot lower its
29
+ contrast. See docs/internal/model.md#cascade-layers. */
30
+ font-weight: var(--ui-label-weight, var(--font-weight-semibold));
31
+ letter-spacing: var(--ui-label-tracking);
32
+ text-transform: var(--ui-label-case, none);
33
+ text-shadow: var(--ui-label-shadow, none);
20
34
 
21
35
  &.sm {
22
36
  @apply min-h-8 px-3 py-1 text-sm;
37
+
38
+ --_scale-size: 0.75;
23
39
  }
24
40
 
25
41
  &.lg {
26
42
  @apply min-h-12 px-5 py-2.5 text-[1.0625rem];
43
+
44
+ --_scale-size: 1.25;
27
45
  }
28
46
 
29
47
  &.icon {
@@ -32,27 +50,25 @@
32
50
  min-width: var(--control-height);
33
51
  }
34
52
 
35
- /* `.p-xs`/`.p-sm`/`.p-lg` are the canonical names, matching the `sm`/`md`/`lg`
36
- vocabulary the elevation modifier uses; `.dense`/`.compact`/`.spacious` are
37
- first-class aliases, not a deprecation shim -- the same pattern
38
- `.destructive`/`.danger`/`.error` uses on the intent axis. Bare `sm`/`lg`
39
- are already the size modifier's class names, so `p-` prefixes here to
40
- avoid colliding with a button's own size. `.p-xs` is the tightest step,
41
- below `.compact`: a chip-sized button in a toolbar or a table cell.
53
+ /* Bare `sm`/`lg` are already the size modifier's class names, so `p-`
54
+ prefixes here to avoid colliding with a button's own size. `.p-xs` is the
55
+ tightest step, below `.p-sm`: a chip-sized button in a toolbar or a table
56
+ cell.
42
57
 
43
58
  `.icon` drops both floors -- `min-width` from its own base rule below and
44
59
  `min-height` from `--control-height` -- at every padding tier, labeled
45
- buttons keep the height floor everywhere but `.p-xs`/`.dense`. `.icon`
60
+ buttons keep the height floor everywhere but `.p-xs`. `.icon`
46
61
  already asks to be square: `aspect-square` ties its width to whatever
47
62
  height it lands on, so a height floor that survives keeps the button
48
63
  bigger than its own glyph regardless of how tight the padding asked to be
49
- -- the one combination `.p-xs`/`.dense` used to get right and the other two
50
- tiers did not, leaving a `.icon.compact` floating in a box far bigger than
51
- it needed and a `.icon.spacious` doing the same one step further out. */
52
- &.p-xs,
53
- &.dense {
64
+ -- the one combination `.p-xs` used to get right and the other two tiers
65
+ did not, leaving a `.icon.p-sm` floating in a box far bigger than it
66
+ needed and a `.icon.p-lg` doing the same one step further out. */
67
+ &.p-xs {
54
68
  @apply px-2 py-0.5;
55
69
 
70
+ --_scale-pad: 0.5;
71
+
56
72
  /* The one padding step that also drops the size floor for a labeled
57
73
  button. At this tier the button is a chip in a toolbar or a table
58
74
  cell, where `--control-height` is taller than the row it sits in;
@@ -67,10 +83,11 @@
67
83
  }
68
84
  }
69
85
 
70
- &.p-sm,
71
- &.compact {
86
+ &.p-sm {
72
87
  @apply px-2.5 py-1;
73
88
 
89
+ --_scale-pad: 0.75;
90
+
74
91
  &.icon {
75
92
  @apply p-1;
76
93
 
@@ -79,10 +96,11 @@
79
96
  }
80
97
  }
81
98
 
82
- &.p-lg,
83
- &.spacious {
99
+ &.p-lg {
84
100
  @apply px-6 py-3;
85
101
 
102
+ --_scale-pad: 1.25;
103
+
86
104
  &.icon {
87
105
  @apply p-3;
88
106
 
@@ -91,7 +109,10 @@
91
109
  }
92
110
  }
93
111
 
112
+ /* A pill normally takes the round material ahead of the box radius. Sketch
113
+ gives this token the button's own box outline; a bevel still cuts the full
114
+ radius to points under cyber. */
94
115
  &.pill {
95
- @apply rounded-full;
116
+ border-radius: var(--ui-radius-pill, calc(infinity * 1px));
96
117
  }
97
118
  }
@@ -1,7 +1,7 @@
1
1
  @reference "tailwindcss";
2
2
  @import "../theme.css";
3
3
  @reference "../box.css";
4
- @reference "./surface.css";
4
+ @reference "../surface.css";
5
5
 
6
6
  @keyframes anim-skeleton {
7
7
  0% {
@@ -49,16 +49,17 @@
49
49
  plus `.solid`/`.ghost` so an explicit presentation on the element is
50
50
  never clamped to a cap it did not ask for.
51
51
 
52
- Grounds on `--color-foreground` through the public `--ui-surface-ground`
53
- escape hatch, exactly like `.panel` and a resting `.card.soft` -- an
52
+ Grounds on `--color-foreground` through `--_d-surface-ground`, exactly
53
+ like `.panel` and a resting `.card.soft`, so glass's ground and a
54
+ consumer's `--ui-surface-ground` still win over it -- an
54
55
  alert has no hover state to reveal a tint into either, so this is
55
56
  permanent rather than revealed. Unlike a neutral panel or badge, an
56
57
  alert cannot lose its visual identity from this: `--_d-border: 100%`
57
58
  above holds regardless of fill, so a neutral alert still reads as a
58
59
  framed box, just a quiet one instead of a grey slab. */
59
- &:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid, .ghost) {
60
+ &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid, .ghost) {
60
61
  --_fill-cap: 0%;
61
- --ui-surface-ground: var(--color-foreground);
62
+ --_d-surface-ground: var(--color-foreground);
62
63
  }
63
64
  }
64
65
 
@@ -91,11 +92,20 @@
91
92
  --_d-border: 0%;
92
93
  --_d-elevation: 0;
93
94
 
94
- @apply box inline-flex min-h-6 w-fit items-center gap-1 px-2.5 text-xs font-bold;
95
+ @apply box inline-flex min-h-6 w-fit items-center gap-1 px-2.5 text-xs;
96
+
97
+ /* The label material `.btn` reads. Unset, tracking, case, and shadow inherit,
98
+ as a badge's text always has. */
99
+ font-weight: var(--ui-label-weight, var(--font-weight-bold));
100
+ letter-spacing: var(--ui-label-tracking);
101
+ text-transform: var(--ui-label-case);
102
+ text-shadow: var(--ui-label-shadow);
95
103
 
96
104
  /* Pill by default, but an aesthetic that declares no radius has to mean it, so
97
- the round corner is this component's fallback rather than a hardcoded value. */
98
- border-radius: var(--ui-radius, calc(infinity * 1px));
105
+ the round corner is this component's fallback rather than a hardcoded value.
106
+ An aesthetic that keeps pills distinct from its control corner says so
107
+ through `--ui-radius-pill` first. */
108
+ border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
99
109
  clip-path: var(--ui-clip-tight, var(--ui-clip, none));
100
110
  }
101
111
 
@@ -114,7 +124,15 @@
114
124
  animation: anim-skeleton calc(var(--motion-duration-slow) * 4) linear infinite;
115
125
  background: linear-gradient(90deg, var(--skeleton-base), var(--skeleton-sheen), var(--skeleton-base));
116
126
  background-size: 200% 100%;
117
- border-radius: var(--ui-radius, var(--radius-control));
127
+ /* Not a box, so it draws the control corner and its pattern itself, the way
128
+ `box` does at scale 1, and reads the corner shape beside it. */
129
+ --_corner: var(--ui-corner, var(--radius-control));
130
+ border-radius: var(
131
+ --ui-radius,
132
+ calc(var(--_corner) * var(--ui-corner-tl, 1)) calc(var(--_corner) * var(--ui-corner-tr, 1))
133
+ calc(var(--_corner) * var(--ui-corner-br, 1)) calc(var(--_corner) * var(--ui-corner-bl, 1))
134
+ );
135
+ corner-shape: var(--ui-corner-shape, round);
118
136
  }
119
137
 
120
138
  /* The track is a box, the fill is not: `box.css`'s formula composes the frame,
@@ -122,8 +140,8 @@
122
140
  same contract as `.loader`/`.skeleton`. `.progress` cannot `@apply box`
123
141
  (the fill is a `::after`, not the element's own background), so it hand-
124
142
  reimplements the formula the way `.quote` does -- see
125
- docs/internal/progress-presentation-axes.md for the decision and why the
126
- other three indicators do not get the same treatment. */
143
+ docs/internal/model.md#components-that-do-not-take-the-whole-of-box for the
144
+ decision and why the other three indicators do not get the same treatment. */
127
145
  @utility progress {
128
146
  --_d-fill: 12%;
129
147
  --_d-fg-on-fill: 0%;
@@ -158,16 +176,21 @@
158
176
 
159
177
  @apply relative h-2.5 w-full overflow-hidden;
160
178
  background-color: var(--progress-surface, var(--_progress-surface));
161
- border: var(--ui-border-width, var(--border-width)) solid var(--progress-edge, var(--_progress-edge));
179
+ border: var(--ui-border-width, var(--border-width)) var(--ui-line-style, solid)
180
+ var(--progress-edge, var(--_progress-edge));
162
181
  /* Round by default; an aesthetic that declares no radius squares the track and
163
- its fill together. */
164
- border-radius: var(--ui-radius, calc(infinity * 1px));
182
+ its fill together, and a pill corner reaches both first. */
183
+ border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
184
+ /* Not a box either, and the fill below restates it, so a bevelled track
185
+ carries a bevelled fill rather than a pill inside a cut frame. */
186
+ corner-shape: var(--ui-corner-shape, round);
165
187
 
166
188
  &::after {
167
189
  content: "";
168
190
  @apply absolute start-0 top-0 bottom-0 block h-full transition-[width] duration-(--motion-duration-slow) ease-(--motion-ease);
169
191
 
170
- border-radius: var(--ui-radius, calc(infinity * 1px));
192
+ border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
193
+ corner-shape: var(--ui-corner-shape, round);
171
194
  background-color: var(--progress-color, var(--intent-color));
172
195
  width: var(--progress-value, 0%);
173
196
  z-index: 2;