@codenhub/styles 0.0.3 → 0.1.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 (75) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +52 -0
  3. package/README.md +60 -90
  4. package/dist/aesthetics/chunky-tile.css +1 -0
  5. package/dist/aesthetics/glass.css +1 -0
  6. package/dist/aesthetics/index.css +2 -0
  7. package/dist/aesthetics/neobrutalism.css +1 -0
  8. package/dist/aesthetics/pixel.css +1 -0
  9. package/dist/components.css +2 -2
  10. package/dist/index.css +2 -2
  11. package/dist/native.css +2 -0
  12. package/dist/theme.css +2 -2
  13. package/dist/tw/aesthetics/chunky-tile.css +107 -0
  14. package/dist/tw/aesthetics/glass.css +66 -0
  15. package/dist/tw/aesthetics/index.css +9 -0
  16. package/dist/tw/aesthetics/neobrutalism.css +63 -0
  17. package/dist/tw/aesthetics/pixel.css +88 -0
  18. package/dist/tw/box.css +185 -0
  19. package/dist/tw/components/button.css +88 -0
  20. package/dist/tw/components/feedback.css +163 -0
  21. package/dist/tw/components/form.css +601 -0
  22. package/dist/tw/components/index.css +18 -0
  23. package/dist/tw/components/loader.css +122 -0
  24. package/dist/tw/components/surface.css +98 -0
  25. package/dist/tw/components/tooltip.css +132 -0
  26. package/dist/tw/elevation.css +39 -0
  27. package/dist/tw/index.css +12 -0
  28. package/dist/tw/intent.css +189 -0
  29. package/dist/tw/native.css +203 -0
  30. package/dist/tw/presentation.css +77 -0
  31. package/dist/tw/reset.css +142 -0
  32. package/dist/tw/theme.css +218 -0
  33. package/dist/tw/typography.css +34 -0
  34. package/dist/tw/utilities/content.css +349 -0
  35. package/dist/tw/utilities/index.css +6 -0
  36. package/dist/tw/utilities/layout.css +107 -0
  37. package/docs/accessibility.md +43 -14
  38. package/docs/concepts.md +94 -0
  39. package/docs/index.md +62 -73
  40. package/docs/integrating/astro.md +29 -0
  41. package/docs/integrating/index.md +32 -0
  42. package/docs/integrating/nextjs.md +44 -0
  43. package/docs/integrating/svelte.md +37 -0
  44. package/docs/integrating/tailwind.md +40 -0
  45. package/docs/integrating/vue.md +25 -0
  46. package/docs/setup.md +116 -0
  47. package/docs/usage/aesthetics.md +197 -0
  48. package/docs/usage/buttons.md +74 -0
  49. package/docs/usage/composing.md +218 -0
  50. package/docs/usage/content-and-layout.md +200 -0
  51. package/docs/usage/customizing.md +213 -0
  52. package/docs/usage/feedback.md +117 -0
  53. package/docs/usage/forms.md +177 -0
  54. package/docs/usage/index.md +32 -0
  55. package/docs/usage/theming.md +210 -0
  56. package/docs/usage/tooltips.md +45 -0
  57. package/llms-full.txt +2122 -0
  58. package/llms.txt +26 -0
  59. package/package.json +96 -32
  60. package/docs/classes.md +0 -198
  61. package/docs/roadmap.md +0 -34
  62. package/docs/tests.md +0 -128
  63. package/docs/tokens.md +0 -101
  64. package/src/components/button.css +0 -155
  65. package/src/components/feedback.css +0 -214
  66. package/src/components/form.css +0 -296
  67. package/src/components/index.css +0 -9
  68. package/src/components/loader.css +0 -56
  69. package/src/components/surface.css +0 -6
  70. package/src/components/tooltip.css +0 -60
  71. package/src/index.css +0 -7
  72. package/src/overrides.css +0 -76
  73. package/src/theme.css +0 -136
  74. package/src/typography.css +0 -30
  75. package/src/utilities.css +0 -40
@@ -0,0 +1,122 @@
1
+ @reference "tailwindcss";
2
+ @source inline("loader dots-wave dots-fade dots-queue dots-rotate dots-grow dots-grow-alternate dot-bounce bars-wave pulse-ring");
3
+
4
+ /* The mask machinery, shared by the loader and by any component that shows a
5
+ spinner of its own. Artwork is a mask rather than an image so it takes its
6
+ 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. */
8
+ @utility loader-mask {
9
+ mask-image: var(--loader-art);
10
+ -webkit-mask-image: var(--loader-art);
11
+ mask-size: contain;
12
+ -webkit-mask-size: contain;
13
+ mask-repeat: no-repeat;
14
+ -webkit-mask-repeat: no-repeat;
15
+ mask-position: center;
16
+ -webkit-mask-position: center;
17
+ }
18
+
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
+ .loading:not(
33
+ :where(
34
+ .dots-wave,
35
+ .dots-fade,
36
+ .dots-queue,
37
+ .dots-rotate,
38
+ .dots-grow,
39
+ .dots-grow-alternate,
40
+ .dot-bounce,
41
+ .bars-wave,
42
+ .pulse-ring
43
+ )
44
+ ) {
45
+ --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>");
46
+ }
47
+
48
+ @utility dots-wave {
49
+ --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>");
50
+ }
51
+
52
+ @utility dots-fade {
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>.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>");
54
+ }
55
+
56
+ @utility dots-queue {
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_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>");
58
+ }
59
+
60
+ @utility dots-rotate {
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_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>");
62
+ }
63
+
64
+ @utility dots-grow {
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_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>");
66
+ }
67
+
68
+ @utility dots-grow-alternate {
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_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>");
70
+ }
71
+
72
+ @utility dot-bounce {
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>@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>");
74
+ }
75
+
76
+ @utility bars-wave {
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>.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>");
78
+ }
79
+
80
+ @utility pulse-ring {
81
+ --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>");
82
+ }
83
+
84
+ /* A loader takes a colour and its own geometry, and nothing else. No fill, no
85
+ edge, no silhouette: what used to be a bordered box wrapped around a mask is
86
+ now the mask. `--intent-color` defaults to `currentColor` for indicators, so
87
+ a loader inside filled content matches the text around it and
88
+ `.loader.success` is green. There is nothing else to know. */
89
+ @utility loader {
90
+ @apply loader-mask inline-block size-8 [&.lg]:size-10 [&.sm]:size-6;
91
+
92
+ color: var(--intent-color);
93
+ background-color: currentColor;
94
+ }
95
+
96
+ @media (prefers-reduced-motion: reduce) {
97
+ .loader,
98
+ .loading {
99
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='9.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-dasharray='42 18'/></svg>");
100
+ }
101
+
102
+ .dots-wave,
103
+ .dots-fade,
104
+ .dots-queue,
105
+ .dots-rotate,
106
+ .dots-grow,
107
+ .dots-grow-alternate {
108
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><circle cx='4' cy='12' r='3'/><circle cx='12' cy='12' r='3'/><circle cx='20' cy='12' r='3'/></svg>");
109
+ }
110
+
111
+ .dot-bounce {
112
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='4'/></svg>");
113
+ }
114
+
115
+ .bars-wave {
116
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><rect x='3' y='5' width='4' height='14' rx='2'/><rect x='10' y='5' width='4' height='14' rx='2'/><rect x='17' y='5' width='4' height='14' rx='2'/></svg>");
117
+ }
118
+
119
+ .pulse-ring {
120
+ --loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='9' fill='none' stroke='black' stroke-width='3'/></svg>");
121
+ }
122
+ }
@@ -0,0 +1,98 @@
1
+ @reference "tailwindcss";
2
+ @import "../theme.css";
3
+ @reference "../box.css";
4
+
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
+ /* A ghost, edged container that takes one unit when depth is supplied. */
38
+ @utility card {
39
+ @apply surface p-(--surface-padding,1.25rem);
40
+
41
+ /* After the `@apply`, not before: `surface` inlines its own resting geometry
42
+ at that point, so defaults written above it would be overridden by the very
43
+ utility they are meant to specialise. */
44
+ --_d-fill: 0%;
45
+ --_d-fg-on-fill: 0%;
46
+ --_d-border: 100%;
47
+ --_d-elevation: 1;
48
+
49
+ /* `.p-sm`/`.p-lg` are the canonical names; `.compact`/`.spacious` are
50
+ first-class aliases -- see the identical note in `button.css`. */
51
+ &.p-sm,
52
+ &.compact {
53
+ --surface-padding: 0.75rem;
54
+ }
55
+
56
+ &.p-lg,
57
+ &.spacious {
58
+ --surface-padding: 2rem;
59
+ }
60
+
61
+ &.flush {
62
+ --surface-padding: 0;
63
+ }
64
+
65
+ /* Opt-in, because a card is only sometimes a control. Applications still owe
66
+ it a real interactive element and an accessible name. Hover and press are
67
+ the derived ones every other pressable box gets; there is nothing
68
+ card-specific about either. */
69
+ &.interactive {
70
+ @apply box-hover box-active cursor-pointer;
71
+ }
72
+ }
73
+
74
+ /* A panel is the flush, unelevated sibling: sidebars, toolbars, and wells that
75
+ sit flat against the layout rather than floating above it. Soft and edgeless
76
+ is what makes it read as a well without drawing a line to say so. */
77
+ @utility panel {
78
+ @apply surface p-(--surface-padding,1rem);
79
+
80
+ --_d-fill: 12%;
81
+ --_d-fg-on-fill: 0%;
82
+ --_d-border: 0%;
83
+ --_d-elevation: 0;
84
+
85
+ &.p-sm,
86
+ &.compact {
87
+ --surface-padding: 0.5rem;
88
+ }
89
+
90
+ &.p-lg,
91
+ &.spacious {
92
+ --surface-padding: 1.5rem;
93
+ }
94
+
95
+ &.flush {
96
+ --surface-padding: 0;
97
+ }
98
+ }
@@ -0,0 +1,132 @@
1
+ @reference "tailwindcss";
2
+ @import "../theme.css";
3
+ @reference "../box.css";
4
+ @reference "./surface.css";
5
+
6
+ /* The host is a positioning wrapper and nothing else. Its bubble is a
7
+ pseudo-element placed outside the host's own box, so painting the host would
8
+ draw a box around the trigger rather than around the message -- which is
9
+ exactly what a blanket surface membership did to this component once already.
10
+
11
+ The bubble is the component. It is a surface, so an aesthetic that blurs
12
+ surfaces blurs it too, and it rests on a ground of its own, edged, and flat.
13
+ Depth is opt-in through `.raised` and `.floating` like every other component;
14
+ its boundary against arbitrary content is the floored edge below, not a
15
+ shadow. See model.md#elevation. */
16
+ @utility tooltip {
17
+ --tooltip-gap: 0.5rem;
18
+ --tooltip-top: calc(var(--tooltip-gap) * -1);
19
+ --tooltip-right: auto;
20
+ --tooltip-bottom: auto;
21
+ --tooltip-left: 50%;
22
+ --tooltip-origin: bottom;
23
+ --tooltip-transform: translate(-50%, -100%);
24
+
25
+ @apply relative;
26
+
27
+ /* The trigger badge, for a page with no element to hang the message on. Quiet
28
+ -- ghost, edged, on `--intent-subtle`, a key cap's resting shape -- because
29
+ it marks that there is a message, it is not one. The line earns its keep:
30
+ `--intent-subtle` is a near-page tint that reads the same across families,
31
+ so a plate alone left three of eight intents indistinguishable;
32
+ `--intent-border` is the slot that differs. Painting the host is right only
33
+ here -- everywhere else the bubble is outside the host's box, so painting it
34
+ boxes the trigger. `.tooltip-icon` is the opt-in that says the host *is* the
35
+ badge. */
36
+ &.tooltip-icon {
37
+ @apply box inline-flex size-5 cursor-default items-center justify-center text-sm font-bold;
38
+
39
+ --_d-fill: 0%;
40
+ --_d-fg-on-fill: 0%;
41
+ --_d-border: 100%;
42
+ --_d-elevation: 0;
43
+ --_d-ground: var(--intent-subtle);
44
+
45
+ border-radius: var(--ui-radius, calc(infinity * 1px));
46
+ /* The one thing `box` hands it that it must refuse. An aesthetic's `clip-path`
47
+ clips a host's pseudo-elements along with the host, and this host's
48
+ pseudo-element is the bubble, positioned outside its box -- so a stepped
49
+ badge under `.pixel` would clip the message away entirely. The bubble
50
+ carries the stepped shape itself; the badge stays round. */
51
+ clip-path: none;
52
+ }
53
+
54
+ &[data-tooltip-position="bottom"] {
55
+ --tooltip-top: auto;
56
+ --tooltip-bottom: calc(var(--tooltip-gap) * -1);
57
+ --tooltip-origin: top;
58
+ --tooltip-transform: translate(-50%, 100%);
59
+ }
60
+
61
+ &[data-tooltip-position="left"] {
62
+ --tooltip-top: 50%;
63
+ --tooltip-left: calc(var(--tooltip-gap) * -1);
64
+ --tooltip-origin: right;
65
+ --tooltip-transform: translate(-100%, -50%);
66
+ }
67
+
68
+ &[data-tooltip-position="right"] {
69
+ --tooltip-top: 50%;
70
+ --tooltip-left: auto;
71
+ --tooltip-right: calc(var(--tooltip-gap) * -1);
72
+ --tooltip-origin: left;
73
+ --tooltip-transform: translate(100%, -50%);
74
+ }
75
+
76
+ &::after {
77
+ @apply surface pointer-events-none absolute z-(--z-popover) w-max max-w-72 px-2.5 py-2 text-sm font-normal whitespace-normal opacity-0 transition-[opacity,transform] duration-(--motion-duration-normal) ease-(--motion-ease);
78
+
79
+ /* Filled, over a ground of its own. An intent floods the fill
80
+ (`.tooltip.primary` is the primary colour); with no intent `intent.css`
81
+ resolves the bubble onto `--color-tooltip` and lifts the neutral cap, a
82
+ plate stated per theme because the derived version was a mid-grey slab in
83
+ light and lifted in dark. The ground stays the surface tone, not the
84
+ plate -- pointed at both, the fill axis went inert and `axes.spec.ts`
85
+ caught it. An opaque ground is also what keeps a cascaded `.ghost` from
86
+ leaving the bubble boundaryless, retiring the old pinned-fill bound.
87
+ `--ui-surface-ground` is read first so a glass tooltip stays glass. */
88
+ --_d-fill: 100%;
89
+ --_d-fg-on-fill: 100%;
90
+ --_d-border: 100%;
91
+ /* Flat, and stated rather than left to `box`'s fallback of 1: the bubble is
92
+ a surface, so an aesthetic's part-based depth would otherwise reach it
93
+ unasked -- `.neobrutalism`'s slab in particular. `.raised`/`.floating`
94
+ opt back in. */
95
+ --_d-elevation: 0;
96
+ --_d-ground: var(--ui-surface-ground, var(--intent-subtle));
97
+
98
+ /* The bubble's line is a boundary against content nobody chose, so `box`'s
99
+ edge blend is wrong here: at a full fill the line *is* the plate. Restate
100
+ the colour only; how much line comes from `--_edge-amount`, floored below. */
101
+ --_line: var(--intent-border);
102
+
103
+ /* Floored, for the same reason the ground is opaque: a cascade must not take
104
+ the boundary from a bubble nobody classed (worst in light, where the plate
105
+ is near-white). The element's own `.edgeless` lowers it -- same
106
+ element-vs-cascade idiom `text-control` uses -- and `axes.spec.ts` still
107
+ sees the edge move because it probes with the class on the element. */
108
+ --_edge-floor: 100%;
109
+
110
+ /* A bubble is a small transient popover, not a modal, so it takes the
111
+ control radius rather than the surface one `surface` hands it. */
112
+ border-radius: var(--ui-radius, var(--radius-control));
113
+ content: attr(data-tooltip);
114
+ left: var(--tooltip-left);
115
+ right: var(--tooltip-right);
116
+ top: var(--tooltip-top);
117
+ bottom: var(--tooltip-bottom);
118
+ transform: var(--tooltip-transform) scale(0);
119
+ transform-origin: var(--tooltip-origin);
120
+ }
121
+
122
+ &.edgeless::after {
123
+ --_edge-floor: 0%;
124
+ }
125
+
126
+ &:hover::after,
127
+ &:focus-visible::after,
128
+ &[data-state="open"]::after {
129
+ opacity: 1;
130
+ transform: var(--tooltip-transform) scale(1);
131
+ }
132
+ }
@@ -0,0 +1,39 @@
1
+ /* Elevation is a modifier, not an axis: depth is a per-element decision (raised
2
+ tiles beside a flat panel in one aesthetic), so one unitless number multiplies
3
+ the aesthetic's shadow geometry where the component composes it. Unitless so
4
+ it inherits -- a container flattens a region, an element opts back in -- and
5
+ so `.flat` removes a part-based slab or blur without a conditional (complete
6
+ surface shadows like glass remain). The two classes that ask for depth carry
7
+ the geometry themselves; on a component it would inherit into everything
8
+ nested and a plain page would have a shadow under every card. See
9
+ model.md#elevation.
10
+
11
+ `.flat`/`.raised`/`.floating` and `.elevation-none`/`-sm`/`-md` are
12
+ interchangeable aliases, the second borrowing the raw `--elevation-*` scale's
13
+ naming. Bare `sm`/`md`/`lg` would collide with the size modifier;
14
+ `.elevation-lg` is reserved, not shipped. */
15
+
16
+ /* No part-based depth. Complete surface shadow values, such as glass, are not
17
+ reached by the elevation multiplier. */
18
+ .elevation-none,
19
+ .flat {
20
+ --ui-elevation: 0;
21
+ }
22
+
23
+ /* The aesthetic's depth as authored, said explicitly. The geometry is the
24
+ fallback for a page with no aesthetic in scope; an aesthetic that declares its
25
+ own overrides it, and the elevation multiplies whichever one lands. */
26
+ .elevation-sm,
27
+ .raised {
28
+ --ui-elevation: 1;
29
+ --elevation-y: 1px;
30
+ --elevation-blur: 3px;
31
+ }
32
+
33
+ /* Twice it, for menus and popovers. */
34
+ .elevation-md,
35
+ .floating {
36
+ --ui-elevation: 2;
37
+ --elevation-y: 1px;
38
+ --elevation-blur: 3px;
39
+ }
@@ -0,0 +1,12 @@
1
+ @import "tailwindcss";
2
+ @source not "../dist";
3
+ @source not "../tests";
4
+ @source not "../dev";
5
+ @source not "../debug";
6
+ @source not "../playground";
7
+
8
+ @import "./theme.css";
9
+ @import "./typography.css";
10
+ @import "./utilities/index.css";
11
+ @import "./components/index.css";
12
+ @import "./reset.css";
@@ -0,0 +1,189 @@
1
+ /* An intent maps one colour family onto seven shared slots and nothing else.
2
+ It does not cascade: the reset at the bottom redeclares the neutral defaults
3
+ at every component's own root, so a `.success` container cannot recolour a
4
+ nested destructive button while `.btn.success` still wins. Every intent writes
5
+ every slot, `--intent-fill-max` included -- omitting it inherits the reset's
6
+ neutral cap, and `tests/integration/registry.test.ts` fails the build on one
7
+ that does. Neutral maps `--intent-subtle` to `--color-surface` (the tone
8
+ tooltips, key caps, code and table heads sit on) and `--intent-border` to
9
+ `--ui-ink` (how an aesthetic restates the neutral line without naming one). */
10
+
11
+ /* Neutral stops its fill short: its colour is the page's ink, so a full fill is
12
+ a slab and `.neutral.solid` was pixel-for-pixel `.primary.solid`. 20% is
13
+ chosen against `.soft`'s 12% -- a step past the tint, not a repeat.
14
+ `--intent-contrast` stays the page colour; a `.neutral.solid` badge's ink is
15
+ `box`'s question, answered by the same `--ui-fg-on-fill` cap. Pointing this
16
+ slot at `--color-text` instead rendered a checked neutral checkbox
17
+ black-on-black. */
18
+ .neutral {
19
+ --intent-color: var(--color-text);
20
+ --intent-contrast: var(--color-text-contrast);
21
+ --intent-hover: var(--color-text-hover);
22
+ --intent-strong: var(--color-text-strong);
23
+ --intent-subtle: var(--color-surface);
24
+ --intent-border: var(--ui-ink, var(--color-border));
25
+ --intent-fill-max: 20%;
26
+ }
27
+
28
+ .primary {
29
+ --intent-color: var(--color-primary);
30
+ --intent-contrast: var(--color-primary-contrast);
31
+ --intent-hover: var(--color-primary-hover);
32
+ --intent-strong: var(--color-primary-strong);
33
+ --intent-subtle: var(--color-primary-subtle);
34
+ --intent-border: var(--color-primary);
35
+ --intent-fill-max: 100%;
36
+ }
37
+
38
+ .secondary {
39
+ --intent-color: var(--color-accent);
40
+ --intent-contrast: var(--color-accent-contrast);
41
+ --intent-hover: var(--color-accent-hover);
42
+ --intent-strong: var(--color-accent-strong);
43
+ --intent-subtle: var(--color-accent-subtle);
44
+ --intent-border: var(--color-accent);
45
+ --intent-fill-max: 100%;
46
+ }
47
+
48
+ .success {
49
+ --intent-color: var(--color-success);
50
+ --intent-contrast: var(--color-success-contrast);
51
+ --intent-hover: var(--color-success-hover);
52
+ --intent-strong: var(--color-success-strong);
53
+ --intent-subtle: var(--color-success-subtle);
54
+ --intent-border: var(--color-success);
55
+ --intent-fill-max: 100%;
56
+ }
57
+
58
+ .warning {
59
+ --intent-color: var(--color-warning);
60
+ --intent-contrast: var(--color-warning-contrast);
61
+ --intent-hover: var(--color-warning-hover);
62
+ --intent-strong: var(--color-warning-strong);
63
+ --intent-subtle: var(--color-warning-subtle);
64
+ --intent-border: var(--color-warning);
65
+ --intent-fill-max: 100%;
66
+ }
67
+
68
+ .destructive,
69
+ .danger,
70
+ .error {
71
+ --intent-color: var(--color-destructive);
72
+ --intent-contrast: var(--color-destructive-contrast);
73
+ --intent-hover: var(--color-destructive-hover);
74
+ --intent-strong: var(--color-destructive-strong);
75
+ --intent-subtle: var(--color-destructive-subtle);
76
+ --intent-border: var(--color-destructive);
77
+ --intent-fill-max: 100%;
78
+ }
79
+
80
+ .info {
81
+ --intent-color: var(--color-info);
82
+ --intent-contrast: var(--color-info-contrast);
83
+ --intent-hover: var(--color-info-hover);
84
+ --intent-strong: var(--color-info-strong);
85
+ --intent-subtle: var(--color-info-subtle);
86
+ --intent-border: var(--color-info);
87
+ --intent-fill-max: 100%;
88
+ }
89
+
90
+ /* Every component resolves the neutral defaults at its own root, at zero
91
+ specificity, so an intent class on the element wins while an inherited value
92
+ loses -- the one rule that lets every component drop its per-intent branches.
93
+ This list and the one in `native.css` are the package's only hand-maintained
94
+ selector lists; `tests/integration/registry.test.ts` fails the build on a
95
+ registry component missing from either. `.text-control` and `.surface` are on
96
+ it though they are not components: both are documented as typeable on their
97
+ own, and a bare `<div class="surface">` with no intent resolved computes a
98
+ transparent background and a zero-width border -- `box` collapses. */
99
+ :where(
100
+ .btn,
101
+ .ipt,
102
+ .text-control,
103
+ .surface,
104
+ .textarea,
105
+ .select,
106
+ .input-group,
107
+ .checkbox,
108
+ .radio,
109
+ .switch,
110
+ .card,
111
+ .panel,
112
+ .alert,
113
+ .data-table,
114
+ .pre,
115
+ .quote,
116
+ .tooltip,
117
+ .badge,
118
+ .kbd,
119
+ .code,
120
+ .loader,
121
+ .skeleton,
122
+ .progress,
123
+ .divider
124
+ ) {
125
+ --intent-color: var(--color-text);
126
+ --intent-contrast: var(--color-text-contrast);
127
+ --intent-hover: var(--color-text-hover);
128
+ --intent-strong: var(--color-text-strong);
129
+ --intent-subtle: var(--color-surface);
130
+ --intent-border: var(--ui-ink, var(--color-border));
131
+ --intent-fill-max: 20%;
132
+ }
133
+
134
+ /* The six text controls take a heavier line than everything else that rests on
135
+ the shared neutral. A card, a table or a divider sits on a filled surface or
136
+ separates content that identifies itself; a text control at rest is a
137
+ transparent box whose only mark is its border, and WCAG 1.4.11 asks that
138
+ border for 3:1. `--color-border` gives it 1.42:1.
139
+
140
+ Declared here at zero specificity for the same reason the indicators below
141
+ are: written inside `@utility text-control` it would land in the utilities
142
+ layer, the reset above lands in no layer at all, and unlayered beats layered
143
+ at any specificity. Written as a plain class list it would carry 0-1-0 and,
144
+ being later in the stylesheet, beat `.primary` -- which would make every
145
+ colored control draw a grey line. `:where()` keeps it under both. */
146
+ :where(.text-control, .ipt, .textarea, .select, .input-group, .checkbox, .radio, .switch) {
147
+ --intent-border: var(--ui-ink, var(--color-control-border));
148
+ }
149
+
150
+ /* The bubble a tooltip draws is the one plate in the package that is chosen
151
+ rather than composed, so the tooltip resolves the no-intent case onto its own
152
+ pair instead of onto the shared neutral. Composed, it was 20% of the page's
153
+ ink over `--color-surface`, which lifts off a dark page and sinks into a light
154
+ one -- see `--color-tooltip` in `theme.css` for the measurements.
155
+
156
+ The cap goes with it. Neutral stops its fill at 20% because its colour is the
157
+ page's ink and a full fill of that is a slab; `--color-tooltip` is a plate
158
+ rather than an ink, so a full fill of it is the plate and there is nothing to
159
+ stop short of.
160
+
161
+ `--intent-subtle` stays the surface token. It is what the trigger badge rests
162
+ on, and a badge is a quiet marker on the page rather than the message, so it
163
+ keeps the tone every other unclassed component sits on.
164
+
165
+ Two selectors, because `none` and `neutral` have to agree here. `:where()`
166
+ loses to any intent class, so `.tooltip.primary` still floods with primary;
167
+ `.tooltip.neutral` carries 0-2-0 and beats `.neutral`, so writing the intent
168
+ out gives the same bubble as writing nothing. Without the second selector the
169
+ two would differ on a component where they mean the same thing. */
170
+ :where(.tooltip),
171
+ .tooltip.neutral {
172
+ --intent-color: var(--color-tooltip);
173
+ --intent-contrast: var(--color-tooltip-contrast);
174
+ --intent-hover: var(--color-tooltip);
175
+ --intent-strong: var(--color-tooltip-contrast);
176
+ --intent-subtle: var(--color-surface);
177
+ --intent-border: var(--ui-ink, var(--color-border));
178
+ --intent-fill-max: 100%;
179
+ }
180
+
181
+ /* Indicators take the color they sit in rather than the shared neutral, so a
182
+ loader inside filled or colored content still matches it. It has to be
183
+ declared here at zero specificity: written in the component's own block it
184
+ would carry 0-1-0 and, being later in the stylesheet, beat `.success` --
185
+ which silently makes every indicator ignore every intent class. */
186
+ :where(.loader, .skeleton, .progress, .divider) {
187
+ --intent-color: currentColor;
188
+ --intent-border: currentColor;
189
+ }