@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,8 +1,20 @@
1
+ /* The layer map, stated before any import uses a layer -- see `theme.css`.
2
+ Tailwind's utilities are imported into `utilities` the way
3
+ `tailwindcss/index.css` does it, so every component lands there on this
4
+ entrypoint and on `/native` too, exactly where `.` already puts them. Without
5
+ `layer(utilities)` the CLI emitted them unlayered here, and they beat every
6
+ consumer rule on these two entries alone. */
7
+ @layer theme, base, components, utilities;
8
+
9
+ /* The theme before `@reference "tailwindcss"`, as `native.css` has it:
10
+ Tailwind writes the theme's variables at the first `@theme` it meets, and
11
+ after the reference that is Tailwind's own reference-only one. See
12
+ docs/internal/model.md (Which files import the theme). */
13
+ @import "../theme.css";
1
14
  @reference "tailwindcss";
2
- @import "tailwindcss/utilities.css";
15
+ @import "tailwindcss/utilities.css" layer(utilities);
3
16
  /* Everything that is not `src/` -- see the note in `index.css`. Keeps a class
4
17
  name in a docs code fence from shipping as a real rule in `dist/components.css`. */
5
- @import "../theme.css";
6
18
  @import "../typography.css";
7
19
  @import "../box.css";
8
20
  @import "./button.css";
@@ -17,12 +29,8 @@
17
29
  the content scan will not emit on its own. `box` and friends appear only as
18
30
  `@utility` blocks and `@apply` targets; `text-control`, `surface` and
19
31
  `input-group` appear only inside `:is()`/`:where()` argument lists in
20
- `intent.css` and `form.css`, never as a selector of their own. Their standalone
21
- rules shipped only because docs prose happened to name them, and excluding
22
- `docs/` from the scan removed that -- `.input-group` (and, with it, its focus
23
- ring, its flush inner control, and `:has()`-propagated invalid state) went
24
- silently dark, caught only by the browser suite. Safelisting all seven here
25
- makes the emission deliberate; the composition-body check in
32
+ `intent.css` and `form.css`, never as a selector of their own. Safelisting
33
+ all seven makes the emission deliberate; the composition-body check in
26
34
  `exports.test.ts` fails the build if one is dropped. `text-control` and
27
35
  `surface` are also documented as consumer-usable on their own. */
28
- @source inline("box box-hover box-active loader-mask text-control surface input-group text-display text-title-lg text-title text-title-sm text-label-lg text-label text-body btn field label hint ipt email password url tel search date datetime month week time icon left right textarea select checkbox radio switch alert alert-icon badge skeleton progress tooltip tooltip-bubble tooltip-icon card panel interactive flush compact spacious neutral solid soft ghost edged edgeless flat raised floating loader");
36
+ @source inline("box box-hover box-active loader-mask text-control surface input-group unadorned text-display text-title-lg text-title text-title-sm text-label-lg text-label text-body btn field label hint ipt email password url tel search date datetime month week time icon left right textarea select checkbox radio switch alert alert-icon badge skeleton progress tooltip tooltip-bubble tooltip-icon card panel interactive flush neutral solid soft ghost edged edgeless flat raised floating loader");
@@ -1,10 +1,15 @@
1
- @reference "tailwindcss";
2
1
  @source inline("loader dots-wave dots-fade dots-queue dots-rotate dots-grow dots-grow-alternate dot-bounce bars-wave pulse-ring");
3
2
 
4
3
  /* The mask machinery, shared by the loader and by any component that shows a
5
4
  spinner of its own. Artwork is a mask rather than an image so it takes its
6
5
  colour from whatever paints behind it, which is the whole indicator contract:
7
- an intent class recolours the loader and nothing else has to know. */
6
+ an intent class recolours the loader and nothing else has to know.
7
+
8
+ Each artwork's closing `style` tag is percent-encoded. A stylesheet pasted
9
+ into an HTML `<style>` element ends at the first literal close tag, so one
10
+ left raw would silently drop every rule after it; the data URI decodes the
11
+ escape before the SVG is parsed, so the artwork is unchanged. The exports
12
+ test fails a build that ships the raw sequence. */
8
13
  @utility loader-mask {
9
14
  mask-image: var(--loader-art);
10
15
  -webkit-mask-image: var(--loader-art);
@@ -16,63 +21,66 @@
16
21
  -webkit-mask-position: center;
17
22
  }
18
23
 
19
- .loader:not(
20
- :where(
21
- .dots-wave,
22
- .dots-fade,
23
- .dots-queue,
24
- .dots-rotate,
25
- .dots-grow,
26
- .dots-grow-alternate,
27
- .dot-bounce,
28
- .bars-wave,
29
- .pulse-ring
30
- )
31
- ) {
32
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.spinner_V8m1{transform-origin:center;animation:spinner_zKoa 2s linear infinite}.spinner_V8m1 circle{stroke-linecap:round;animation:spinner_YpZS 1.5s ease-in-out infinite}@keyframes spinner_zKoa{100%{transform:rotate(360deg)}}@keyframes spinner_YpZS{0%{stroke-dasharray:0 150;stroke-dashoffset:0}47.5%{stroke-dasharray:42 150;stroke-dashoffset:-16}95%,100%{stroke-dasharray:42 150;stroke-dashoffset:-59}}</style><g class='spinner_V8m1'><circle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'></circle></g></svg>");
24
+ /* In `utilities`, beside the loader and art utilities it defaults for -- see
25
+ the seams note in `form.css`. */
26
+ @layer utilities {
27
+ .loader:not(
28
+ :where(
29
+ .dots-wave,
30
+ .dots-fade,
31
+ .dots-queue,
32
+ .dots-rotate,
33
+ .dots-grow,
34
+ .dots-grow-alternate,
35
+ .dot-bounce,
36
+ .bars-wave,
37
+ .pulse-ring
38
+ )
39
+ ) {
40
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.spinner_V8m1{transform-origin:center;animation:spinner_zKoa 2s linear infinite}.spinner_V8m1 circle{stroke-linecap:round;animation:spinner_YpZS 1.5s ease-in-out infinite}@keyframes spinner_zKoa{100%{transform:rotate(360deg)}}@keyframes spinner_YpZS{0%{stroke-dasharray:0 150;stroke-dashoffset:0}47.5%{stroke-dasharray:42 150;stroke-dashoffset:-16}95%,100%{stroke-dasharray:42 150;stroke-dashoffset:-59}}%3C/style%3E<g class='spinner_V8m1'><circle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'></circle></g></svg>");
41
+ }
33
42
  }
34
43
 
35
44
  @utility dots-wave {
36
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg fill='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_8HQG{0%,57.14%{animation-timing-function:cubic-bezier(.33,.66,.66,1);transform:translate(0)}28.57%{animation-timing-function:cubic-bezier(.33,0,.66,.33);transform:translateY(-6px)}to{transform:translate(0)}}.spinner_qM83{animation:spinner_8HQG 1.05s infinite}</style><circle class='spinner_qM83' cx='4' cy='12' r='3'/><circle class='spinner_qM83' cx='12' cy='12' r='3' style='animation-delay:.1s'/><circle class='spinner_qM83' cx='20' cy='12' r='3' style='animation-delay:.2s'/></svg>");
45
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg fill='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_8HQG{0%,57.14%{animation-timing-function:cubic-bezier(.33,.66,.66,1);transform:translate(0)}28.57%{animation-timing-function:cubic-bezier(.33,0,.66,.33);transform:translateY(-6px)}to{transform:translate(0)}}.spinner_qM83{animation:spinner_8HQG 1.05s infinite}%3C/style%3E<circle class='spinner_qM83' cx='4' cy='12' r='3'/><circle class='spinner_qM83' cx='12' cy='12' r='3' style='animation-delay:.1s'/><circle class='spinner_qM83' cx='20' cy='12' r='3' style='animation-delay:.2s'/></svg>");
37
46
  }
38
47
 
39
48
  @utility dots-fade {
40
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.dot{animation:dot-anim 1s ease-in-out infinite;fill:currentColor}.d1{animation-delay:-0.32s}.d2{animation-delay:-0.16s}@keyframes dot-anim{0%,80%,100%{transform:scale(0.6);opacity:0.2}40%{transform:scale(1);opacity:1}}</style><circle class='dot d1' cx='4' cy='12' r='3' transform-origin='4px 12px'></circle><circle class='dot d2' cx='12' cy='12' r='3' transform-origin='12px 12px'></circle><circle class='dot d3' cx='20' cy='12' r='3' transform-origin='20px 12px'></circle></svg>");
49
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.dot{animation:dot-anim 1s ease-in-out infinite;fill:currentColor}.d1{animation-delay:-0.32s}.d2{animation-delay:-0.16s}@keyframes dot-anim{0%,80%,100%{transform:scale(0.6);opacity:0.2}40%{transform:scale(1);opacity:1}}%3C/style%3E<circle class='dot d1' cx='4' cy='12' r='3' transform-origin='4px 12px'></circle><circle class='dot d2' cx='12' cy='12' r='3' transform-origin='12px 12px'></circle><circle class='dot d3' cx='20' cy='12' r='3' transform-origin='20px 12px'></circle></svg>");
41
50
  }
42
51
 
43
52
  @utility dots-queue {
44
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_qtyZ{0%{r:0}25%{r:3px;cx:4px}50%{r:3px;cx:12px}75%{r:3px;cx:20px}to{r:0;cx:20px}}.spinner_nOfF{animation:spinner_qtyZ 2s cubic-bezier(.36,.6,.31,1) infinite}</style><circle class='spinner_nOfF' cx='4' cy='12' r='3'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-.5s'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-1s'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-1.5s'/></svg>");
53
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_qtyZ{0%{r:0}25%{r:3px;cx:4px}50%{r:3px;cx:12px}75%{r:3px;cx:20px}to{r:0;cx:20px}}.spinner_nOfF{animation:spinner_qtyZ 2s cubic-bezier(.36,.6,.31,1) infinite}%3C/style%3E<circle class='spinner_nOfF' cx='4' cy='12' r='3'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-.5s'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-1s'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-1.5s'/></svg>");
45
54
  }
46
55
 
47
56
  @utility dots-rotate {
48
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_XVY9{50%{transform:rotate(180deg)}to{transform:rotate(360deg)}}</style><circle cx='12' cy='12' r='3'/><g style='transform-origin:center;animation:spinner_XVY9 1s cubic-bezier(.36,.6,.31,1) infinite'><circle cx='4' cy='12' r='3'/><circle cx='20' cy='12' r='3'/></g></svg>");
57
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_XVY9{50%{transform:rotate(180deg)}to{transform:rotate(360deg)}}%3C/style%3E<circle cx='12' cy='12' r='3'/><g style='transform-origin:center;animation:spinner_XVY9 1s cubic-bezier(.36,.6,.31,1) infinite'><circle cx='4' cy='12' r='3'/><circle cx='20' cy='12' r='3'/></g></svg>");
49
58
  }
50
59
 
51
60
  @utility dots-grow {
52
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_xe7Q{93.75%,to{r:3px}46.875%{r:.2px}}.spinner_b2T7{animation:spinner_xe7Q .8s linear infinite}</style><circle class='spinner_b2T7' cx='4' cy='12' r='3'/><circle class='spinner_b2T7' cx='12' cy='12' r='3' style='animation-delay:-.65s'/><circle class='spinner_b2T7' cx='20' cy='12' r='3' style='animation-delay:-.5s'/></svg>");
61
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_xe7Q{93.75%,to{r:3px}46.875%{r:.2px}}.spinner_b2T7{animation:spinner_xe7Q .8s linear infinite}%3C/style%3E<circle class='spinner_b2T7' cx='4' cy='12' r='3'/><circle class='spinner_b2T7' cx='12' cy='12' r='3' style='animation-delay:-.65s'/><circle class='spinner_b2T7' cx='20' cy='12' r='3' style='animation-delay:-.5s'/></svg>");
53
62
  }
54
63
 
55
64
  @utility dots-grow-alternate {
56
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_qhi1{0%,to{r:1.5px}50%{r:3px}}.spinner_I8Q1{animation:spinner_qhi1 .75s linear infinite}</style><circle class='spinner_I8Q1' cx='4' cy='12' r='1.5'/><circle class='spinner_I8Q1' cx='12' cy='12' r='3' style='animation-delay:-.375s'/><circle class='spinner_I8Q1' cx='20' cy='12' r='1.5'/></svg>");
65
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_qhi1{0%,to{r:1.5px}50%{r:3px}}.spinner_I8Q1{animation:spinner_qhi1 .75s linear infinite}%3C/style%3E<circle class='spinner_I8Q1' cx='4' cy='12' r='1.5'/><circle class='spinner_I8Q1' cx='12' cy='12' r='3' style='animation-delay:-.375s'/><circle class='spinner_I8Q1' cx='20' cy='12' r='1.5'/></svg>");
57
66
  }
58
67
 
59
68
  @utility dot-bounce {
60
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_YeBj{0%{animation-timing-function:cubic-bezier(.33,0,.66,.33);cy:5px}46.875%{cy:20px;rx:4px;ry:4px}50%{animation-timing-function:cubic-bezier(.33,.66,.66,1);cy:20.5px;rx:4.8px;ry:3px}53.125%{rx:4px;ry:4px}to{cy:5px}}</style><circle cx='12' cy='5' style='animation:spinner_YeBj .8s infinite' r='4'/></svg>");
69
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_YeBj{0%{animation-timing-function:cubic-bezier(.33,0,.66,.33);cy:5px}46.875%{cy:20px;rx:4px;ry:4px}50%{animation-timing-function:cubic-bezier(.33,.66,.66,1);cy:20.5px;rx:4.8px;ry:3px}53.125%{rx:4px;ry:4px}to{cy:5px}}%3C/style%3E<circle cx='12' cy='5' style='animation:spinner_YeBj .8s infinite' r='4'/></svg>");
61
70
  }
62
71
 
63
72
  @utility bars-wave {
64
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.bar{animation:bar-anim 1s ease-in-out infinite;fill:currentColor}.b1{animation-delay:-0.4s}.b2{animation-delay:-0.2s}@keyframes bar-anim{0%,100%{transform:scaleY(0.3)}50%{transform:scaleY(1)}}</style><rect class='bar b1' x='3' y='2' width='4' height='20' rx='2' transform-origin='5px 12px'></rect><rect class='bar b2' x='10' y='2' width='4' height='20' rx='2' transform-origin='12px 12px'></rect><rect class='bar b3' x='17' y='2' width='4' height='20' rx='2' transform-origin='19px 12px'></rect></svg>");
73
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.bar{animation:bar-anim 1s ease-in-out infinite;fill:currentColor}.b1{animation-delay:-0.4s}.b2{animation-delay:-0.2s}@keyframes bar-anim{0%,100%{transform:scaleY(0.3)}50%{transform:scaleY(1)}}%3C/style%3E<rect class='bar b1' x='3' y='2' width='4' height='20' rx='2' transform-origin='5px 12px'></rect><rect class='bar b2' x='10' y='2' width='4' height='20' rx='2' transform-origin='12px 12px'></rect><rect class='bar b3' x='17' y='2' width='4' height='20' rx='2' transform-origin='19px 12px'></rect></svg>");
65
74
  }
66
75
 
67
76
  @utility pulse-ring {
68
- --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.pulse{animation:pulse-anim 1.2s cubic-bezier(0, 0.2, 0.8, 1) infinite;stroke:currentColor;transform-origin:center}.p2{animation-delay:-0.6s}@keyframes pulse-anim{0%{transform:scale(0);opacity:1}100%{transform:scale(1);opacity:0}}</style><circle class='pulse p1' cx='12' cy='12' r='10' fill='none' stroke-width='3'></circle><circle class='pulse p2' cx='12' cy='12' r='10' fill='none' stroke-width='3'></circle></svg>");
77
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.pulse{animation:pulse-anim 1.2s cubic-bezier(0, 0.2, 0.8, 1) infinite;stroke:currentColor;transform-origin:center}.p2{animation-delay:-0.6s}@keyframes pulse-anim{0%{transform:scale(0);opacity:1}100%{transform:scale(1);opacity:0}}%3C/style%3E<circle class='pulse p1' cx='12' cy='12' r='10' fill='none' stroke-width='3'></circle><circle class='pulse p2' cx='12' cy='12' r='10' fill='none' stroke-width='3'></circle></svg>");
69
78
  }
70
79
 
71
- /* A loader takes a colour and its own geometry, and nothing else. No fill, no
72
- edge, no silhouette: what used to be a bordered box wrapped around a mask is
73
- now the mask. `--intent-color` defaults to `currentColor` for indicators, so
74
- a loader inside filled content matches the text around it and
75
- `.loader.success` is green. There is nothing else to know. */
80
+ /* A loader takes a colour and its own geometry, and nothing else: no fill, no
81
+ edge, no silhouette. `--intent-color` defaults to `currentColor` for
82
+ indicators, so a loader inside filled content matches the text around it and
83
+ `.loader.success` is green. */
76
84
  @utility loader {
77
85
  @apply loader-mask inline-block size-7 [&.lg]:size-9 [&.sm]:size-5;
78
86
 
@@ -1,39 +1,8 @@
1
1
  @reference "tailwindcss";
2
2
  @import "../theme.css";
3
+ @import "../surface.css";
3
4
  @reference "../box.css";
4
5
 
5
- /* What every surface shares. Two slots exist here and nowhere else, and that is
6
- how an aesthetic reaches surfaces without naming one: only a surface resolves
7
- `--ui-backdrop`, so glass blurs cards and panels while controls stay solid,
8
- and only a surface resolves `--ui-surface-shadow`, so a two-layer drop shadow
9
- does not land under every button on the page. */
10
- @utility surface {
11
- @apply box block;
12
-
13
- /* Bare and edged. `box` reads these three with no fallback, so `surface` --
14
- typeable on its own -- owes all three; `card` restates its own. No shadow
15
- geometry: depth is asked for (`.raised`/`.floating`, an aesthetic, the
16
- registry elevation) rather than carried, so a plain card is flat. */
17
- --_d-fill: 0%;
18
- --_d-fg-on-fill: 0%;
19
- --_d-border: 100%;
20
-
21
- --_d-ground: var(--ui-surface-ground, var(--color-background));
22
-
23
- /* The complete-shadow slot for a shadow that is not one layer (glass tucks two
24
- under with negative spread). Only `surface` reads it -- through `--ui-shadow`
25
- it would land an 18px drop under every button and chip -- like the backdrop. */
26
- --_shadow: var(--ui-surface-shadow, var(--_shadow-parts));
27
-
28
- border-radius: var(--ui-radius-surface, var(--radius-surface));
29
- /* Prefixed property first, deliberately: written second the minifier folds the
30
- pair into the prefixed one alone, and the standard property never reaches
31
- `dist/` -- which drops the blur in Firefox (the one engine with
32
- `backdrop-filter` and not the `-webkit-` alias). Measured on the built CSS. */
33
- -webkit-backdrop-filter: var(--ui-backdrop, none);
34
- backdrop-filter: var(--ui-backdrop, none);
35
- }
36
-
37
6
  /* A ghost, edged container that takes one unit when depth is supplied. */
38
7
  @utility card {
39
8
  @apply surface p-(--surface-padding,1.25rem);
@@ -46,71 +15,58 @@
46
15
  --_d-border: 100%;
47
16
  --_d-elevation: 1;
48
17
 
49
- /* `.p-xs`/`.p-sm`/`.p-lg` are the canonical names; `.dense`/`.compact`/`.spacious`
50
- are first-class aliases -- see the identical note in `button.css`. */
51
- &.p-xs,
52
- &.dense {
18
+ &.p-xs {
53
19
  --surface-padding: 0.5rem;
20
+ --_scale-pad: 0.5;
54
21
  }
55
22
 
56
- &.p-sm,
57
- &.compact {
23
+ &.p-sm {
58
24
  --surface-padding: 0.75rem;
25
+ --_scale-pad: 0.75;
59
26
  }
60
27
 
61
- &.p-lg,
62
- &.spacious {
28
+ &.p-lg {
63
29
  --surface-padding: 2rem;
30
+ --_scale-pad: 1.25;
64
31
  }
65
32
 
66
33
  &.flush {
67
34
  --surface-padding: 0;
68
35
  }
69
36
 
70
- /* `.soft` rests untinted for no named intent, rather than mixing its 12% --
71
- measured on a neutral card, 12% of `--color-text` onto any of
72
- `--color-background`, `--color-foreground`, or `--color-surface` lands
73
- within a few units of the others (a near-black ink dominates the result
74
- regardless of which near-white ground it is mixed onto), which is why
75
- swapping the ground token alone does not read as quieter. A resting
76
- no-intent `.card.soft` is instead `--color-foreground` with no ink mixed
77
- in at all -- a plain step up from the page that marks a different
78
- container without drawing attention to it, the way a card that is not
79
- currently interesting should.
80
-
81
- A named intent keeps its ordinary 12% wash rather than being untinted
82
- unconditionally: 12% of an actual hue does not read as a slab the way
83
- 12% of near-black ink does -- measured, `.card.soft.destructive` at 12%
84
- is a gentle `249, 225, 225`, nothing like the `217, 217, 217` a neutral
85
- surface mixing the same 12% of its own ink used to read as -- and
86
- untinting it anyway would silence a `.card.soft.destructive` at rest
87
- rather than merely quiet it, the same reason `.panel`'s own carve-out
88
- (below) is scoped the same way. `:not(...)` names every intent class for
89
- that reason; a version of this fix that untinted every intent shipped
90
- first and was corrected once `.panel` needed the identical, narrower
91
- carve-out to keep a resting `.panel.destructive` legible.
92
-
93
- `--_fill-cap: 0%` (box.css's override slot, not `--ui-fill` directly --
94
- see the comment there) composed with `box-hover`'s own formula
95
- (`min(100%, resting + 14%)`) is what reveals the intent on hover without
96
- a second fill declaration, for the no-intent case: resting at 0% means
97
- hover lands at exactly the hover step, 14%, in `--intent-hover` as
98
- always. The ground steps to `--color-surface` at the same moment,
99
- through the public `--ui-surface-ground` escape hatch rather than the
100
- private `--_d-ground`, so a consumer's own override still wins over both
101
- states the way it always has. Scoped to `.interactive` and `.hoverable`,
102
- the two classes that already opt a card into `box-hover` -- a plain
103
- `.card.soft` with neither has no hover state to reveal into and stays at
104
- the quiet, untinted rest. A named intent needs neither special case: it
105
- already rests tinted, so the same generic `box-hover` formula deepens it
106
- on hover (12% to 26%) with no ground shift required. */
107
- &.soft:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info) {
37
+ /* `.soft` rests untinted for no named intent instead of mixing its 12%: 12%
38
+ of `--color-text` onto any of `--color-background`, `--color-foreground`,
39
+ or `--color-surface` lands within a few units of the others (a near-black
40
+ ink dominates the result), so swapping the ground token alone does not read
41
+ as quieter. A resting no-intent `.card.soft` is `--color-foreground` with
42
+ no ink mixed in -- a plain step up from the page that marks a different
43
+ container without drawing attention to it.
44
+
45
+ A named intent keeps its ordinary 12% wash: 12% of an actual hue does not
46
+ read as a slab the way 12% of near-black ink does (`.card.soft.destructive`
47
+ is a gentle `249, 225, 225`), and untinting it would silence it rather than
48
+ quiet it. `:not(...)` names every intent class for that reason; `.panel`'s
49
+ carve-out below is scoped the same way.
50
+
51
+ `--_fill-cap: 0%` (box.css's override slot, not `--ui-fill` directly -- see
52
+ the comment there) composed with `box-hover`'s formula (`min(100%, resting
53
+ + 14%)`) reveals the intent on hover with no second fill declaration for
54
+ the no-intent case: resting at 0% means hover lands at exactly the hover
55
+ step, 14%, in `--intent-hover`. The ground steps to `--color-surface` at
56
+ the same moment, through `--_d-surface-ground`, the default `surface` reads
57
+ beneath the public `--ui-surface-ground`, so a consumer's override, or
58
+ glass's ground, wins over both states. Scoped to `.interactive` and
59
+ `.hoverable`, the two classes that opt a card into `box-hover`: a plain
60
+ `.card.soft` has no hover state to reveal into and stays at the quiet rest.
61
+ A named intent needs neither special case: it rests tinted, so the generic
62
+ `box-hover` formula deepens it on hover (12% to 26%). */
63
+ &.soft:not(.primary, .secondary, .success, .warning, .destructive, .info) {
108
64
  --_fill-cap: 0%;
109
- --ui-surface-ground: var(--color-foreground);
65
+ --_d-surface-ground: var(--color-foreground);
110
66
 
111
67
  &.interactive:hover,
112
68
  &.hoverable:hover {
113
- --ui-surface-ground: var(--color-surface);
69
+ --_d-surface-ground: var(--color-surface);
114
70
  }
115
71
  }
116
72
 
@@ -148,68 +104,72 @@
148
104
  --_d-border: 0%;
149
105
  --_d-elevation: 0;
150
106
 
151
- &.p-xs,
152
- &.dense {
107
+ &.p-xs {
153
108
  --surface-padding: 0.25rem;
109
+ --_scale-pad: 0.5;
154
110
  }
155
111
 
156
- &.p-sm,
157
- &.compact {
112
+ &.p-sm {
158
113
  --surface-padding: 0.5rem;
114
+ --_scale-pad: 0.75;
159
115
  }
160
116
 
161
- &.p-lg,
162
- &.spacious {
117
+ &.p-lg {
163
118
  --surface-padding: 1.5rem;
119
+ --_scale-pad: 1.25;
164
120
  }
165
121
 
166
122
  &.flush {
167
123
  --surface-padding: 0;
168
124
  }
169
125
 
170
- /* Neutral panels are the common case -- most are plain layout wells,
171
- sidebars, and toolbars with no named intent -- and 12% of `--color-text`
172
- (near-black) mixed onto the page is a far more assertive move than 12%
173
- of an actual hue: measured, `217, 217, 217` against a `250, 250, 250`
174
- page, the same "neutral ink reads louder" effect `.card.soft`'s fix
175
- already answered, and the same reason `--intent-fill-max` stops
176
- neutral's fill short everywhere else in the model.
177
-
178
- Scoped to no named intent rather than untinted unconditionally the way
179
- `data-table` is: a panel is not interactive, so there is no hover to
180
- reveal a colored intent back into later, and it is edgeless by default,
181
- so the fill is the only channel a `.primary` bulk-actions bar or a
182
- `.destructive` danger-zone panel has to say what it is. Untinting those
183
- unconditionally would silence them, not just quiet them -- measured, a
184
- resting `.panel.destructive` is a gentle `249, 225, 225` warm wash,
185
- nothing like the neutral slab above. `.card.soft` carries the identical
186
- no-named-intent scoping now, corrected to match once this carve-out
187
- shipped -- see the comment on `.card`'s own `&.soft`, above. `:not(...)`
188
- names every intent class for that reason, plus `.solid`/`.ghost` so an
189
- explicit presentation on the element is never clamped to a cap it did
190
- not ask for -- the same guard `data-table`'s own `:not(.solid, .ghost)`
191
- uses.
192
-
193
- Grounds on `--color-foreground` through the public `--ui-surface-ground`
194
- escape hatch, not the private `--_d-ground`, the same token `.card.soft`
195
- steps to -- so a consumer's own override still wins, and a bare `.panel`
196
- rests at exactly the plate a `.card.soft` does (measured: `245, 245, 245`
197
- light, `23, 23, 23` dark, identical both ways). Permanent rather than
198
- hover-revealed: neither a panel nor `data-table` has an interaction
199
- state to reveal a tint into, so both rest quiet rather than reveal.
200
-
201
- The one cost, inherited rather than new: an own declaration always beats
202
- an inherited one, so this also beats `.glass` setting the same token on
203
- an ancestor for its translucent ground -- a neutral panel under `.glass`
204
- is quiet but opaque rather than blurred. `.card.soft` has carried this
205
- exact trade-off since it shipped; nothing exercised it there because
206
- `tests/browser/aesthetics.spec.ts`'s glass coverage tests a default
207
- (ghost) card, never a soft one. Accepted rather than solved differently:
208
- there is no way to ask "did an ancestor already set this token" in CSS,
209
- only which declaration wins, and the quiet neutral rest is the more
210
- common case a panel needs to get right. */
211
- &:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid, .ghost) {
126
+ /* Neutral panels are the common case -- layout wells, sidebars, toolbars with
127
+ no named intent -- and 12% of `--color-text` (near-black) mixed onto the
128
+ page is far more assertive than 12% of a hue (`217, 217, 217` against a
129
+ `250, 250, 250` page): the same "neutral ink reads louder" effect
130
+ `.card.soft` answers, and the reason `--intent-fill-max` stops neutral's
131
+ fill short everywhere else.
132
+
133
+ Scoped to no named intent rather than untinted unconditionally, as
134
+ `data-table` is: a panel is not interactive, so there is no hover to reveal
135
+ a colored intent into, and it is edgeless by default, so the fill is the
136
+ only channel a `.primary` bulk-actions bar or a `.destructive` danger-zone
137
+ panel has to say what it is (a resting `.panel.destructive` is a gentle
138
+ `249, 225, 225` wash). `.card.soft` carries the same scoping. `:not(...)`
139
+ names every intent class, plus `.solid`/`.ghost` so an explicit
140
+ presentation on the element is never clamped to a cap it did not ask for --
141
+ the guard `data-table`'s `:not(.solid, .ghost)` also uses.
142
+
143
+ Grounds on `--color-foreground` through `--_d-surface-ground`, the default
144
+ `.card.soft` rests on, so a bare `.panel` rests at exactly the plate a
145
+ `.card.soft` does, and a consumer's `--ui-surface-ground` or glass's
146
+ translucent ground wins over it: a neutral panel under `.glass` is glass.
147
+ Permanent rather than hover-revealed: neither a panel nor `data-table` has
148
+ an interaction state to reveal a tint into. */
149
+ &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid, .ghost) {
212
150
  --_fill-cap: 0%;
213
- --ui-surface-ground: var(--color-foreground);
151
+ --_d-surface-ground: var(--color-foreground);
214
152
  }
215
153
  }
154
+
155
+ /* The user agent already promises both of these for a native `<dialog>` --
156
+ `dialog:not([open]) { display: none }` and a centred `margin: auto` on the
157
+ open box -- but both are user-agent origin, which loses to any author rule
158
+ at all. `.card`'s own `display: block` beats the first outright, so a
159
+ `<dialog class="card">` rendered open with no `open` attribute and no
160
+ `.showModal()` call, and a margin reset beats the second, so an opened one
161
+ pinned to the top-left. The registry has no dialog component, and these
162
+ three are the containers a consumer reaches for.
163
+
164
+ Here rather than in the reset, so every entrypoint that ships `.card` ships
165
+ the fix, `/components` included. Unlayered, because a behaviour override has
166
+ to win over any layered rule whatever its specificity, and scoped to the
167
+ package's own containers, so a dialog the package does not style keeps
168
+ whatever margin its author gives it. */
169
+ dialog:is(.card, .panel, .surface):not([open]) {
170
+ display: none;
171
+ }
172
+
173
+ dialog:is(.card, .panel, .surface)[open] {
174
+ margin: auto;
175
+ }