@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
@@ -0,0 +1,227 @@
1
+ /* Cyber: bevelled corners, a thin bright edge, and a glow in the component's own
2
+ colour. See docs/internal/model.md#cyber for the decision.
3
+
4
+ Tier 1 only. The bevel is `corner-shape`, not `clip-path`: a clip removes
5
+ every outer shadow, so a clipped chamfer has nothing left to glow, and an
6
+ inset ring cannot follow a diagonal cut the way `.pixel`'s follows its square
7
+ one. `corner-shape` draws the corner itself, and the border, the glow, and the
8
+ focus outline all follow it. Load after the base stylesheet. */
9
+
10
+ /* The layer map, stated before anything uses a layer -- see `theme.css`. The
11
+ aesthetic's token class sits in `components`, below every utility, so a
12
+ consumer's own utility or unlayered CSS beats it; the solo class stays
13
+ unlayered, because it has to beat a foreign component's unlayered CSS. See
14
+ docs/internal/model.md#cascade-layers. */
15
+ @layer theme, base, components, utilities;
16
+
17
+ @layer components {
18
+ .cyber {
19
+ /* Knobs, read with a fallback so an ancestor can set them (R8; see
20
+ model.md#rules-for-aesthetics). One cut size for controls and surfaces
21
+ alike, so a button and the card around it read as the same material; the
22
+ two shapes then place that cut on opposite diagonals, so they read as
23
+ different things. A shape knob takes any `border-radius` value -- the
24
+ documented presets are a hexagon, a parallelogram, and a single notch --
25
+ and the cut knob sizes the default pair without restating it.
26
+
27
+ The cut is the corner length at scale 1, and `box` multiplies it by the
28
+ size step, so a `.p-xs` icon button takes half the cut and a `.lg` button
29
+ a quarter more -- always a true 45 degrees, because it stays a length. It
30
+ used to be capped at a quarter of the element's box, a percentage, which
31
+ ran shallower than 45 degrees on a small wide button; the step retired
32
+ the cap. A shape set through a knob is taken as written, unscaled: it is
33
+ read as `--ui-radius`, which `box` honours over the computed corner, and
34
+ left unset the reference makes the token guaranteed-invalid so the
35
+ computed corner draws. */
36
+ --_cyber-cut: var(--cyber-cut, 0.625rem);
37
+ --_cyber-glow: var(--cyber-glow, 8px);
38
+
39
+ /* The neutral line. A neon hue is the look's signature, but hue is intent's
40
+ (R1), so the default follows the theme the way neobrutalism's and pixel's
41
+ ink does, and `--cyber-ink` lets an application choose a neon neutral
42
+ without the aesthetic imposing one on every palette. A named intent keeps
43
+ its own edge colour. */
44
+ --ui-ink: var(--cyber-ink, light-dark(var(--color-neutral-950), var(--color-neutral-50)));
45
+
46
+ /* Controls cut top-left and bottom-right, through the four corner switches
47
+ every component reads; surfaces cut top-right and bottom-left, which the
48
+ switches cannot say at the same time, so their shape is written whole and
49
+ does not scale -- a card is large enough that the full cut is right at
50
+ every padding. Anything fully round -- a badge, a switch, the progress
51
+ track, the tooltip icon, and through their own full radius a radio and a
52
+ `.btn.pill` -- cuts all the way to points: a diamond, or a hexagon on
53
+ something wider than it is tall. Chips square outright, which is what
54
+ tells the checkbox from the radio beside it. */
55
+ --ui-corner-shape: bevel;
56
+ --ui-corner: var(--_cyber-cut);
57
+ --ui-corner-surface: var(--_cyber-cut);
58
+ --ui-corner-tl: 1;
59
+ --ui-corner-tr: 0;
60
+ --ui-corner-br: 1;
61
+ --ui-corner-bl: 0;
62
+ --ui-radius: var(--cyber-shape);
63
+ --ui-radius-surface: var(--cyber-shape-surface, 0 var(--_cyber-cut) / 0 var(--_cyber-cut));
64
+ --ui-radius-pill: calc(infinity * 1px);
65
+ --ui-radius-tight: 0;
66
+ --ui-border-width: 1px;
67
+ --ui-border-max: 100px;
68
+ --ui-line-style: initial;
69
+ --ui-rule-style: initial;
70
+
71
+ /* The glow is light, not depth, so it is the halo: the component's own intent
72
+ colour at 40%, blurred, with no offset. Elevation does not scale it, so
73
+ every component glows -- fields and chips included -- and `.flat` does
74
+ not put it out. There is no depth, and the transparent depth colour keeps
75
+ `.raised` and `.floating` from drawing any. */
76
+ --elevation-color: transparent;
77
+ --ui-shadow-ink: 0%;
78
+ --ui-shadow-x: 0px;
79
+ --ui-shadow-y: 0px;
80
+ --ui-shadow-blur: 0px;
81
+ --ui-shadow-spread: 0px;
82
+ --ui-halo-blur: var(--_cyber-glow);
83
+ --ui-halo-spread: 0px;
84
+ --ui-halo-ink: 40%;
85
+
86
+ /* The glow holds still under the pointer and under a press; the press is the
87
+ base scale, restated because a region nested inside another aesthetic must
88
+ not inherit that one's travel. Reduced motion drops it below. */
89
+ --ui-hover-transform: none;
90
+ --ui-active-shadow-x: 0px;
91
+ --ui-active-shadow-y: 0px;
92
+ --ui-active-transform: scale(0.97);
93
+ --ui-active-translate-x: initial;
94
+ --ui-active-translate-y: initial;
95
+
96
+ /* Nothing here clips, blurs, thins its fill, or draws its edge as a ring, and
97
+ an aesthetic nested inside one that does has to say so rather than inherit
98
+ it. */
99
+ --ui-clip: none;
100
+ --ui-clip-tight: none;
101
+ --ui-shadow-inset: ;
102
+ --ui-shadow-edge: initial;
103
+ --ui-backdrop: none;
104
+ --ui-surface-ground: initial;
105
+ --ui-surface-shadow: initial;
106
+ --ui-surface-image: initial;
107
+ --ui-bg-alpha: 1;
108
+
109
+ /* Consumer-supplied, the way `.pixel` reads `--font-pixel`. The package ships
110
+ no font binary; the monospace stack is the closest technical face every
111
+ platform already has. */
112
+ font-family: var(--font-cyber, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
113
+
114
+ /* The control ink glass and chunky tile name, cleared so a region nested
115
+ inside either draws its controls in this aesthetic's own ink. */
116
+ --ui-control-ink: initial;
117
+
118
+ /* Chunky tile's heavier label, cleared so a region nested inside it keeps
119
+ the button's and the badge's own label. */
120
+ --ui-label-weight: initial;
121
+ --ui-label-tracking: initial;
122
+ --ui-label-case: initial;
123
+ --ui-label-shadow: initial;
124
+ }
125
+
126
+ /* Firefox and Safari do not draw `corner-shape` yet, and there the radius would
127
+ round the corners instead. A rounded cyber element reads as a different
128
+ aesthetic; a square one reads as the same aesthetic simplified. What is fully
129
+ round stays round rather than square: a square radio is a checkbox, and a
130
+ pill is the nearest a diamond gets without a bevel. */
131
+ @supports not (corner-shape: bevel) {
132
+ .cyber {
133
+ --ui-corner: 0;
134
+ --ui-radius: 0;
135
+ --ui-radius-surface: 0;
136
+ }
137
+ }
138
+
139
+ @media (prefers-reduced-motion: reduce) {
140
+ .cyber {
141
+ --ui-active-transform: none;
142
+ }
143
+ }
144
+ }
145
+
146
+ /* Solo: the same look painted onto the one element carrying the class, for an
147
+ element the package does not style. See docs/internal/model.md#solo-classes and
148
+ the note on `.glass-solo`: own knobs and foundation tokens with literal
149
+ fallbacks, never a shared `--ui-*`, `--intent-*`, or `--elevation-color`
150
+ token, unlayered, and not for this package's own components. */
151
+ .cyber-solo {
152
+ --_cyber-ink: var(
153
+ --cyber-ink,
154
+ light-dark(var(--color-neutral-950, oklch(14.5% 0 0)), var(--color-neutral-50, oklch(98.5% 0 0)))
155
+ );
156
+
157
+ border: 1px solid var(--_cyber-ink);
158
+ /* The surface shape: the element this exists for is usually a pane. An action
159
+ takes the control shape below. */
160
+ border-radius: var(
161
+ --cyber-shape-surface,
162
+ 0 min(var(--cyber-cut, 0.625rem), 25%) / 0 min(var(--cyber-cut, 0.625rem), 25%)
163
+ );
164
+ corner-shape: bevel;
165
+ /* Always lit: the elevation gate the aesthetic class answers to is `box`'s,
166
+ and a solo element is one the consumer chose to light up. */
167
+ box-shadow: 0 0 var(--cyber-glow, 8px) 0 color-mix(in oklab, var(--_cyber-ink) 40%, transparent);
168
+ font-family: var(--font-cyber, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
169
+ transition: transform var(--motion-duration-normal, 200ms) var(--motion-ease, cubic-bezier(0.2, 0, 0, 1));
170
+ }
171
+
172
+ /* An action takes the control shape, on the opposite diagonal to a pane's. */
173
+ .cyber-solo:is(
174
+ button,
175
+ a[href],
176
+ [role="button"],
177
+ summary,
178
+ input[type="button"],
179
+ input[type="submit"],
180
+ input[type="reset"]
181
+ ) {
182
+ border-radius: var(--cyber-shape, min(var(--cyber-cut, 0.625rem), 25%) 0 / min(var(--cyber-cut, 0.625rem), 25%) 0);
183
+ }
184
+
185
+ @supports not (corner-shape: bevel) {
186
+ .cyber-solo,
187
+ .cyber-solo:is(
188
+ button,
189
+ a[href],
190
+ [role="button"],
191
+ summary,
192
+ input[type="button"],
193
+ input[type="submit"],
194
+ input[type="reset"]
195
+ ) {
196
+ border-radius: 0;
197
+ }
198
+ }
199
+
200
+ /* The press, only on something pressed: an action, not disabled. A solo
201
+ container stays put, the way only `.btn` and an opted-in card press under
202
+ the aesthetic class. */
203
+ .cyber-solo:is(
204
+ button,
205
+ a[href],
206
+ [role="button"],
207
+ summary,
208
+ input[type="button"],
209
+ input[type="submit"],
210
+ input[type="reset"]
211
+ ):active:not(:disabled, [aria-disabled="true"]) {
212
+ transform: scale(0.97);
213
+ }
214
+
215
+ @media (prefers-reduced-motion: reduce) {
216
+ .cyber-solo:is(
217
+ button,
218
+ a[href],
219
+ [role="button"],
220
+ summary,
221
+ input[type="button"],
222
+ input[type="submit"],
223
+ input[type="reset"]
224
+ ):active:not(:disabled, [aria-disabled="true"]) {
225
+ transform: none;
226
+ }
227
+ }
@@ -7,60 +7,147 @@
7
7
  panels, alerts and tooltips while controls stay solid. An active blur costs a
8
8
  compositing layer apiece, and one under every control of a dense cluster reads
9
9
  as noise. */
10
- .glass {
11
- /* Knobs read with a fallback so an ancestor can set them -- declared here they
12
- outrank `:root` (R8; see model.md#rules-for-aesthetics). Glass is softer
13
- than the base geometry because a translucent tight corner reads as a
14
- cut-out; a control/surface pair rather than one value, because they do not
15
- scale together and 12/16 keeps a button looking at home inside a card. */
16
- --ui-radius: var(--glass-radius, 0.75rem);
17
- --ui-radius-surface: var(--glass-radius-surface, 1rem);
18
- --ui-border-width: 1px;
19
- --ui-border-max: 100px;
20
10
 
21
- /* Glass catches light from above whatever is under it, so the edge is a light
22
- hairline in both themes rather than a theme-following ink. */
23
- --ui-ink: light-dark(rgb(255 255 255 / 0.65), rgb(255 255 255 / 0.22));
11
+ /* The layer map, stated before anything uses a layer -- see `theme.css`. The
12
+ aesthetic's token class sits in `components`, below every utility, so a
13
+ consumer's own utility or unlayered CSS beats it; the solo class stays
14
+ unlayered, because it has to beat a foreign component's unlayered CSS. See
15
+ docs/internal/model.md#cascade-layers. */
16
+ @layer theme, base, components, utilities;
24
17
 
25
- --ui-bg-alpha: 0.8;
26
- --ui-backdrop: blur(14px) saturate(1.6);
18
+ @layer components {
19
+ .glass {
20
+ /* Knobs read with a fallback so an ancestor can set them -- declared here they
21
+ outrank `:root` (R8; see model.md#rules-for-aesthetics). Glass is softer
22
+ than the base geometry because a translucent tight corner reads as a
23
+ cut-out; a control/surface pair rather than one value, because they do not
24
+ scale together and 12/16 keeps a button looking at home inside a card. */
25
+ --ui-corner: var(--glass-radius, 0.75rem);
26
+ --ui-corner-surface: var(--glass-radius-surface, 1rem);
27
+ --ui-border-width: 1px;
28
+ --ui-border-max: 100px;
29
+ --ui-line-style: initial;
30
+ --ui-rule-style: initial;
31
+
32
+ /* Glass catches light from above whatever is under it, so the edge is a light
33
+ hairline in both themes rather than a theme-following ink. */
34
+ --ui-ink: light-dark(rgb(255 255 255 / 0.65), rgb(255 255 255 / 0.22));
35
+ /* A control's boundary is not a pane's edge. The hairline erases an
36
+ unchecked checkbox on a light page (1.33:1 inside a glass card) and leaves
37
+ a field with nothing marking where typing goes, so controls take the
38
+ theme's ink at 55% instead: translucent, so it tints whatever is behind
39
+ the pane rather than drawing a flat grey, and 6.31:1 on a checkbox, 2.31:1
40
+ on a field's 60% resting line, in light. See
41
+ docs/internal/model.md#presentation. */
42
+ --ui-control-ink: light-dark(rgb(0 0 0 / 0.55), rgb(255 255 255 / 0.55));
43
+
44
+ --ui-bg-alpha: 0.8;
45
+ --ui-backdrop: blur(14px) saturate(1.6);
46
+ --ui-surface-image: initial;
27
47
 
28
- /* R6. Controls carry no shadow of their own -- the depth in this aesthetic is
29
- the blur, and a drop shadow under a translucent button reads as grime. All
30
- general parts are explicitly zero so inherited or future geometry cannot
31
- leak onto controls. `--ui-surface-shadow` below replaces the shadow of
32
- surfaces only. */
33
- --ui-shadow-x: 0px;
34
- --ui-shadow-y: 0px;
35
- --ui-shadow-blur: 0px;
36
- --ui-shadow-spread: 0px;
37
- /* `--ui-bg-alpha` multiplies the fill, and a surface's default fill is 0%:
38
- without a translucent ground the blur ends up behind an opaque background
39
- and nothing shows through. */
40
- --ui-surface-ground: color-mix(in srgb, var(--color-background) 45%, transparent);
48
+ /* R6. Controls carry no shadow of their own -- the depth in this aesthetic is
49
+ the blur, and a drop shadow under a translucent button reads as grime. All
50
+ general parts are explicitly zero so inherited or future geometry cannot
51
+ leak onto controls. `--ui-surface-shadow` below replaces the shadow of
52
+ surfaces only. */
53
+ --ui-shadow-x: 0px;
54
+ --ui-shadow-y: 0px;
55
+ --ui-shadow-blur: 0px;
56
+ --ui-shadow-spread: 0px;
57
+ /* No halo, cleared so a region nested inside another
58
+ aesthetic does not inherit one. */
59
+ --ui-halo-blur: initial;
60
+ --ui-halo-spread: initial;
61
+ --ui-halo-ink: initial;
62
+ /* `--ui-bg-alpha` multiplies the fill, and a surface's default fill is 0%:
63
+ without a translucent ground the blur ends up behind an opaque background
64
+ and nothing shows through. */
65
+ --ui-surface-ground: color-mix(in srgb, var(--color-background) 45%, transparent);
41
66
 
42
- /* Two layers pulling in with negative spread, so the shadow tucks under the
43
- surface instead of haloing onto the backdrop. It is the one shipped aesthetic
44
- that cannot be described with the parts, so it takes `surface`'s
45
- complete-value slot -- which is also what keeps it off buttons and chips --
46
- and the registry records that `.flat` does not reach it. */
47
- --ui-surface-shadow: 0 1px 2px -1px var(--elevation-color), 0 18px 40px -16px var(--elevation-color);
67
+ /* Two layers pulling in with negative spread, so the shadow tucks under the
68
+ surface instead of haloing onto the backdrop. It is the one shipped aesthetic
69
+ that cannot be described with the parts, so it takes `surface`'s
70
+ complete-value slot -- which is also what keeps it off buttons and chips --
71
+ and the registry records that `.flat` does not reach it. */
72
+ --ui-surface-shadow: 0 1px 2px -1px var(--elevation-color), 0 18px 40px -16px var(--elevation-color);
73
+
74
+ --ui-clip: none;
75
+ --ui-clip-tight: none;
76
+ /* Round corners, said explicitly, and the pill and chip corners cleared back
77
+ to `--ui-corner`: a region nested inside `.cyber` would otherwise inherit
78
+ its bevel, its diagonal, its diamond pills, and its square chips. */
79
+ --ui-corner-shape: round;
80
+ --ui-radius-pill: initial;
81
+ --ui-radius-tight: initial;
82
+ --ui-radius: initial;
83
+ --ui-radius-surface: initial;
84
+ --ui-corner-tl: initial;
85
+ --ui-corner-tr: initial;
86
+ --ui-corner-br: initial;
87
+ --ui-corner-bl: initial;
88
+ /* Nothing here draws its edge as a ring, and an aesthetic nested inside pixel
89
+ has to say so rather than inherit it. */
90
+ --ui-shadow-edge: initial;
91
+ --ui-hover-transform: none;
92
+ --ui-active-transform: none;
93
+ --ui-active-translate-x: initial;
94
+ --ui-active-translate-y: initial;
95
+
96
+ /* Chunky tile's heavier label, cleared so a region nested inside it keeps
97
+ the button's and the badge's own label. */
98
+ --ui-label-weight: initial;
99
+ --ui-label-tracking: initial;
100
+ --ui-label-case: initial;
101
+ --ui-label-shadow: initial;
102
+ }
48
103
 
49
- --ui-clip: none;
50
- --ui-clip-tight: none;
51
- /* Nothing here draws its edge as a ring, and an aesthetic nested inside pixel
52
- has to say so rather than inherit it. */
53
- --ui-shadow-edge: initial;
54
- --ui-hover-transform: none;
55
- --ui-active-transform: none;
104
+ /* Transparency is the whole aesthetic, so the honest degradation is an opaque
105
+ surface rather than a softer blur. */
106
+ @media (prefers-reduced-transparency: reduce) {
107
+ .glass {
108
+ --ui-bg-alpha: 1;
109
+ --ui-backdrop: none;
110
+ --ui-surface-ground: var(--color-background);
111
+ }
112
+ }
113
+ }
114
+
115
+ /* Solo: the same look painted onto the one element carrying the class, for an
116
+ element the package does not style -- a toast from another library, a plain
117
+ `<div>` on a page that never loaded the base stylesheet. See
118
+ docs/internal/model.md#solo-classes.
119
+
120
+ It reads the aesthetic's own knobs and the theme's foundation tokens, each
121
+ with its shipped literal as the fallback, and never a `--ui-*`, `--intent-*`,
122
+ or `--elevation-color` token: those belong to whichever aesthetic an ancestor
123
+ carries, and a solo element has to look the same inside any of them.
124
+ Unlayered and one class deep, so it beats a foreign component's
125
+ zero-specificity rules wherever they load. Not for this package's own
126
+ components, which take the aesthetic class instead. */
127
+ .glass-solo {
128
+ background-color: color-mix(
129
+ in srgb,
130
+ var(--color-background, light-dark(oklch(98.5% 0 0), oklch(14.5% 0 0))) 45%,
131
+ transparent
132
+ );
133
+ border: 1px solid light-dark(rgb(255 255 255 / 0.65), rgb(255 255 255 / 0.22));
134
+ /* The surface corner: the element this exists for is a pane. */
135
+ border-radius: var(--glass-radius-surface, 1rem);
136
+ /* The theme's depth colour as a literal, for the reason above: inside a
137
+ `.chunky-tile` region the token is black. */
138
+ box-shadow:
139
+ 0 1px 2px -1px light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55)),
140
+ 0 18px 40px -16px light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.55));
141
+ /* Prefixed first, for the reason `surface` gives: written second, the
142
+ minifier folds the pair into the prefixed one and Firefox loses the blur. */
143
+ -webkit-backdrop-filter: blur(14px) saturate(1.6);
144
+ backdrop-filter: blur(14px) saturate(1.6);
56
145
  }
57
146
 
58
- /* Transparency is the whole aesthetic, so the honest degradation is an opaque
59
- surface rather than a softer blur. */
60
147
  @media (prefers-reduced-transparency: reduce) {
61
- .glass {
62
- --ui-bg-alpha: 1;
63
- --ui-backdrop: none;
64
- --ui-surface-ground: var(--color-background);
148
+ .glass-solo {
149
+ background-color: var(--color-background, light-dark(oklch(98.5% 0 0), oklch(14.5% 0 0)));
150
+ -webkit-backdrop-filter: none;
151
+ backdrop-filter: none;
65
152
  }
66
153
  }
@@ -7,3 +7,5 @@
7
7
  @import "./glass.css";
8
8
  @import "./pixel.css";
9
9
  @import "./chunky-tile.css";
10
+ @import "./cyber.css";
11
+ @import "./sketch.css";
@@ -3,53 +3,100 @@
3
3
  named -- the ink reaches everything through the intent reset, the shadow is
4
4
  colourless geometry each component colours against its own intent. Load after
5
5
  the base stylesheet; source order decides against the zero-specificity reset. */
6
- .neobrutalism {
7
- /* Read with a fallback, not declared, so an ancestor (`:root`, where an app
8
- themes) can set `--neo-offset`; the private carries the resolved value to
9
- the six places below. See model.md#rules-for-aesthetics (R8). */
10
- --_neo-offset: var(--neo-offset, 4px);
11
6
 
12
- /* Pure black reads as ink on a light page and disappears on a dark one, so the
13
- ink follows the theme rather than the palette. A thick border in the neutral
14
- border grey is a thick grey box, not a brutalist one. */
15
- --ui-ink: light-dark(var(--color-neutral-950), var(--color-neutral-50));
16
-
17
- --ui-radius: 0;
18
- --ui-radius-surface: 0;
19
- --ui-border-width: 2px;
20
- --ui-border-max: 100px;
21
-
22
- /* Hard, unblurred, and offset. Scaled by each component's own elevation, so
23
- the registry decides which components sit on a slab and `.flat` takes one
24
- off without this file knowing which component it landed on. */
25
- /* The slab is ink, not shade: a hard offset of the element's own line colour
26
- is the whole look, so this aesthetic asks for the intent instead of the
27
- neutral depth colour every other shadow uses. */
28
- --ui-shadow-ink: 100%;
29
- --ui-shadow-x: var(--_neo-offset);
30
- --ui-shadow-y: var(--_neo-offset);
31
- --ui-shadow-blur: 0px;
32
- --ui-shadow-spread: 0px;
33
-
34
- /* The press: travel the offset, shrink the shadow to nothing, so the element
35
- lands flat where its slab was. Hover keeps the slab still under the pointer;
36
- `box-hover`'s derived fill tint is the whole hover response. One token pair,
37
- no `:hover` list, R3 intact. */
38
- --ui-active-shadow-x: 0px;
39
- --ui-active-shadow-y: 0px;
40
- --ui-active-transform: translate(var(--_neo-offset), var(--_neo-offset));
41
-
42
- /* Nothing here clips or blurs, and an aesthetic nested inside another has to
43
- say so rather than inherit it. */
44
- --ui-clip: none;
45
- --ui-clip-tight: none;
46
- --ui-shadow-inset: ;
47
- /* The slab is depth cast in ink, not this element's line, so an `.edgeless`
48
- button keeps it. Cleared rather than omitted, because a neobrutalist region
49
- nested inside a pixel one would otherwise inherit pixel's answer and lose the
50
- slab on everything the edge axis rests at zero. */
51
- --ui-shadow-edge: initial;
52
- --ui-backdrop: none;
7
+ /* The layer map, stated before anything uses a layer -- see `theme.css`. The
8
+ aesthetic's token class sits in `components`, below every utility, so a
9
+ consumer's own utility or unlayered CSS beats it; the solo class stays
10
+ unlayered, because it has to beat a foreign component's unlayered CSS. See
11
+ docs/internal/model.md#cascade-layers. */
12
+ @layer theme, base, components, utilities;
13
+
14
+ @layer components {
15
+ .neobrutalism {
16
+ /* Read with a fallback, not declared, so an ancestor (`:root`, where an app
17
+ themes) can set `--neo-offset`; the private carries the resolved value to
18
+ the six places below. See model.md#rules-for-aesthetics (R8). */
19
+ --_neo-offset: var(--neo-offset, 4px);
20
+
21
+ /* Pure black reads as ink on a light page and disappears on a dark one, so the
22
+ ink follows the theme rather than the palette. A thick border in the neutral
23
+ border grey is a thick grey box, not a brutalist one. */
24
+ --ui-ink: light-dark(var(--color-neutral-950), var(--color-neutral-50));
25
+
26
+ --ui-corner: 0;
27
+ --ui-corner-surface: 0;
28
+ --ui-border-width: 2px;
29
+ --ui-border-max: 100px;
30
+ --ui-line-style: initial;
31
+ --ui-rule-style: initial;
32
+
33
+ /* Hard, unblurred, and offset. Scaled by each component's own elevation, so
34
+ the registry decides which components sit on a slab and `.flat` takes one
35
+ off without this file knowing which component it landed on. */
36
+ /* The slab is ink, not shade: a hard offset of the element's own line colour
37
+ is the whole look, so this aesthetic asks for the intent instead of the
38
+ neutral depth colour every other shadow uses. */
39
+ --ui-shadow-ink: 100%;
40
+ --ui-shadow-x: var(--_neo-offset);
41
+ --ui-shadow-y: var(--_neo-offset);
42
+ --ui-shadow-blur: 0px;
43
+ --ui-shadow-spread: 0px;
44
+ /* No halo, cleared so a region nested inside another
45
+ aesthetic does not inherit one. */
46
+ --ui-halo-blur: initial;
47
+ --ui-halo-spread: initial;
48
+ --ui-halo-ink: initial;
49
+
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. */
55
+ --ui-active-shadow-x: 0px;
56
+ --ui-active-shadow-y: 0px;
57
+ --ui-active-transform: none;
58
+ --ui-active-translate-x: var(--_neo-offset);
59
+ --ui-active-translate-y: var(--_neo-offset);
60
+
61
+ /* Nothing here clips or blurs, and an aesthetic nested inside another has to
62
+ say so rather than inherit it. */
63
+ --ui-clip: none;
64
+ --ui-clip-tight: none;
65
+ /* Round corners, said explicitly, and the pill and chip corners cleared back
66
+ to `--ui-corner`: a region nested inside `.cyber` would otherwise inherit
67
+ its bevel, its diagonal, its diamond pills, and its square chips. */
68
+ --ui-corner-shape: round;
69
+ --ui-radius-pill: initial;
70
+ --ui-radius-tight: initial;
71
+ --ui-radius: initial;
72
+ --ui-radius-surface: initial;
73
+ --ui-corner-tl: initial;
74
+ --ui-corner-tr: initial;
75
+ --ui-corner-br: initial;
76
+ --ui-corner-bl: initial;
77
+ --ui-shadow-inset: ;
78
+ /* The slab is depth cast in ink, not this element's line, so an `.edgeless`
79
+ button keeps it. Cleared rather than omitted, because a neobrutalist region
80
+ nested inside a pixel one would otherwise inherit pixel's answer and lose the
81
+ slab on everything the edge axis rests at zero. */
82
+ --ui-shadow-edge: initial;
83
+ --ui-backdrop: none;
84
+ --ui-surface-ground: initial;
85
+ --ui-surface-shadow: initial;
86
+ --ui-bg-alpha: 1;
87
+ --ui-surface-image: initial;
88
+
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;
92
+
93
+ /* Chunky tile's heavier label, cleared so a region nested inside it keeps
94
+ the button's and the badge's own label. */
95
+ --ui-label-weight: initial;
96
+ --ui-label-tracking: initial;
97
+ --ui-label-case: initial;
98
+ --ui-label-shadow: initial;
99
+ }
53
100
  }
54
101
 
55
102
  /* The one R3 exception, recorded in the registry as `selectors`. The slab here
@@ -57,7 +104,60 @@
57
104
  registry rests flat -- looks unfinished without it. It writes the component's
58
105
  resting default rather than the modifier, so `.flat`/`.raised`/`.floating`
59
106
  still win on the element; a token would name the component through a longer
60
- route. `--_d-elevation` inheriting is inert -- it equals `box`'s own fallback. */
61
- .neobrutalism .alert {
62
- --_d-elevation: 1;
107
+ route. `--_d-elevation` inheriting is inert -- it equals `box`'s own fallback.
108
+
109
+ In `utilities`, not with the token class: it has to beat `@utility alert`'s
110
+ own `--_d-elevation: 0`, which it does there at 0-2-0, and it writes only a
111
+ private nothing a consumer sets. */
112
+ @layer utilities {
113
+ .neobrutalism .alert {
114
+ --_d-elevation: 1;
115
+ }
116
+ }
117
+
118
+ /* Solo: the same look painted onto the one element carrying the class, for an
119
+ element the package does not style. See docs/internal/model.md#solo-classes and
120
+ the note on `.glass-solo`: own knobs and foundation tokens with literal
121
+ fallbacks, never a shared `--ui-*`, `--intent-*`, or `--elevation-color`
122
+ token, unlayered, and not for this package's own components. */
123
+ .neobrutalism-solo {
124
+ --_neo-offset: var(--neo-offset, 4px);
125
+ --_neo-ink: light-dark(var(--color-neutral-950, oklch(14.5% 0 0)), var(--color-neutral-50, oklch(98.5% 0 0)));
126
+
127
+ border: 2px solid var(--_neo-ink);
128
+ border-radius: 0;
129
+ box-shadow: var(--_neo-offset) var(--_neo-offset) 0 0 var(--_neo-ink);
130
+ transition:
131
+ box-shadow var(--motion-duration-normal, 200ms) var(--motion-ease, cubic-bezier(0.2, 0, 0, 1)),
132
+ transform var(--motion-duration-normal, 200ms) var(--motion-ease, cubic-bezier(0.2, 0, 0, 1));
133
+ }
134
+
135
+ /* The press, only on something pressed: an action, not disabled. A solo
136
+ container -- a toast clicked to dismiss -- stays put, the way only `.btn` and
137
+ an opted-in card press under the aesthetic class. */
138
+ .neobrutalism-solo:is(
139
+ button,
140
+ a[href],
141
+ [role="button"],
142
+ summary,
143
+ input[type="button"],
144
+ input[type="submit"],
145
+ input[type="reset"]
146
+ ):active:not(:disabled, [aria-disabled="true"]) {
147
+ box-shadow: 0 0 0 0 var(--_neo-ink);
148
+ transform: translate(var(--_neo-offset), var(--_neo-offset));
149
+ }
150
+
151
+ @media (prefers-reduced-motion: reduce) {
152
+ .neobrutalism-solo:is(
153
+ button,
154
+ a[href],
155
+ [role="button"],
156
+ summary,
157
+ input[type="button"],
158
+ input[type="submit"],
159
+ input[type="reset"]
160
+ ):active:not(:disabled, [aria-disabled="true"]) {
161
+ transform: none;
162
+ }
63
163
  }