@codenhub/styles 0.4.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 (61) 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 -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 +144 -128
  15. package/dist/tw/aesthetics/cyber.css +138 -96
  16. package/dist/tw/aesthetics/glass.css +332 -66
  17. package/dist/tw/aesthetics/index.css +3 -1
  18. package/dist/tw/aesthetics/neobrutalism.css +113 -54
  19. package/dist/tw/aesthetics/pixel.css +137 -87
  20. package/dist/tw/aesthetics/sketch.css +333 -0
  21. package/dist/tw/box.css +139 -52
  22. package/dist/tw/components/button.css +67 -28
  23. package/dist/tw/components/feedback.css +85 -67
  24. package/dist/tw/components/form.css +300 -230
  25. package/dist/tw/components/index.css +17 -9
  26. package/dist/tw/components/loader.css +38 -30
  27. package/dist/tw/components/surface.css +89 -129
  28. package/dist/tw/components/tooltip.css +91 -47
  29. package/dist/tw/corner.css +26 -0
  30. package/dist/tw/elevation.css +19 -28
  31. package/dist/tw/index.css +15 -19
  32. package/dist/tw/intent.css +47 -32
  33. package/dist/tw/native.css +58 -42
  34. package/dist/tw/palette.css +455 -446
  35. package/dist/tw/presentation.css +20 -29
  36. package/dist/tw/reset.css +21 -28
  37. package/dist/tw/surface.css +61 -0
  38. package/dist/tw/theme.css +156 -153
  39. package/dist/tw/utilities/content.css +283 -205
  40. package/dist/tw/utilities/layout.css +17 -27
  41. package/docs/accessibility.md +63 -46
  42. package/docs/changelog/0.5.0.md +38 -0
  43. package/docs/changelog/0.6.0.md +52 -0
  44. package/docs/changelog/index.md +2 -0
  45. package/docs/concepts.md +6 -6
  46. package/docs/index.md +12 -3
  47. package/docs/integrating/nextjs.md +1 -1
  48. package/docs/integrating/tailwind.md +14 -6
  49. package/docs/setup.md +28 -8
  50. package/docs/usage/aesthetics.md +88 -18
  51. package/docs/usage/buttons.md +14 -14
  52. package/docs/usage/composing.md +12 -12
  53. package/docs/usage/content-and-layout.md +25 -24
  54. package/docs/usage/customizing.md +92 -55
  55. package/docs/usage/feedback.md +5 -3
  56. package/docs/usage/forms.md +61 -45
  57. package/docs/usage/theming.md +41 -42
  58. package/docs/usage/tooltips.md +37 -17
  59. package/llms-full.txt +590 -304
  60. package/llms.txt +6 -0
  61. package/package.json +15 -5
@@ -1,113 +1,155 @@
1
1
  /* Cyber: bevelled corners, a thin bright edge, and a glow in the component's own
2
- colour. See docs/internal/cyber-aesthetic.md for the decision.
2
+ colour. See docs/internal/model.md#cyber for the decision.
3
3
 
4
4
  Tier 1 only. The bevel is `corner-shape`, not `clip-path`: a clip removes
5
5
  every outer shadow, so a clipped chamfer has nothing left to glow, and an
6
6
  inset ring cannot follow a diagonal cut the way `.pixel`'s follows its square
7
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
- .cyber {
10
- /* Knobs, read with a fallback so an ancestor can set them (R8; see
11
- model.md#rules-for-aesthetics). One cut size for controls and surfaces
12
- alike, so a button and the card around it read as the same material; the
13
- two shapes then place that cut on opposite diagonals, so they read as
14
- different things. A shape knob takes any `border-radius` value -- the
15
- documented presets are a hexagon, a parallelogram, and a single notch --
16
- and the cut knob scales the default pair without restating it.
17
-
18
- The cut is a ceiling, not a size: the default pair caps it at a quarter of
19
- the element's own box, so a 40px button or a card takes the full cut and a
20
- 19px icon button about 5px rather than having its corners eaten. Nothing in
21
- the model scales a corner with a component's size -- `.sm` and `.dense`
22
- change padding and height, never the radius -- and a radius percentage is
23
- the one length that already knows the box. Horizontal percentages resolve
24
- against the width and vertical ones against the height, so on a small wide
25
- button the cut runs slightly shallower than 45 degrees; at those sizes that
26
- is not visible. A shape set through the knob is taken as written. */
27
- --_cyber-cut: var(--cyber-cut, 0.625rem);
28
- --_cyber-glow: var(--cyber-glow, 8px);
29
- --_cyber-shape: var(--cyber-shape, min(var(--_cyber-cut), 25%) 0 / min(var(--_cyber-cut), 25%) 0);
30
- --_cyber-shape-surface: var(--cyber-shape-surface, 0 min(var(--_cyber-cut), 25%) / 0 min(var(--_cyber-cut), 25%));
31
-
32
- /* The neutral line. A neon hue is the look's signature, but hue is intent's
33
- (R1), so the default follows the theme the way neobrutalism's and pixel's
34
- ink does, and `--cyber-ink` lets an application choose a neon neutral
35
- without the aesthetic imposing one on every palette. A named intent keeps
36
- its own edge colour. */
37
- --ui-ink: var(--cyber-ink, light-dark(var(--color-neutral-950), var(--color-neutral-50)));
38
-
39
- /* Controls cut top-left and bottom-right, surfaces top-right and bottom-left.
40
- Anything fully round -- a badge, a switch, the progress track, the tooltip
41
- icon, and through their own full radius a radio and a `.btn.pill` -- cuts
42
- all the way to points: a diamond, or a hexagon on something wider than it
43
- is tall. Chips square outright, which is what tells the checkbox from the
44
- radio beside it. */
45
- --ui-corner-shape: bevel;
46
- --ui-radius: var(--_cyber-shape);
47
- --ui-radius-surface: var(--_cyber-shape-surface);
48
- --ui-radius-pill: calc(infinity * 1px);
49
- --ui-radius-tight: 0;
50
- --ui-border-width: 1px;
51
- --ui-border-max: 100px;
52
-
53
- /* The glow: the component's own ink, blurred, with no offset. `box` mixes
54
- depth as `color-mix(in oklab, var(--intent-border) var(--ui-shadow-ink),
55
- var(--elevation-color))`, so a transparent depth colour turns 40% ink into
56
- the intent colour at 40% alpha rather than into a darker shade of it.
57
- Elevation scales the blur like every shadow, so the glow lights what rests
58
- above zero -- buttons and cards -- and anything raised, and leaves fields
59
- and chips crisp. */
60
- --elevation-color: transparent;
61
- --ui-shadow-ink: 40%;
62
- --ui-shadow-x: 0px;
63
- --ui-shadow-y: 0px;
64
- --ui-shadow-blur: var(--_cyber-glow);
65
- --ui-shadow-spread: 0px;
66
-
67
- /* The glow holds still under the pointer and under a press; the press is the
68
- base scale, restated because a region nested inside another aesthetic must
69
- not inherit that one's travel. Reduced motion drops it below. */
70
- --ui-hover-transform: none;
71
- --ui-active-shadow-x: 0px;
72
- --ui-active-shadow-y: 0px;
73
- --ui-active-transform: scale(0.97);
74
-
75
- /* Nothing here clips, blurs, thins its fill, or draws its edge as a ring, and
76
- an aesthetic nested inside one that does has to say so rather than inherit
77
- it. */
78
- --ui-clip: none;
79
- --ui-clip-tight: none;
80
- --ui-shadow-inset: ;
81
- --ui-shadow-edge: initial;
82
- --ui-backdrop: none;
83
- --ui-bg-alpha: 1;
84
-
85
- /* Consumer-supplied, the way `.pixel` reads `--font-pixel`. The package ships
86
- no font binary; the monospace stack is the closest technical face every
87
- platform already has. */
88
- font-family: var(--font-cyber, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
89
- }
8
+ focus outline all follow it. */
90
9
 
91
- /* Firefox and Safari do not draw `corner-shape` yet, and there the radius would
92
- round the corners instead. A rounded cyber element reads as a different
93
- aesthetic; a square one reads as the same aesthetic simplified. What is fully
94
- round stays round rather than square: a square radio is a checkbox, and a
95
- pill is the nearest a diamond gets without a bevel. */
96
- @supports not (corner-shape: bevel) {
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 {
97
18
  .cyber {
98
- --ui-radius: 0;
99
- --ui-radius-surface: 0;
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. A
30
+ shape set through a knob is taken as written, unscaled: it is read as
31
+ `--ui-radius`, which `box` honours over the computed corner, and left
32
+ unset the reference makes the token guaranteed-invalid so the computed
33
+ corner draws. */
34
+ --_cyber-cut: var(--cyber-cut, 0.625rem);
35
+ --_cyber-glow: var(--cyber-glow, 8px);
36
+
37
+ /* The neutral line. A neon hue is the look's signature, but hue is intent's
38
+ (R1), so the default follows the theme the way neobrutalism's and pixel's
39
+ ink does, and `--cyber-ink` lets an application choose a neon neutral
40
+ without the aesthetic imposing one on every palette. A named intent keeps
41
+ its own edge colour. */
42
+ --ui-ink: var(--cyber-ink, light-dark(var(--color-neutral-950), var(--color-neutral-50)));
43
+
44
+ /* Controls cut top-left and bottom-right, through the four corner switches
45
+ every component reads; surfaces cut top-right and bottom-left, which the
46
+ switches cannot say at the same time, so their shape is written whole and
47
+ does not scale -- a card is large enough that the full cut is right at
48
+ every padding. Anything fully round -- a badge, a switch, the progress
49
+ track, the tooltip icon, and through their own full radius a radio and a
50
+ `.btn.pill` -- cuts all the way to points: a diamond, or a hexagon on
51
+ something wider than it is tall. Chips square outright, which is what
52
+ tells the checkbox from the radio beside it. */
53
+ --ui-corner-shape: bevel;
54
+ --ui-corner: var(--_cyber-cut);
55
+ --ui-corner-surface: var(--_cyber-cut);
56
+ --ui-corner-tl: 1;
57
+ --ui-corner-tr: 0;
58
+ --ui-corner-br: 1;
59
+ --ui-corner-bl: 0;
60
+ --ui-radius: var(--cyber-shape);
61
+ --ui-radius-surface: var(--cyber-shape-surface, 0 var(--_cyber-cut) / 0 var(--_cyber-cut));
62
+ --ui-radius-pill: calc(infinity * 1px);
63
+ --ui-radius-tight: 0;
64
+ --ui-border-width: 1px;
65
+ --ui-border-max: 100px;
66
+ --ui-line-style: initial;
67
+ --ui-rule-style: initial;
68
+
69
+ /* The glow is light, not depth, so it is the halo: the component's own intent
70
+ colour at 40%, blurred, with no offset. Elevation does not scale it, so
71
+ every component glows -- fields and chips included -- and `.flat` does
72
+ not put it out. There is no depth, and the transparent depth colour keeps
73
+ `.raised` and `.floating` from drawing any. */
74
+ --elevation-color: transparent;
75
+ --ui-shadow-ink: 0%;
76
+ --ui-shadow-x: 0px;
77
+ --ui-shadow-y: 0px;
78
+ --ui-shadow-blur: 0px;
79
+ --ui-shadow-spread: 0px;
80
+ --ui-halo-blur: var(--_cyber-glow);
81
+ --ui-halo-spread: 0px;
82
+ --ui-halo-ink: 40%;
83
+
84
+ /* The glow holds still under the pointer and under a press; the press is the
85
+ base scale, restated because a region nested inside another aesthetic must
86
+ not inherit that one's travel. Reduced motion drops it below. */
87
+ --ui-hover-transform: none;
88
+ --ui-active-shadow-x: 0px;
89
+ --ui-active-shadow-y: 0px;
90
+ --ui-active-transform: scale(0.97);
91
+ --ui-active-translate-x: initial;
92
+ --ui-active-translate-y: initial;
93
+
94
+ /* Nothing here clips, blurs, thins its fill, or draws its edge as a ring, and
95
+ an aesthetic nested inside one that does has to say so rather than inherit
96
+ it. */
97
+ --ui-clip: none;
98
+ --ui-clip-tight: none;
99
+ --ui-shadow-inset: ;
100
+ --ui-shadow-edge: initial;
101
+ /* The bevel is a corner shape, not a clip, so the global focus outline
102
+ follows it; pixel's inset focus layer is cleared rather than inherited. */
103
+ --ui-focus-inset: initial;
104
+ --ui-backdrop: none;
105
+ --ui-surface-ground: initial;
106
+ --ui-surface-shadow: initial;
107
+ --ui-surface-image: initial;
108
+ --ui-bg-alpha: 1;
109
+
110
+ /* Consumer-supplied, the way `.pixel` reads `--font-pixel`. The package ships
111
+ no font binary; the monospace stack is the closest technical face every
112
+ platform already has. */
113
+ font-family: var(--font-cyber, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
114
+
115
+ /* A control's neutral line is the theme's control border, not this
116
+ aesthetic's ink: that ink is the primary colour in both themes, so every
117
+ unchecked toggle and resting field drew its boundary as loudly as a
118
+ primary one. Named rather than cleared, so a region nested inside glass
119
+ or chunky tile does not inherit theirs either. */
120
+ --ui-control-ink: var(--color-control-border);
121
+ --ui-rule-ink: initial;
122
+
123
+ /* Chunky tile's heavier label, cleared so a region nested inside it keeps
124
+ the button's and the badge's own label. */
125
+ --ui-label-weight: initial;
126
+ --ui-label-tracking: initial;
127
+ --ui-label-case: initial;
128
+ --ui-label-shadow: initial;
100
129
  }
101
- }
102
130
 
103
- @media (prefers-reduced-motion: reduce) {
104
- .cyber {
105
- --ui-active-transform: none;
131
+ /* Firefox and Safari do not draw `corner-shape` yet, and there the radius would
132
+ round the corners instead. A rounded cyber element reads as a different
133
+ aesthetic; a square one reads as the same aesthetic simplified. What is fully
134
+ round stays round rather than square: a square radio is a checkbox, and a
135
+ pill is the nearest a diamond gets without a bevel. */
136
+ @supports not (corner-shape: bevel) {
137
+ .cyber {
138
+ --ui-corner: 0;
139
+ --ui-radius: 0;
140
+ --ui-radius-surface: 0;
141
+ }
142
+ }
143
+
144
+ @media (prefers-reduced-motion: reduce) {
145
+ .cyber {
146
+ --ui-active-transform: none;
147
+ }
106
148
  }
107
149
  }
108
150
 
109
151
  /* Solo: the same look painted onto the one element carrying the class, for an
110
- element the package does not style. See docs/internal/solo-utilities.md and
152
+ element the package does not style. See docs/internal/model.md#solo-classes and
111
153
  the note on `.glass-solo`: own knobs and foundation tokens with literal
112
154
  fallbacks, never a shared `--ui-*`, `--intent-*`, or `--elevation-color`
113
155
  token, unlayered, and not for this package's own components. */
@@ -1,80 +1,290 @@
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.
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. */
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
-
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));
24
-
25
- --ui-bg-alpha: 0.8;
26
- --ui-backdrop: blur(14px) saturate(1.6);
27
-
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);
41
-
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);
48
-
49
- --ui-clip: none;
50
- --ui-clip-tight: none;
51
- /* Round corners, said explicitly, and the pill and chip corners cleared back
52
- to `--ui-radius`: a region nested inside `.cyber` would otherwise inherit
53
- its bevel, its diamond pills, and its square chips. */
54
- --ui-corner-shape: round;
55
- --ui-radius-pill: initial;
56
- --ui-radius-tight: initial;
57
- /* Nothing here draws its edge as a ring, and an aesthetic nested inside pixel
58
- has to say so rather than inherit it. */
59
- --ui-shadow-edge: initial;
60
- --ui-hover-transform: none;
61
- --ui-active-transform: none;
62
- }
2
+ edge. It needs something behind it to blur -- on a flat page background it is
3
+ a translucent panel and nothing more.
63
4
 
64
- /* Transparency is the whole aesthetic, so the honest degradation is an opaque
65
- surface rather than a softer blur. */
66
- @media (prefers-reduced-transparency: reduce) {
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. */
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 {
67
18
  .glass {
19
+ /* Knobs read with a fallback so an ancestor can set them -- declared here they
20
+ outrank `:root` (R8; see model.md#rules-for-aesthetics). Glass is softer
21
+ than the base geometry because a translucent tight corner reads as a
22
+ cut-out; a control/surface pair rather than one value, because they do not
23
+ scale together and 12/16 keeps a button looking at home inside a card. */
24
+ --ui-corner: var(--glass-radius, 0.75rem);
25
+ --ui-corner-surface: var(--glass-radius-surface, 1rem);
26
+ --ui-border-width: 1px;
27
+ --ui-border-max: 100px;
28
+ --ui-line-style: initial;
29
+ --ui-rule-style: initial;
30
+
31
+ /* Glass catches light from above whatever is under it, so the edge is a light
32
+ hairline in both themes rather than a theme-following ink. */
33
+ --ui-ink: light-dark(rgb(255 255 255 / 0.65), rgb(255 255 255 / 0.22));
34
+ /* A control's boundary is not a pane's edge. The hairline erases an
35
+ unchecked checkbox on a light page (1.33:1 inside a glass card) and leaves
36
+ a field with nothing marking where typing goes, so controls take the
37
+ theme's ink at 55% instead: translucent, so it tints whatever is behind
38
+ the pane rather than drawing a flat grey, and 6.31:1 on a checkbox, 2.31:1
39
+ on a field's 60% resting line, in light. See
40
+ docs/internal/model.md#presentation. */
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));
47
+
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. */
68
54
  --ui-bg-alpha: 1;
55
+ --ui-backdrop: blur(14px) saturate(1.6);
56
+ --ui-surface-image: initial;
57
+
58
+ /* R6. Controls carry no shadow of their own -- the depth in this aesthetic is
59
+ the blur, and a drop shadow under a translucent button reads as grime. All
60
+ general parts are explicitly zero so inherited or future geometry cannot
61
+ leak onto controls. `--ui-surface-shadow` below replaces the shadow of
62
+ surfaces only. */
63
+ --ui-shadow-x: 0px;
64
+ --ui-shadow-y: 0px;
65
+ --ui-shadow-blur: 0px;
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));
76
+ /* No halo, cleared so a region nested inside another
77
+ aesthetic does not inherit one. */
78
+ --ui-halo-blur: initial;
79
+ --ui-halo-spread: initial;
80
+ --ui-halo-ink: initial;
81
+ /* `--ui-bg-alpha` multiplies the fill, and a surface's default fill is 0%:
82
+ without a translucent ground the blur ends up behind an opaque background
83
+ and nothing shows through. */
84
+ --ui-surface-ground: color-mix(in srgb, var(--color-background) 45%, transparent);
85
+
86
+ /* Two layers pulling in with negative spread, so the shadow tucks under the
87
+ surface instead of haloing onto the backdrop. It is the one shipped aesthetic
88
+ that cannot be described with the parts, so it takes `surface`'s
89
+ complete-value slot -- which is also what keeps it off buttons and chips --
90
+ and the registry records that `.flat` does not reach it. */
91
+ --ui-surface-shadow: 0 1px 2px -1px var(--elevation-color), 0 18px 40px -16px var(--elevation-color);
92
+
93
+ --ui-clip: none;
94
+ --ui-clip-tight: none;
95
+ /* Round corners, said explicitly, and the pill and chip corners cleared back
96
+ to `--ui-corner`: a region nested inside `.cyber` would otherwise inherit
97
+ its bevel, its diagonal, its diamond pills, and its square chips. */
98
+ --ui-corner-shape: round;
99
+ --ui-radius-pill: initial;
100
+ --ui-radius-tight: initial;
101
+ --ui-radius: initial;
102
+ --ui-radius-surface: initial;
103
+ --ui-corner-tl: initial;
104
+ --ui-corner-tr: initial;
105
+ --ui-corner-br: initial;
106
+ --ui-corner-bl: initial;
107
+ /* Nothing here draws its edge as a ring, and an aesthetic nested inside pixel
108
+ has to say so rather than inherit it. */
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;
113
+ --ui-hover-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);
118
+ --ui-active-translate-x: initial;
119
+ --ui-active-translate-y: initial;
120
+
121
+ /* Chunky tile's heavier label, cleared so a region nested inside it keeps
122
+ the button's and the badge's own label. */
123
+ --ui-label-weight: initial;
124
+ --ui-label-tracking: initial;
125
+ --ui-label-case: initial;
126
+ --ui-label-shadow: initial;
127
+ }
128
+
129
+ /* Transparency is the whole aesthetic, so the honest degradation is an opaque
130
+ surface rather than a softer blur. */
131
+ @media (prefers-reduced-transparency: reduce) {
132
+ .glass {
133
+ --ui-backdrop: none;
134
+ --ui-surface-ground: var(--color-background);
135
+ }
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;
69
204
  --ui-backdrop: none;
70
- --ui-surface-ground: var(--color-background);
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;
71
281
  }
72
282
  }
73
283
 
74
284
  /* Solo: the same look painted onto the one element carrying the class, for an
75
285
  element the package does not style -- a toast from another library, a plain
76
286
  `<div>` on a page that never loaded the base stylesheet. See
77
- docs/internal/solo-utilities.md.
287
+ docs/internal/model.md#solo-classes.
78
288
 
79
289
  It reads the aesthetic's own knobs and the theme's foundation tokens, each
80
290
  with its shipped literal as the fallback, and never a `--ui-*`, `--intent-*`,
@@ -110,3 +320,59 @@
110
320
  backdrop-filter: none;
111
321
  }
112
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
+ }