@codenhub/styles 0.5.0 → 0.6.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 (59) hide show
  1. package/README.md +7 -7
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/cyber.css +1 -1
  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 -1
  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 +39 -39
  15. package/dist/tw/aesthetics/cyber.css +16 -11
  16. package/dist/tw/aesthetics/glass.css +235 -10
  17. package/dist/tw/aesthetics/index.css +2 -1
  18. package/dist/tw/aesthetics/neobrutalism.css +23 -11
  19. package/dist/tw/aesthetics/pixel.css +18 -8
  20. package/dist/tw/aesthetics/sketch.css +14 -7
  21. package/dist/tw/box.css +49 -27
  22. package/dist/tw/components/button.css +35 -14
  23. package/dist/tw/components/feedback.css +59 -56
  24. package/dist/tw/components/form.css +200 -126
  25. package/dist/tw/components/index.css +3 -7
  26. package/dist/tw/components/loader.css +4 -5
  27. package/dist/tw/components/surface.css +71 -68
  28. package/dist/tw/components/tooltip.css +75 -41
  29. package/dist/tw/elevation.css +14 -22
  30. package/dist/tw/index.css +15 -19
  31. package/dist/tw/intent.css +31 -24
  32. package/dist/tw/native.css +15 -5
  33. package/dist/tw/palette.css +8 -5
  34. package/dist/tw/presentation.css +16 -30
  35. package/dist/tw/reset.css +21 -28
  36. package/dist/tw/surface.css +11 -12
  37. package/dist/tw/theme.css +58 -86
  38. package/dist/tw/utilities/content.css +268 -199
  39. package/dist/tw/utilities/layout.css +15 -25
  40. package/docs/accessibility.md +50 -33
  41. package/docs/changelog/0.6.0.md +52 -0
  42. package/docs/changelog/index.md +1 -0
  43. package/docs/concepts.md +6 -6
  44. package/docs/index.md +9 -0
  45. package/docs/integrating/nextjs.md +1 -1
  46. package/docs/integrating/tailwind.md +14 -6
  47. package/docs/setup.md +6 -6
  48. package/docs/usage/aesthetics.md +35 -8
  49. package/docs/usage/buttons.md +1 -1
  50. package/docs/usage/composing.md +1 -1
  51. package/docs/usage/content-and-layout.md +12 -11
  52. package/docs/usage/customizing.md +8 -9
  53. package/docs/usage/feedback.md +5 -3
  54. package/docs/usage/forms.md +51 -35
  55. package/docs/usage/theming.md +39 -40
  56. package/docs/usage/tooltips.md +37 -17
  57. package/llms-full.txt +331 -183
  58. package/llms.txt +6 -0
  59. package/package.json +5 -5
@@ -1,12 +1,11 @@
1
1
  /* Glass: translucent surfaces over a blurred backdrop with a hairline highlight
2
- edge. It needs something behind it to blur -- on a flat page background it is a
3
- translucent panel and nothing more.
2
+ edge. It needs something behind it to blur -- on a flat page background it is
3
+ a translucent panel and nothing more.
4
4
 
5
- Tier 1 only. The scope that used to take a hand-written selector list is now a
6
- token: only `surface` resolves `--ui-backdrop`, so blur reaches cards,
7
- panels, alerts and tooltips while controls stay solid. An active blur costs a
8
- compositing layer apiece, and one under every control of a dense cluster reads
9
- as noise. */
5
+ Tier 1 only: the scope is a token. Only `surface` resolves `--ui-backdrop`,
6
+ so blur reaches cards, panels, alerts and tooltips while controls stay solid.
7
+ An active blur costs a compositing layer apiece, and one under every control
8
+ of a dense cluster reads as noise. */
10
9
 
11
10
  /* The layer map, stated before anything uses a layer -- see `theme.css`. The
12
11
  aesthetic's token class sits in `components`, below every utility, so a
@@ -40,8 +39,19 @@
40
39
  on a field's 60% resting line, in light. See
41
40
  docs/internal/model.md#presentation. */
42
41
  --ui-control-ink: light-dark(rgb(0 0 0 / 0.55), rgb(255 255 255 / 0.55));
42
+ /* Nor is a line drawn on a plate: the hairline vanishes on a table's light
43
+ plate, and its default, `.ruled`, and `.ruleless` forms would render as
44
+ one. A table's frame and rules take the theme's ink at a fifth,
45
+ translucent for the same reason the control line is. */
46
+ --ui-rule-ink: light-dark(rgb(0 0 0 / 0.2), rgb(255 255 255 / 0.2));
43
47
 
44
- --ui-bg-alpha: 0.8;
48
+ /* The glass is the ground, not the fill. Thinning fills with
49
+ `--ui-bg-alpha` would make how much colour a presentation asked for
50
+ depend on the aesthetic, which is presentation's question (R1): a
51
+ `.solid` button, a checked toggle, and a `.solid` card stay opaque.
52
+ Translucency lives in `--ui-surface-ground` below, the plate a partial
53
+ fill is painted over. */
54
+ --ui-bg-alpha: 1;
45
55
  --ui-backdrop: blur(14px) saturate(1.6);
46
56
  --ui-surface-image: initial;
47
57
 
@@ -54,6 +64,15 @@
54
64
  --ui-shadow-y: 0px;
55
65
  --ui-shadow-blur: 0px;
56
66
  --ui-shadow-spread: 0px;
67
+ --ui-shadow-inset: initial;
68
+ --ui-shadow-ink: initial;
69
+ --ui-active-shadow-x: initial;
70
+ --ui-active-shadow-y: initial;
71
+ /* The theme's depth colour, restated: the surface shadow below resolves it
72
+ here, on the region, so inside chunky tile it inherited opaque black and
73
+ cast a black slab under every card, and inside cyber it inherited
74
+ transparent and cast none. */
75
+ --elevation-color: light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
57
76
  /* No halo, cleared so a region nested inside another
58
77
  aesthetic does not inherit one. */
59
78
  --ui-halo-blur: initial;
@@ -88,8 +107,14 @@
88
107
  /* Nothing here draws its edge as a ring, and an aesthetic nested inside pixel
89
108
  has to say so rather than inherit it. */
90
109
  --ui-shadow-edge: initial;
110
+ /* Nothing clips, so the global focus outline shows; pixel's inset focus
111
+ layer is cleared rather than inherited. */
112
+ --ui-focus-inset: initial;
91
113
  --ui-hover-transform: none;
92
- --ui-active-transform: none;
114
+ /* The base press, stated because every token here is: the depth in this
115
+ aesthetic is the blur, so a control has no slab to press into, and a pane
116
+ that gives slightly under the pointer is the one answer a press gets. */
117
+ --ui-active-transform: scale(0.97);
93
118
  --ui-active-translate-x: initial;
94
119
  --ui-active-translate-y: initial;
95
120
 
@@ -105,11 +130,155 @@
105
130
  surface rather than a softer blur. */
106
131
  @media (prefers-reduced-transparency: reduce) {
107
132
  .glass {
108
- --ui-bg-alpha: 1;
109
133
  --ui-backdrop: none;
110
134
  --ui-surface-ground: var(--color-background);
111
135
  }
112
136
  }
137
+
138
+ @media (prefers-reduced-motion: reduce) {
139
+ .glass {
140
+ --ui-active-transform: none;
141
+ }
142
+ }
143
+
144
+ /* Liquid glass: a modifier of glass, the way `.sketch-rounded` is of sketch.
145
+ Continuous corners, a bright rim where light catches the pane, a lighter
146
+ blur, and -- in engines that render an SVG filter in `backdrop-filter`,
147
+ Chromium today -- a lens that bends the backdrop at the edges. On the region
148
+ beside `.glass`, or on one surface inside a glass region.
149
+
150
+ Only the corners apply on their own. The ink, ground, and blur are glass
151
+ material, so they are set below only where `.glass` is in scope: a lone
152
+ `.glass-liquid` would otherwise make every surface translucent with nothing
153
+ to blur and every field's line a near-invisible hairline.
154
+
155
+ The corner knobs are glass's own, with larger defaults: a continuous corner
156
+ reads smaller than a round one of the same radius. Where `corner-shape` is
157
+ not drawn the corners stay round. */
158
+ .glass-liquid {
159
+ --ui-corner: var(--glass-radius, 1rem);
160
+ --ui-corner-surface: var(--glass-radius-surface, 1.5rem);
161
+ --ui-corner-shape: squircle;
162
+
163
+ --_glass-liquid-blur: blur(2px) saturate(1.8);
164
+
165
+ /* The lens is its own layer rather than a second function in the blur's
166
+ list: Firefox and WebKit accept `url()` there and draw nothing for it, and
167
+ a filter list with an unusable reference is dropped whole -- the blur with
168
+ it. Kept apart, an engine that ignores the lens still blurs.
169
+
170
+ The displacement map is built inside the filter from the box itself:
171
+ erode a flood of the filter region by 8px, blur it into a ramp, and take
172
+ the ramp's slope across x and y as the two displacement channels. Every
173
+ length is absolute, so the band is the same width on a tooltip and on a
174
+ page-wide card; no length is a percentage of the box. The blur is wider
175
+ than the erosion so the ramp's corners round off -- otherwise the square
176
+ core shows through as loops at every corner. A filter cannot read a custom
177
+ property, so the band and the strength are fixed, not knobs. */
178
+ --_glass-liquid-lens: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg'><filter id='lens' x='0' y='0' width='100%' height='100%' color-interpolation-filters='sRGB'><feFlood flood-color='white'/><feMorphology operator='erode' radius='8'/><feGaussianBlur stdDeviation='10' result='ramp'/><feOffset in='ramp' dx='-3' result='l'/><feOffset in='ramp' dx='3' result='r'/><feComposite in='l' in2='r' operator='arithmetic' k2='2' k3='-2' k4='0.5' result='dx'/><feOffset in='ramp' dy='-3' result='u'/><feOffset in='ramp' dy='3' result='d'/><feComposite in='u' in2='d' operator='arithmetic' k2='2' k3='-2' k4='0.5' result='dy'/><feColorMatrix in='dx' values='0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1' result='x'/><feColorMatrix in='dy' values='0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1'/><feComposite in='x' operator='arithmetic' k2='1' k3='1'/><feDisplacementMap in='SourceGraphic' scale='110' xChannelSelector='R' yChannelSelector='G'/></filter></svg>#lens");
179
+
180
+ /* The rim: light caught on the top-left edge and, fainter, the bottom-right,
181
+ over a soft inner glow. Drawn on the lens layer, the topmost, because a
182
+ rim drawn on the surface itself would sit under the layers and blur. */
183
+ --_glass-liquid-rim:
184
+ inset 1px 1px 0 light-dark(rgb(255 255 255 / 0.75), rgb(255 255 255 / 0.35)),
185
+ inset -1px -1px 0 light-dark(rgb(255 255 255 / 0.35), rgb(255 255 255 / 0.12)),
186
+ inset 0 0 18px light-dark(rgb(255 255 255 / 0.22), rgb(255 255 255 / 0.05));
187
+ --_glass-liquid-sheen: linear-gradient(
188
+ 160deg,
189
+ light-dark(rgb(255 255 255 / 0.22), rgb(255 255 255 / 0.06)),
190
+ transparent 45%
191
+ );
192
+ }
193
+
194
+ /* The glass material the modifier changes, where glass is in scope: on the
195
+ same element, or on a surface inside a glass region. The blur moves off the
196
+ surface onto a layer behind its content (below): a surface with a backdrop
197
+ filter of its own is a backdrop root, and a layer inside one sees only the
198
+ surface, never the page behind it. */
199
+ .glass.glass-liquid,
200
+ .glass .glass-liquid {
201
+ --ui-ink: light-dark(rgb(255 255 255 / 0.45), rgb(255 255 255 / 0.16));
202
+ --ui-surface-ground: color-mix(in srgb, var(--color-background) 30%, transparent);
203
+ --ui-surface-image: initial;
204
+ --ui-backdrop: none;
205
+ }
206
+
207
+ @media (prefers-reduced-transparency: reduce) {
208
+ .glass.glass-liquid,
209
+ .glass .glass-liquid {
210
+ --ui-surface-ground: var(--color-background);
211
+ }
212
+
213
+ .glass-liquid {
214
+ --_glass-liquid-blur: none;
215
+ --_glass-liquid-lens: none;
216
+ }
217
+ }
218
+
219
+ /* The layers: a treatment no token reaches, so a selector list glass owns and
220
+ the registry records. Two pseudo-elements behind a surface's content, the
221
+ blur beneath and the lens with the rim and sheen above it. `isolation`
222
+ keeps them above the surface's own plate, which they then blur with the
223
+ page; `position` anchors them, and a component that positions itself --
224
+ the tooltip bubble -- keeps its own. A default or `.soft` table frosts
225
+ under glass, so it takes the layers too. Both slots are spent, so a surface
226
+ inside a liquid region carries no `::before` or `::after` of the
227
+ consumer's; the fields cannot host one, and controls stay solid as they do
228
+ under glass.
229
+
230
+ A region of another aesthetic nested inside, or of plain glass, keeps its
231
+ own material and takes no layers. This list names the other aesthetics, and
232
+ `registry.test.ts` fails when one is missing. */
233
+ :is(.glass-liquid, .glass-liquid *):is(.glass, .glass *):is(
234
+ .card,
235
+ .panel,
236
+ .alert,
237
+ .tooltip-bubble,
238
+ .surface,
239
+ .data-table:not(.solid, .ghost)
240
+ ):not(
241
+ .glass-liquid :is(.neobrutalism, .pixel, .chunky-tile, .cyber, .sketch),
242
+ .glass-liquid :is(.neobrutalism, .pixel, .chunky-tile, .cyber, .sketch) *,
243
+ .glass-liquid .glass:not(.glass-liquid),
244
+ .glass-liquid .glass:not(.glass-liquid) *
245
+ ) {
246
+ position: relative;
247
+ isolation: isolate;
248
+
249
+ &::before,
250
+ &::after {
251
+ position: absolute;
252
+ inset: 0;
253
+ z-index: -1;
254
+ border-radius: inherit;
255
+ corner-shape: inherit;
256
+ content: "";
257
+ pointer-events: none;
258
+ }
259
+
260
+ /* Prefixed first, for the reason `surface` gives. */
261
+ &::before {
262
+ -webkit-backdrop-filter: var(--_glass-liquid-blur);
263
+ backdrop-filter: var(--_glass-liquid-blur);
264
+ }
265
+
266
+ &::after {
267
+ backdrop-filter: var(--_glass-liquid-lens);
268
+ background-image: var(--_glass-liquid-sheen);
269
+ box-shadow: var(--_glass-liquid-rim);
270
+ }
271
+ }
272
+ }
273
+
274
+ /* Forced colours paint the plain surface: no layer blurs, bends, or lights it. */
275
+ @media (forced-colors: active) {
276
+ .glass-liquid {
277
+ --_glass-liquid-blur: none;
278
+ --_glass-liquid-lens: none;
279
+ --_glass-liquid-rim: none;
280
+ --_glass-liquid-sheen: none;
281
+ }
113
282
  }
114
283
 
115
284
  /* Solo: the same look painted onto the one element carrying the class, for an
@@ -151,3 +320,59 @@
151
320
  backdrop-filter: none;
152
321
  }
153
322
  }
323
+
324
+ /* With `.glass-liquid`, the liquid pane: its corner and ground, and the same two
325
+ layers behind the element's content. The element's own blur goes, for the
326
+ backdrop-root reason the modifier gives. Both pseudo-elements are spent, so a
327
+ foreign element that draws its own `::before` or `::after` loses them.
328
+
329
+ The layers need a positioned element to anchor to. That one declaration is
330
+ layered, where the element's own `fixed` or `absolute` beats it -- a toast
331
+ keeps its placement, and an unpositioned element is anchored. */
332
+ @layer components {
333
+ .glass-solo.glass-liquid {
334
+ position: relative;
335
+ }
336
+ }
337
+
338
+ .glass-solo.glass-liquid {
339
+ isolation: isolate;
340
+ background-color: color-mix(
341
+ in srgb,
342
+ var(--color-background, light-dark(oklch(98.5% 0 0), oklch(14.5% 0 0))) 30%,
343
+ transparent
344
+ );
345
+ border-color: light-dark(rgb(255 255 255 / 0.45), rgb(255 255 255 / 0.16));
346
+ border-radius: var(--glass-radius-surface, 1.5rem);
347
+ corner-shape: squircle;
348
+ -webkit-backdrop-filter: none;
349
+ backdrop-filter: none;
350
+
351
+ &::before,
352
+ &::after {
353
+ position: absolute;
354
+ inset: 0;
355
+ z-index: -1;
356
+ border-radius: inherit;
357
+ corner-shape: inherit;
358
+ content: "";
359
+ pointer-events: none;
360
+ }
361
+
362
+ &::before {
363
+ -webkit-backdrop-filter: var(--_glass-liquid-blur);
364
+ backdrop-filter: var(--_glass-liquid-blur);
365
+ }
366
+
367
+ &::after {
368
+ backdrop-filter: var(--_glass-liquid-lens);
369
+ background-image: var(--_glass-liquid-sheen);
370
+ box-shadow: var(--_glass-liquid-rim);
371
+ }
372
+ }
373
+
374
+ @media (prefers-reduced-transparency: reduce) {
375
+ .glass-solo.glass-liquid {
376
+ background-color: var(--color-background, light-dark(oklch(98.5% 0 0), oklch(14.5% 0 0)));
377
+ }
378
+ }
@@ -2,7 +2,8 @@
2
2
  for only the one in use: a cascading class has no static call site, so nothing
3
3
  here can be tree-shaken.
4
4
 
5
- Import after the base stylesheet in either form. */
5
+ Import before or after the base stylesheet, in either form: every entrypoint
6
+ states the layer order first, so load order decides nothing. */
6
7
  @import "./neobrutalism.css";
7
8
  @import "./glass.css";
8
9
  @import "./pixel.css";
@@ -1,8 +1,7 @@
1
1
  /* Neobrutalism: thick ink outline, a hard unblurred offset shadow, and a press
2
2
  that moves the element into its own shadow. Material tokens only, no component
3
3
  named -- the ink reaches everything through the intent reset, the shadow is
4
- colourless geometry each component colours against its own intent. Load after
5
- the base stylesheet; source order decides against the zero-specificity reset. */
4
+ colourless geometry each component colours against its own intent. */
6
5
 
7
6
  /* The layer map, stated before anything uses a layer -- see `theme.css`. The
8
7
  aesthetic's token class sits in `components`, below every utility, so a
@@ -37,6 +36,10 @@
37
36
  is the whole look, so this aesthetic asks for the intent instead of the
38
37
  neutral depth colour every other shadow uses. */
39
38
  --ui-shadow-ink: 100%;
39
+ /* The ink replaces the depth colour whole, so this one is inert here; it is
40
+ restated so a region nested inside chunky tile or cyber does not inherit
41
+ their black or transparent depth. */
42
+ --elevation-color: light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
40
43
  --ui-shadow-x: var(--_neo-offset);
41
44
  --ui-shadow-y: var(--_neo-offset);
42
45
  --ui-shadow-blur: 0px;
@@ -47,19 +50,24 @@
47
50
  --ui-halo-spread: initial;
48
51
  --ui-halo-ink: initial;
49
52
 
50
- /* The press: travel the offset, shrink the shadow to nothing, so the element
51
- lands flat where its slab was. A translation `box` scales with the slab, so
52
- the two stay the same length at every size and elevation. Hover keeps the slab still under the pointer;
53
- `box-hover`'s derived fill tint is the whole hover response. One token pair,
54
- no `:hover` list, R3 intact. */
53
+ /* The press: travel the offset, shrink the shadow to nothing, so the
54
+ element lands flat where its slab was. A translation `box` scales with
55
+ the slab, so the two stay the same length at every size and elevation.
56
+ Hover keeps the slab still under the pointer; `box-hover`'s derived fill
57
+ tint is the whole hover response. One token pair, no `:hover` list, R3
58
+ intact. */
55
59
  --ui-active-shadow-x: 0px;
56
60
  --ui-active-shadow-y: 0px;
57
61
  --ui-active-transform: none;
58
62
  --ui-active-translate-x: var(--_neo-offset);
59
63
  --ui-active-translate-y: var(--_neo-offset);
64
+ --ui-hover-transform: none;
60
65
 
61
66
  /* Nothing here clips or blurs, and an aesthetic nested inside another has to
62
- say so rather than inherit it. */
67
+ say so rather than inherit it. Nothing clips, so the global focus outline
68
+ shows and no inset focus layer is wanted: cleared so a region nested
69
+ inside pixel does not inherit pixel's. */
70
+ --ui-focus-inset: initial;
63
71
  --ui-clip: none;
64
72
  --ui-clip-tight: none;
65
73
  /* Round corners, said explicitly, and the pill and chip corners cleared back
@@ -86,9 +94,13 @@
86
94
  --ui-bg-alpha: 1;
87
95
  --ui-surface-image: initial;
88
96
 
89
- /* The control ink glass and chunky tile name, cleared so a region nested
90
- inside either draws its controls in this aesthetic's own ink. */
91
- --ui-control-ink: initial;
97
+ /* A control's neutral line is the theme's control border, not this
98
+ aesthetic's ink: that ink is the primary colour in both themes, so every
99
+ unchecked toggle and resting field drew its boundary as loudly as a
100
+ primary one. Named rather than cleared, so a region nested inside glass
101
+ or chunky tile does not inherit theirs either. */
102
+ --ui-control-ink: var(--color-control-border);
103
+ --ui-rule-ink: initial;
92
104
 
93
105
  /* Chunky tile's heavier label, cleared so a region nested inside it keeps
94
106
  the button's and the badge's own label. */
@@ -64,11 +64,10 @@
64
64
  has to be wider than the edge to show at all. */
65
65
  --ui-focus-inset: calc(var(--_pixel-unit) + var(--focus-ring-width));
66
66
 
67
- /* One square unit off each corner, and nothing else. The staircase this
68
- replaces stepped twice at half the unit to approximate a curve, which is the
69
- one thing a low-resolution grid cannot do: at any size it read as a bevel
70
- that had not quite committed. A single cut of the grid's own unit is the
71
- shape the look is actually made of.
67
+ /* One square unit off each corner, and nothing else. A staircase
68
+ approximates a curve, which is the one thing a low-resolution grid cannot
69
+ do: it reads as a bevel that has not quite committed. A single cut of the
70
+ grid's own unit is the shape the look is made of.
72
71
 
73
72
  The polygon resolves the unit here, on `.pixel`, and inherits down
74
73
  already-resolved -- so a component cannot rescale the corner by setting
@@ -108,6 +107,13 @@
108
107
  --ui-corner-br: initial;
109
108
  --ui-corner-bl: initial;
110
109
 
110
+ /* The ring is the edge and nothing here is depth, so the shadow ink, the
111
+ press's shadow, and the depth colour are cleared or restated rather than
112
+ inherited from an aesthetic around this one. */
113
+ --ui-shadow-ink: initial;
114
+ --ui-active-shadow-x: initial;
115
+ --ui-active-shadow-y: initial;
116
+ --elevation-color: light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
111
117
  --ui-hover-transform: none;
112
118
  --ui-active-transform: none;
113
119
  --ui-active-translate-x: initial;
@@ -122,9 +128,13 @@
122
128
  font to fall back to but a monospace stack. */
123
129
  font-family: var(--font-pixel, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
124
130
 
125
- /* The control ink glass and chunky tile name, cleared so a region nested
126
- inside either draws its controls in this aesthetic's own ink. */
127
- --ui-control-ink: initial;
131
+ /* A control's neutral line is the theme's control border, not this
132
+ aesthetic's ink: that ink is the primary colour in both themes, so every
133
+ unchecked toggle and resting field drew its boundary as loudly as a
134
+ primary one. Named rather than cleared, so a region nested inside glass
135
+ or chunky tile does not inherit theirs either. */
136
+ --ui-control-ink: var(--color-control-border);
137
+ --ui-rule-ink: initial;
128
138
 
129
139
  /* Chunky tile's heavier label, cleared so a region nested inside it keeps
130
140
  the button's and the badge's own label. */
@@ -1,6 +1,6 @@
1
1
  /* Sketch: uneven elliptical corners, a solid ink edge, and a small hard shadow.
2
2
  The handwriting face is consumer-supplied, so this file has no font asset or
3
- network side effect. Load after the base stylesheet. */
3
+ network side effect. */
4
4
 
5
5
  /* The layer map, stated before anything uses a layer -- see `theme.css`. The
6
6
  aesthetic's token class sits in `components`, below every utility, so a
@@ -28,7 +28,10 @@
28
28
  --ui-corner-shape: round;
29
29
 
30
30
  --ui-ink: light-dark(var(--color-neutral-950), var(--color-neutral-50));
31
- --ui-control-ink: initial;
31
+ /* Controls take the theme's control border rather than the ink, which is the
32
+ primary colour in both themes; see `neobrutalism.css`. */
33
+ --ui-control-ink: var(--color-control-border);
34
+ --ui-rule-ink: initial;
32
35
  --ui-border-width: 1px;
33
36
  --ui-border-max: 100px;
34
37
  --ui-line-style: solid;
@@ -107,7 +110,7 @@
107
110
  ) {
108
111
  /* The pointed outlines: controls, fields, surfaces, and chips. */
109
112
  &:is(.sketch, .sketch *):not(.sketch-rounded, .sketch-rounded *) {
110
- &:is(.btn, .ipt, .textarea, .select, .input-group, .pre, .tooltip-bubble, .data-table) {
113
+ &:is(.btn, .ipt, .textarea, .select, .input-group, .pre, .tooltip-bubble) {
111
114
  &:nth-child(4n + 2) {
112
115
  --ui-radius: var(--sketch-radius, 18px 240px 15px 255px / 240px 15px 255px 18px);
113
116
  }
@@ -139,7 +142,7 @@
139
142
  }
140
143
  }
141
144
 
142
- &:is(.card, .panel, .alert) {
145
+ &:is(.card, .panel, .alert, .data-table) {
143
146
  &:nth-child(4n + 2) {
144
147
  --ui-radius-surface: var(--sketch-radius-surface, 4px 75px 5px 80px / 75px 3px 80px 4px);
145
148
  }
@@ -170,7 +173,7 @@
170
173
 
171
174
  /* The rounded outlines, in the same four places. */
172
175
  &:is(.sketch-rounded, .sketch-rounded *) {
173
- &:is(.btn, .ipt, .textarea, .select, .input-group, .pre, .tooltip-bubble, .data-table) {
176
+ &:is(.btn, .ipt, .textarea, .select, .input-group, .pre, .tooltip-bubble) {
174
177
  &:nth-child(4n + 2) {
175
178
  --ui-radius: var(--sketch-radius-rounded, 11px 25px 12px 24px / 23px 11px 22px 12px);
176
179
  }
@@ -202,7 +205,7 @@
202
205
  }
203
206
  }
204
207
 
205
- &:is(.card, .panel, .alert) {
208
+ &:is(.card, .panel, .alert, .data-table) {
206
209
  &:nth-child(4n + 2) {
207
210
  --ui-radius-surface: var(--sketch-radius-surface-rounded, 17px 36px 18px 34px / 33px 16px 32px 17px);
208
211
  }
@@ -283,7 +286,11 @@
283
286
  /* The surface shape: the element this exists for is usually a pane. An action
284
287
  takes the control shape below. */
285
288
  border-radius: var(--sketch-radius-surface, 80px 4px 75px 5px / 3px 70px 4px 80px);
286
- box-shadow: 2px 2px 0 0 var(--_sketch-ink);
289
+ /* The aesthetic's neutral depth cue, not the ink: the class casts its 2px
290
+ offset in the depth colour, and casting a solo pane in full ink would read
291
+ as a heavier, neobrutalist slab. The depth colour is a literal, for the reason
292
+ `.glass-solo` gives. */
293
+ box-shadow: 2px 2px 0 0 light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
287
294
  font-family: var(--font-sketch, cursive);
288
295
  }
289
296
 
package/dist/tw/box.css CHANGED
@@ -35,6 +35,21 @@
35
35
  own presentation instead of the container's reinterpretation of one. */
36
36
  --_fill-cap: 100%;
37
37
  --_capped: min(var(--ui-fill, var(--_d-fill)), var(--_fill-cap, 100%), var(--intent-fill-max, 100%));
38
+ /* Whether the intent's cap stopped the fill short, as 0% or 100%. A capped
39
+ fill is a plate the presentation asked to be full, so it rests on the page
40
+ background rather than on nothing: 20% of the page's ink over nothing
41
+ showed whatever sat behind a neutral `.solid` -- glass's backdrop, an
42
+ image, the colour of a filled card around it -- where on a plain page it
43
+ read as the light grey plate it is meant to be. The component's own cap
44
+ is measured out first, so a text control's 20% wash and a neutral
45
+ `.card.soft`'s 0% stay the tints they are, whatever the intent. Stepped
46
+ rather than scaled because the two percentages cannot be divided in every
47
+ engine yet. */
48
+ --_plate: clamp(
49
+ 0%,
50
+ calc((min(var(--ui-fill, var(--_d-fill)), var(--_fill-cap, 100%)) - var(--_capped)) * 1000),
51
+ 100%
52
+ );
38
53
  --_fill: calc(var(--_capped) * var(--ui-bg-alpha, 1));
39
54
  --_hover-fill: calc(min(100%, var(--_capped) + var(--hover-step)) * var(--ui-bg-alpha, 1));
40
55
  /* How much of the foreground is the contrast ink: what the presentation asks
@@ -57,40 +72,41 @@
57
72
  declaration beats the inherited one at any specificity; `surface`, `kbd`,
58
73
  `code`, `pre` and the tooltip each restate theirs after `@apply box`. */
59
74
  --_d-ground: transparent;
75
+ /* The ground, made the page background wherever `--_plate` says the cap
76
+ stopped the fill -- over a component's own ground too, so a capped
77
+ `.solid` surface under glass is opaque like every other `.solid`. */
78
+ --_ground: color-mix(in oklab, var(--color-background) var(--_plate), var(--_d-ground, transparent));
60
79
 
61
- --_bg: color-mix(in oklab, var(--intent-color) var(--_fill), var(--_d-ground, transparent));
80
+ --_bg: color-mix(in oklab, var(--intent-color) var(--_fill), var(--_ground));
62
81
  /* The two ends answer different questions: `--intent-contrast` prints *on* the
63
82
  fill, `--intent-strong` prints on the page (the slot for readable ink on a
64
83
  subtle fill). Reading `--intent-color` at the page end instead makes every
65
84
  colour do double duty and prints a soft amber component at 3.1:1.
66
85
  `--intent-contrast` keeps its own meaning -- `--_on-fill` reconciles it with
67
86
  a capped intent -- rather than being bent so neutral's contrast is the ink,
68
- which rendered a checked neutral checkbox black-on-black. */
87
+ which renders a checked neutral checkbox black-on-black. */
69
88
  --_fg: color-mix(in oklab, var(--intent-contrast) var(--_on-fill), var(--intent-strong));
70
89
  /* 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.
90
+ edge. It fades toward `transparent`, not toward the plate: the plate
91
+ already runs under the border (nothing clips the background to the padding
92
+ box), so a line blended toward `--_bg` paints the plate a second time over
93
+ itself -- invisible on an opaque plate, a ring on a translucent one (a
94
+ neutral fill capped at 20%, or any fill `--ui-bg-alpha` thins). Faded
95
+ toward nothing, the border shows the one coat beneath it.
78
96
 
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
97
+ Blend first, amount after (`--_edge-amount` below): the other order lands
98
+ `.edgeless` on the fill colour, which the border box paints as a second
99
+ coat -- a ring around an element that asked for no edge. See
82
100
  docs/internal/model.md#presentation. */
83
101
  --_line: color-mix(in oklab, transparent var(--ui-fill, var(--_d-fill)), var(--intent-border));
84
102
  /* How much line there is, as an input rather than a result. A component that
85
103
  must keep its line whatever the edge class says raises `--_edge-floor`
86
104
  instead of rewriting `--_edge`, and the composition above still runs.
87
105
 
88
- The distinction is not cosmetic. Rewriting the result drops every term the
89
- result was composed from, silently and with nothing to report: that is how
90
- `--ui-border` stopped reaching all six text controls, and how `--_d-border`
91
- sat declared and unread on each of them for a release. Bounding the input
92
- leaves the axis live, so `.edged` and `.edgeless` still mean what they say
93
- on a component that happens to floor itself. */
106
+ Rewriting the result drops every term it was composed from, silently and
107
+ with nothing to report. Bounding the input leaves the axis live, so
108
+ `.edged` and `.edgeless` still mean what they say on a component that
109
+ floors itself. */
94
110
  --_edge-amount: max(var(--ui-border, var(--_d-border)), var(--_edge-floor, 0%));
95
111
  --_edge: color-mix(in oklab, var(--_line) var(--_edge-amount), transparent);
96
112
  --_width: min(var(--ui-border-width, var(--border-width)), var(--ui-border-max, 100px));
@@ -171,14 +187,11 @@
171
187
  --_focus-stack: var(--_shadow), inset 0 0 0 var(--ui-focus-inset) var(--focus-ring);
172
188
 
173
189
  /* Every box sizes itself as `min-height`/`min-width: var(--control-height)`
174
- plus its own padding and the border shorthand above, so its geometry is only
175
- right under `box-sizing: border-box`. `reset.css` sets that on `*`, but only
176
- the `.`, `/native` and `/tw/reset` entrypoints carry the reset -- and the
177
- first two pull Tailwind's Preflight, which sets it anyway. `/components` and
178
- the granular `/tw/*` component entrypoints ship neither, so a box there
179
- inherited the `content-box` default and every sized control came out
180
- inflated by its own padding and border. A component's own geometry is not an
181
- external reset's job; `box` owns it. */
190
+ plus its own padding and the border shorthand above, so its geometry is
191
+ only right under `border-box`. `reset.css` sets that on `*`, but only the
192
+ `.`, `/native` and `/tw/reset` entrypoints carry the reset; `/components`
193
+ and the granular `/tw/*` component entrypoints ship neither it nor
194
+ Preflight, so `box` owns it. */
182
195
  box-sizing: border-box;
183
196
  background-color: var(--_bg);
184
197
  color: var(--_fg);
@@ -221,6 +234,15 @@
221
234
  transform var(--motion-duration-normal) var(--motion-ease),
222
235
  translate var(--motion-duration-normal) var(--motion-ease);
223
236
 
237
+ /* Reduced motion stops here as well as in `reset.css`: `/components` and the
238
+ focused `/tw/*` entries carry no reset, so each component that moves says
239
+ so itself -- this for everything `box` paints, and the components with a
240
+ transition or animation of their own beside it. The same near-zero
241
+ duration the reset uses, so a `transitionend` still fires. */
242
+ @media (prefers-reduced-motion: reduce) {
243
+ transition-duration: 0.01ms;
244
+ }
245
+
224
246
  /* An aesthetic that clips cannot show the global focus outline, so it declares
225
247
  `--ui-focus-inset` and gets an inset layer instead. With no such aesthetic
226
248
  the token is undefined, which makes the whole stack invalid at computed-value
@@ -254,7 +276,7 @@
254
276
  and wrong for something you type into, so a component opts in. */
255
277
  @utility box-hover {
256
278
  &:not(:disabled, .disabled, [aria-disabled="true"], [data-disabled]):hover {
257
- --_bg: color-mix(in oklab, var(--intent-hover) var(--_hover-fill), var(--_d-ground, transparent));
279
+ --_bg: color-mix(in oklab, var(--intent-hover) var(--_hover-fill), var(--_ground));
258
280
  /* P3 again, against the hover tone: the line still fades as the fill fills
259
281
  in, but toward the hover ink. Without this the edge keeps the resting ink
260
282
  -- a ring of the colour the box just left. */