neba 0.0.1 → 1.0.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 (141) hide show
  1. package/README.md +169 -2
  2. package/dist/components/accordion/Accordion.d.ts +97 -0
  3. package/dist/components/accordion/Accordion.js +1 -0
  4. package/dist/components/accordion/index.d.ts +2 -0
  5. package/dist/components/accordion/index.js +1 -0
  6. package/dist/components/alert/Alert.d.ts +47 -0
  7. package/dist/components/alert/Alert.js +1 -0
  8. package/dist/components/alert/index.d.ts +2 -0
  9. package/dist/components/alert/index.js +1 -0
  10. package/dist/components/badge/Badge.d.ts +80 -0
  11. package/dist/components/badge/Badge.js +1 -0
  12. package/dist/components/badge/index.d.ts +2 -0
  13. package/dist/components/badge/index.js +1 -0
  14. package/dist/components/box/Box.d.ts +50 -0
  15. package/dist/components/box/Box.js +1 -0
  16. package/dist/components/box/index.d.ts +2 -0
  17. package/dist/components/box/index.js +1 -0
  18. package/dist/components/button/Button.d.ts +25 -3
  19. package/dist/components/button/Button.js +1 -1
  20. package/dist/components/button/index.d.ts +1 -0
  21. package/dist/components/button-group/ButtonGroup.d.ts +31 -0
  22. package/dist/components/button-group/ButtonGroup.js +1 -0
  23. package/dist/components/button-group/index.d.ts +2 -0
  24. package/dist/components/button-group/index.js +1 -0
  25. package/dist/components/card/Card.d.ts +45 -0
  26. package/dist/components/card/Card.js +1 -0
  27. package/dist/components/card/index.d.ts +2 -0
  28. package/dist/components/card/index.js +1 -0
  29. package/dist/components/checkbox/Checkbox.d.ts +39 -0
  30. package/dist/components/checkbox/Checkbox.js +1 -0
  31. package/dist/components/checkbox/index.d.ts +2 -0
  32. package/dist/components/checkbox/index.js +1 -0
  33. package/dist/components/chip/Chip.d.ts +50 -0
  34. package/dist/components/chip/Chip.js +1 -0
  35. package/dist/components/chip/index.d.ts +2 -0
  36. package/dist/components/chip/index.js +1 -0
  37. package/dist/components/combobox/Combobox.d.ts +131 -0
  38. package/dist/components/combobox/Combobox.js +1 -0
  39. package/dist/components/combobox/index.d.ts +2 -0
  40. package/dist/components/combobox/index.js +1 -0
  41. package/dist/components/dialog/Dialog.d.ts +111 -0
  42. package/dist/components/dialog/Dialog.js +1 -0
  43. package/dist/components/dialog/index.d.ts +2 -0
  44. package/dist/components/dialog/index.js +1 -0
  45. package/dist/components/divider/Divider.d.ts +48 -0
  46. package/dist/components/divider/Divider.js +1 -0
  47. package/dist/components/divider/index.d.ts +2 -0
  48. package/dist/components/divider/index.js +1 -0
  49. package/dist/components/file-picker/FilePicker.d.ts +103 -0
  50. package/dist/components/file-picker/FilePicker.js +1 -0
  51. package/dist/components/file-picker/index.d.ts +2 -0
  52. package/dist/components/file-picker/index.js +1 -0
  53. package/dist/components/list/List.d.ts +81 -0
  54. package/dist/components/list/List.js +1 -0
  55. package/dist/components/list/index.d.ts +2 -0
  56. package/dist/components/list/index.js +1 -0
  57. package/dist/components/menu/Menu.d.ts +216 -0
  58. package/dist/components/menu/Menu.js +1 -0
  59. package/dist/components/menu/index.d.ts +2 -0
  60. package/dist/components/menu/index.js +1 -0
  61. package/dist/components/number-field/NumberField.d.ts +113 -0
  62. package/dist/components/number-field/NumberField.js +1 -0
  63. package/dist/components/number-field/index.d.ts +2 -0
  64. package/dist/components/number-field/index.js +1 -0
  65. package/dist/components/overlay/Overlay.d.ts +85 -0
  66. package/dist/components/overlay/Overlay.js +1 -0
  67. package/dist/components/overlay/index.d.ts +2 -0
  68. package/dist/components/overlay/index.js +1 -0
  69. package/dist/components/pagination/Pagination.d.ts +65 -0
  70. package/dist/components/pagination/Pagination.js +1 -0
  71. package/dist/components/pagination/index.d.ts +2 -0
  72. package/dist/components/pagination/index.js +1 -0
  73. package/dist/components/progress-box/ProgressBox.d.ts +33 -0
  74. package/dist/components/progress-box/ProgressBox.js +1 -0
  75. package/dist/components/progress-box/index.d.ts +2 -0
  76. package/dist/components/progress-box/index.js +1 -0
  77. package/dist/components/progress-circular/ProgressCircular.d.ts +19 -0
  78. package/dist/components/progress-circular/ProgressCircular.js +1 -0
  79. package/dist/components/progress-circular/index.d.ts +2 -0
  80. package/dist/components/progress-circular/index.js +1 -0
  81. package/dist/components/progress-linear/ProgressLinear.d.ts +20 -0
  82. package/dist/components/progress-linear/ProgressLinear.js +1 -0
  83. package/dist/components/progress-linear/index.d.ts +2 -0
  84. package/dist/components/progress-linear/index.js +1 -0
  85. package/dist/components/radio-group/RadioGroup.d.ts +51 -0
  86. package/dist/components/radio-group/RadioGroup.js +1 -0
  87. package/dist/components/radio-group/index.d.ts +2 -0
  88. package/dist/components/radio-group/index.js +1 -0
  89. package/dist/components/select/Select.d.ts +76 -0
  90. package/dist/components/select/Select.js +1 -0
  91. package/dist/components/select/index.d.ts +2 -0
  92. package/dist/components/select/index.js +1 -0
  93. package/dist/components/slider/Slider.d.ts +38 -0
  94. package/dist/components/slider/Slider.js +1 -0
  95. package/dist/components/slider/index.d.ts +2 -0
  96. package/dist/components/slider/index.js +1 -0
  97. package/dist/components/switch/Switch.d.ts +39 -0
  98. package/dist/components/switch/Switch.js +1 -0
  99. package/dist/components/switch/index.d.ts +2 -0
  100. package/dist/components/switch/index.js +1 -0
  101. package/dist/components/table/Table.d.ts +80 -0
  102. package/dist/components/table/Table.js +1 -0
  103. package/dist/components/table/index.d.ts +2 -0
  104. package/dist/components/table/index.js +1 -0
  105. package/dist/components/tabs/Tabs.d.ts +92 -0
  106. package/dist/components/tabs/Tabs.js +1 -0
  107. package/dist/components/tabs/index.d.ts +2 -0
  108. package/dist/components/tabs/index.js +1 -0
  109. package/dist/components/text-field/TextField.d.ts +63 -0
  110. package/dist/components/text-field/TextField.js +1 -0
  111. package/dist/components/text-field/index.d.ts +2 -0
  112. package/dist/components/text-field/index.js +1 -0
  113. package/dist/components/toast/Toast.d.ts +116 -0
  114. package/dist/components/toast/Toast.js +1 -0
  115. package/dist/components/toast/index.d.ts +2 -0
  116. package/dist/components/toast/index.js +1 -0
  117. package/dist/components/tooltip/Tooltip.d.ts +81 -0
  118. package/dist/components/tooltip/Tooltip.js +1 -0
  119. package/dist/components/tooltip/index.d.ts +2 -0
  120. package/dist/components/tooltip/index.js +1 -0
  121. package/dist/components/typography/Typography.d.ts +62 -0
  122. package/dist/components/typography/Typography.js +1 -0
  123. package/dist/components/typography/index.d.ts +2 -0
  124. package/dist/components/typography/index.js +1 -0
  125. package/dist/index.d.ts +32 -1
  126. package/dist/index.js +1 -1
  127. package/dist/internal/button-group.d.ts +25 -0
  128. package/dist/internal/button-group.js +1 -0
  129. package/dist/internal/icons.d.ts +48 -0
  130. package/dist/internal/icons.js +1 -0
  131. package/dist/internal/menu.d.ts +20 -0
  132. package/dist/internal/menu.js +1 -0
  133. package/dist/internal/progress.d.ts +122 -0
  134. package/dist/internal/progress.js +1 -0
  135. package/dist/internal/styles.d.ts +224 -0
  136. package/dist/internal/styles.js +1 -0
  137. package/dist/styles.css +758 -0
  138. package/dist/types.d.ts +77 -0
  139. package/dist/types.js +8 -0
  140. package/package.json +75 -19
  141. package/pnpm-workspace.yaml +0 -2
@@ -0,0 +1,758 @@
1
+ /**
2
+ * Neba design tokens.
3
+ *
4
+ * Consumers import this once, next to Tailwind:
5
+ *
6
+ * @import 'tailwindcss';
7
+ * @import 'neba/styles.css';
8
+ *
9
+ * Two lines, and no `@source` of their own — the `@source` below does that job.
10
+ * Components never hardcode a color; they map a `color` prop onto the `--n-*`
11
+ * local slots and let the cascade resolve the rest.
12
+ *
13
+ * Dark mode responds to `prefers-color-scheme`, and can be forced either way
14
+ * with `.dark` / `[data-theme='dark'|'light']` on any ancestor.
15
+ *
16
+ * The surface model is a sheet of acrylic, not a moulded plastic key: a
17
+ * slightly translucent fill, a blurred backdrop, and a hairline edge catching
18
+ * the light. Depth is not part of the surface — it is opt-in, via `elevation`.
19
+ */
20
+
21
+ /*
22
+ * The components' classes are Tailwind utilities, so Tailwind has to see the
23
+ * files that spell them out — otherwise every component renders unstyled.
24
+ *
25
+ * `@source` resolves relative to the file it is written in, not to whatever
26
+ * imported it. In a consumer's install that is `node_modules/neba/dist/`, next
27
+ * to the compiled components; in this repository it is `src/`, next to the
28
+ * `.tsx`. One line covers both, and an explicitly registered source is scanned
29
+ * even inside `node_modules`, which automatic detection skips.
30
+ *
31
+ * This is why consumers do not need an `@source '../node_modules/neba'` of
32
+ * their own — a path that would depend on where their CSS file happens to sit.
33
+ */
34
+ @source '.';
35
+
36
+ /* ---------------------------------------------------------------------------
37
+ * Light theme (default)
38
+ * ------------------------------------------------------------------------- */
39
+
40
+ :root {
41
+ /* Neutral surfaces and content */
42
+ --neba-surface: oklch(100% 0 0);
43
+ --neba-fg: oklch(24% 0.015 262);
44
+ --neba-muted-fg: oklch(56% 0.014 262);
45
+ --neba-border: oklch(91% 0.008 262);
46
+
47
+ /* Inactive controls: muted neutral rather than a faded copy of the color.
48
+ A greyed-out surface reads as "unavailable"; 50% opacity just reads blurry. */
49
+ --neba-disabled-bg: oklch(94.5% 0.005 262);
50
+ --neba-disabled-fg: oklch(73% 0.012 262);
51
+ --neba-disabled-border: oklch(90% 0.008 262);
52
+
53
+ /* Acrylic -------------------------------------------------------------- */
54
+
55
+ /* How opaque a filled surface is, and how hard the backdrop is blurred.
56
+ The blur radius is what decides whether anything behind is *legible*: at
57
+ 16px a grid line behind the button smears into flat colour and the surface
58
+ reads opaque no matter how low the alpha goes. Held at 9px so the backdrop
59
+ is present but never resolves into something you could read.
60
+ 88% is the floor for 4.5:1 white-on-fill over a white page; fill lightness
61
+ is picked against this number, not against opaque. */
62
+ --neba-fill-alpha: 88%;
63
+ --neba-blur: blur(9px) saturate(1.5);
64
+
65
+ /* Frosted, not polished. A single tile of greyscale fractal noise laid over
66
+ the fill in `overlay` is what separates sandblasted acrylic from a pane of
67
+ glass — and it is the one thing a flat fill can never fake. Kept at 12% in
68
+ the tile and blended, so it reads as tooth rather than as dirt.
69
+ One 120px tile, decoded once, reused by every surface on the page. */
70
+ --neba-grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='0.12'/%3E%3C/svg%3E");
71
+
72
+ /* The hairline that reads as the cut edge of an acrylic sheet, plus the
73
+ brighter top edge where the light lands. There is deliberately no dark
74
+ bottom bevel — that is what made the surface read as moulded plastic. */
75
+ --neba-plate-edge: rgb(255 255 255 / 0.18);
76
+ --neba-plate-top: rgb(255 255 255 / 0.26);
77
+ --neba-plate-solid: inset 0 1px 0 var(--neba-plate-top), inset 0 0 0 1px var(--neba-plate-edge);
78
+ --neba-plate-glass: inset 0 1px 0 var(--neba-plate-top);
79
+ /* Off-vertical on purpose: light falling across a sheet at an angle, rather
80
+ than the symmetric top-down gradient every framework ships. */
81
+ --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.13), rgb(255 255 255 / 0) 52%);
82
+
83
+ /* The acrylic with no dye in it — what a colored panel is mixed *into*.
84
+
85
+ At 42% the sheet was letting through more of the page than of itself, so on
86
+ any backdrop that is not pure white it settled into grey. Raising it to 66%
87
+ is the one place alpha is the right lever rather than the blur: the blur
88
+ decides whether the backdrop is *legible*, and 9px still hides it. This
89
+ decides whether the sheet reads as white, and it has to. */
90
+ --neba-glass-bg: rgb(255 255 255 / 0.66);
91
+
92
+ /* The container surface, in the three strengths a panel-shaped component
93
+ needs — Box, Card and TextField read these directly.
94
+
95
+ Undyed on purpose, and this is the one place the color family deliberately
96
+ stops at the edge. A container holds other people's content, which arrives
97
+ with its own colors; tinting the sheet under it puts every one of them on a
98
+ background they were not chosen against. So the family shows up in the
99
+ hairline, the focus ring and the caret, and the sheet stays white.
100
+
101
+ Controls are the opposite case and keep their tinted `--neba-{color}-panel`:
102
+ a Button's surface *is* the thing being colored.
103
+
104
+ The ladder is opacity, not lightness — the sheet catches more light as it
105
+ is engaged, rather than turning grey. */
106
+ --neba-panel: rgb(255 255 255 / 0.66);
107
+ --neba-panel-hover: rgb(255 255 255 / 0.82);
108
+ --neba-panel-press: rgb(255 255 255 / 0.92);
109
+
110
+ /* Pointer spotlight and release afterglow, over a filled surface. Tinted
111
+ variants use their own accent instead — see the derived block. The flash is
112
+ only a shade brighter than the spotlight it replaces: the press is carried
113
+ by the fill going dark, and this is the highlight riding on top of it, not
114
+ the effect itself. */
115
+ --neba-glow-on-fill: rgb(255 255 255 / 0.16);
116
+ --neba-flash-on-fill: rgb(255 255 255 / 0.22);
117
+
118
+ /* The scrim behind a modal surface. Neutral rather than tinted, and dim
119
+ rather than opaque: what a dialog needs is for the page to stop being
120
+ reachable, not for it to stop existing. */
121
+ --neba-scrim: rgb(16 24 40 / 0.34);
122
+
123
+ /* Elevation ------------------------------------------------------------
124
+ Neutral, never tinted with the control's own color: a colored glow is the
125
+ loudest thing a small control can do, and the job here is to place a
126
+ surface in space, not to advertise it. Level 4 is only reachable by
127
+ hovering a level-3 surface. */
128
+ --neba-shadow-ambient: rgb(16 24 40 / 0.13);
129
+ --neba-shadow-0: 0 0 #0000;
130
+ --neba-shadow-1: 0 1px 2px -1px var(--neba-shadow-ambient);
131
+ --neba-shadow-2:
132
+ 0 2px 4px -2px var(--neba-shadow-ambient), 0 5px 12px -7px var(--neba-shadow-ambient);
133
+ --neba-shadow-3:
134
+ 0 4px 8px -3px var(--neba-shadow-ambient), 0 12px 24px -11px var(--neba-shadow-ambient);
135
+ --neba-shadow-4:
136
+ 0 6px 12px -4px var(--neba-shadow-ambient), 0 20px 40px -15px var(--neba-shadow-ambient);
137
+
138
+ /* Corner radius: ~45% of the control height at every step. Held just short of
139
+ the 50% that would make it a pill — the flat run along the top and bottom
140
+ edge is what still reads as a sheet with corners cut off it. */
141
+ --neba-radius-xs: 0.625rem;
142
+ --neba-radius-sm: 0.75rem;
143
+ --neba-radius-md: 0.875rem;
144
+ --neba-radius-lg: 1.125rem;
145
+ --neba-radius-xl: 1.375rem;
146
+
147
+ /* Motion. Controls answer in color and depth, never by moving: a transform on
148
+ a control drags its label with it, and text that shifts under the cursor is
149
+ the thing that reads as cheap. */
150
+ --neba-ease: cubic-bezier(0.32, 0.72, 0, 1);
151
+ --neba-duration: 160ms;
152
+ /* Fills settle more slowly than edges and shadows. Pressing overrides this to
153
+ 0ms, so the colour lands on the frame of the click and then takes this long
154
+ to drain back out — the button itself holds the press, not just the bloom
155
+ under the cursor. */
156
+ --neba-duration-fill: 340ms;
157
+
158
+ /* Color families -------------------------------------------------------
159
+ Chroma sits just under the sRGB gamut boundary for each family's lightness
160
+ — around 90% of the maximum, close enough to be vivid and far enough that
161
+ the browser never has to clip. The old values used about two thirds of what
162
+ was available, which is what made the palette read muddy: the fills were
163
+ not too dark, they were too grey.
164
+
165
+ Lightness is not the free variable it looks like. `on-solid` is white, the
166
+ fill is only 88% opaque, and the pair has to clear 4.5:1 over a white page
167
+ — which pins every family with white text to the high 40s / low 50s. A
168
+ brighter *fill* means a dark ink, which is why `warning` is the one family
169
+ that already goes there. Verified per step, including hover and active.
170
+
171
+ `success` moved 152 → 148 and `info` 218 → 223. Both are hue regions where
172
+ sRGB is starved at mid lightness, and a few degrees toward the gamut's wide
173
+ side buys chroma that no amount of tuning at the original hue could. */
174
+
175
+ --neba-primary-solid: oklch(50% 0.198 262);
176
+ --neba-primary-solid-hover: oklch(45.5% 0.196 262);
177
+ --neba-primary-solid-active: oklch(38% 0.172 262);
178
+ --neba-primary-on-solid: oklch(99% 0.004 262);
179
+ --neba-primary-accent: oklch(55% 0.222 262);
180
+
181
+ --neba-secondary-solid: oklch(48% 0.038 262);
182
+ --neba-secondary-solid-hover: oklch(43.5% 0.036 262);
183
+ --neba-secondary-solid-active: oklch(36% 0.03 262);
184
+ --neba-secondary-on-solid: oklch(99% 0.003 262);
185
+ --neba-secondary-accent: oklch(54% 0.045 262);
186
+
187
+ --neba-success-solid: oklch(48.5% 0.136 148);
188
+ --neba-success-solid-hover: oklch(44% 0.126 148);
189
+ --neba-success-solid-active: oklch(36.5% 0.106 148);
190
+ --neba-success-on-solid: oklch(99% 0.004 148);
191
+ --neba-success-accent: oklch(52.5% 0.143 148);
192
+
193
+ --neba-warning-solid: oklch(80% 0.146 80);
194
+ --neba-warning-solid-hover: oklch(75.5% 0.148 78);
195
+ --neba-warning-solid-active: oklch(66.5% 0.14 74);
196
+ --neba-warning-on-solid: oklch(28% 0.06 70);
197
+ --neba-warning-accent: oklch(54.5% 0.111 68);
198
+
199
+ --neba-danger-solid: oklch(52.5% 0.194 30);
200
+ --neba-danger-solid-hover: oklch(48% 0.186 30);
201
+ --neba-danger-solid-active: oklch(40.5% 0.162 30);
202
+ --neba-danger-on-solid: oklch(99% 0.004 30);
203
+ --neba-danger-accent: oklch(56.5% 0.212 30);
204
+
205
+ --neba-info-solid: oklch(48.5% 0.088 223);
206
+ --neba-info-solid-hover: oklch(44% 0.081 223);
207
+ --neba-info-solid-active: oklch(36.5% 0.068 223);
208
+ --neba-info-on-solid: oklch(99% 0.004 223);
209
+ --neba-info-accent: oklch(53.5% 0.096 223);
210
+ }
211
+
212
+ /* ---------------------------------------------------------------------------
213
+ * Dark theme
214
+ *
215
+ * Fills stay mid-tone rather than pale, so a filled button on a dark surface
216
+ * does not glare; the readable-on-surface `accent` is what lightens.
217
+ * ------------------------------------------------------------------------- */
218
+
219
+ @media (prefers-color-scheme: dark) {
220
+ :root:not(.light):not([data-theme='light']) {
221
+ --neba-surface: oklch(19% 0.012 262);
222
+ --neba-fg: oklch(95% 0.008 262);
223
+ --neba-muted-fg: oklch(66% 0.014 262);
224
+ --neba-border: oklch(30% 0.012 262);
225
+
226
+ --neba-disabled-bg: oklch(27% 0.012 262);
227
+ --neba-disabled-fg: oklch(48% 0.012 262);
228
+ --neba-disabled-border: oklch(32% 0.012 262);
229
+
230
+ --neba-fill-alpha: 87%;
231
+ --neba-blur: blur(9px) saturate(1.3);
232
+
233
+ --neba-plate-edge: rgb(255 255 255 / 0.12);
234
+ --neba-plate-top: rgb(255 255 255 / 0.16);
235
+ --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0) 52%);
236
+
237
+ --neba-glass-bg: rgb(255 255 255 / 0.07);
238
+
239
+ --neba-panel: rgb(255 255 255 / 0.07);
240
+ --neba-panel-hover: rgb(255 255 255 / 0.1);
241
+ --neba-panel-press: rgb(255 255 255 / 0.13);
242
+
243
+ --neba-glow-on-fill: rgb(255 255 255 / 0.13);
244
+ --neba-flash-on-fill: rgb(255 255 255 / 0.18);
245
+
246
+ --neba-scrim: rgb(0 0 0 / 0.58);
247
+
248
+ --neba-shadow-ambient: rgb(0 0 0 / 0.55);
249
+
250
+ --neba-primary-solid: oklch(57% 0.208 262);
251
+ --neba-primary-solid-hover: oklch(62% 0.181 262);
252
+ --neba-primary-solid-active: oklch(48% 0.196 262);
253
+ --neba-primary-on-solid: oklch(99% 0.004 262);
254
+ --neba-primary-accent: oklch(78% 0.103 262);
255
+
256
+ --neba-secondary-solid: oklch(50% 0.036 262);
257
+ --neba-secondary-solid-hover: oklch(55% 0.036 262);
258
+ --neba-secondary-solid-active: oklch(41% 0.03 262);
259
+ --neba-secondary-on-solid: oklch(99% 0.003 262);
260
+ --neba-secondary-accent: oklch(78% 0.026 262);
261
+
262
+ --neba-success-solid: oklch(54% 0.147 148);
263
+ --neba-success-solid-hover: oklch(59% 0.161 148);
264
+ --neba-success-solid-active: oklch(45% 0.123 148);
265
+ --neba-success-on-solid: oklch(99% 0.004 148);
266
+ --neba-success-accent: oklch(78% 0.17 148);
267
+
268
+ --neba-warning-solid: oklch(76% 0.145 80);
269
+ --neba-warning-solid-hover: oklch(81% 0.154 80);
270
+ --neba-warning-solid-active: oklch(66% 0.126 80);
271
+ --neba-warning-on-solid: oklch(25% 0.06 70);
272
+ --neba-warning-accent: oklch(82% 0.14 80);
273
+
274
+ --neba-danger-solid: oklch(55% 0.207 30);
275
+ --neba-danger-solid-hover: oklch(60% 0.226 30);
276
+ --neba-danger-solid-active: oklch(46% 0.174 30);
277
+ --neba-danger-on-solid: oklch(99% 0.004 30);
278
+ --neba-danger-accent: oklch(76% 0.136 32);
279
+
280
+ --neba-info-solid: oklch(55% 0.098 223);
281
+ --neba-info-solid-hover: oklch(60% 0.107 223);
282
+ --neba-info-solid-active: oklch(46% 0.082 223);
283
+ --neba-info-on-solid: oklch(99% 0.004 223);
284
+ --neba-info-accent: oklch(79% 0.11 223);
285
+ }
286
+ }
287
+
288
+ .dark,
289
+ [data-theme='dark'] {
290
+ --neba-surface: oklch(19% 0.012 262);
291
+ --neba-fg: oklch(95% 0.008 262);
292
+ --neba-muted-fg: oklch(66% 0.014 262);
293
+ --neba-border: oklch(30% 0.012 262);
294
+
295
+ --neba-disabled-bg: oklch(27% 0.012 262);
296
+ --neba-disabled-fg: oklch(48% 0.012 262);
297
+ --neba-disabled-border: oklch(32% 0.012 262);
298
+
299
+ --neba-fill-alpha: 87%;
300
+ --neba-blur: blur(9px) saturate(1.3);
301
+
302
+ --neba-plate-edge: rgb(255 255 255 / 0.12);
303
+ --neba-plate-top: rgb(255 255 255 / 0.16);
304
+ --neba-sheen: linear-gradient(148deg, rgb(255 255 255 / 0.09), rgb(255 255 255 / 0) 52%);
305
+
306
+ --neba-glass-bg: rgb(255 255 255 / 0.07);
307
+
308
+ --neba-panel: rgb(255 255 255 / 0.07);
309
+ --neba-panel-hover: rgb(255 255 255 / 0.1);
310
+ --neba-panel-press: rgb(255 255 255 / 0.13);
311
+
312
+ --neba-glow-on-fill: rgb(255 255 255 / 0.13);
313
+ --neba-flash-on-fill: rgb(255 255 255 / 0.18);
314
+
315
+ --neba-scrim: rgb(0 0 0 / 0.58);
316
+
317
+ --neba-shadow-ambient: rgb(0 0 0 / 0.55);
318
+
319
+ --neba-primary-solid: oklch(57% 0.208 262);
320
+ --neba-primary-solid-hover: oklch(62% 0.181 262);
321
+ --neba-primary-solid-active: oklch(48% 0.196 262);
322
+ --neba-primary-on-solid: oklch(99% 0.004 262);
323
+ --neba-primary-accent: oklch(78% 0.103 262);
324
+
325
+ --neba-secondary-solid: oklch(50% 0.036 262);
326
+ --neba-secondary-solid-hover: oklch(55% 0.036 262);
327
+ --neba-secondary-solid-active: oklch(41% 0.03 262);
328
+ --neba-secondary-on-solid: oklch(99% 0.003 262);
329
+ --neba-secondary-accent: oklch(78% 0.026 262);
330
+
331
+ --neba-success-solid: oklch(54% 0.147 148);
332
+ --neba-success-solid-hover: oklch(59% 0.161 148);
333
+ --neba-success-solid-active: oklch(45% 0.123 148);
334
+ --neba-success-on-solid: oklch(99% 0.004 148);
335
+ --neba-success-accent: oklch(78% 0.17 148);
336
+
337
+ --neba-warning-solid: oklch(76% 0.145 80);
338
+ --neba-warning-solid-hover: oklch(81% 0.154 80);
339
+ --neba-warning-solid-active: oklch(66% 0.126 80);
340
+ --neba-warning-on-solid: oklch(25% 0.06 70);
341
+ --neba-warning-accent: oklch(82% 0.14 80);
342
+
343
+ --neba-danger-solid: oklch(55% 0.207 30);
344
+ --neba-danger-solid-hover: oklch(60% 0.226 30);
345
+ --neba-danger-solid-active: oklch(46% 0.174 30);
346
+ --neba-danger-on-solid: oklch(99% 0.004 30);
347
+ --neba-danger-accent: oklch(76% 0.136 32);
348
+
349
+ --neba-info-solid: oklch(55% 0.098 223);
350
+ --neba-info-solid-hover: oklch(60% 0.107 223);
351
+ --neba-info-solid-active: oklch(46% 0.082 223);
352
+ --neba-info-on-solid: oklch(99% 0.004 223);
353
+ --neba-info-accent: oklch(79% 0.11 223);
354
+ }
355
+
356
+ /* ---------------------------------------------------------------------------
357
+ * Derived tokens
358
+ *
359
+ * Fills, tints, hairlines and focus rings are all one base color at different
360
+ * strengths, so they are computed rather than hand-picked per theme. This block
361
+ * is repeated for every theme root because a custom property resolves its
362
+ * `var()`s on the element that declares it — declaring these only on `:root`
363
+ * would freeze them to the light values inside a `.dark` subtree.
364
+ * ------------------------------------------------------------------------- */
365
+
366
+ :root,
367
+ .dark,
368
+ .light,
369
+ [data-theme='dark'],
370
+ [data-theme='light'] {
371
+ --neba-primary-fill: color-mix(
372
+ in srgb,
373
+ var(--neba-primary-solid) var(--neba-fill-alpha),
374
+ transparent
375
+ );
376
+ --neba-primary-fill-hover: color-mix(
377
+ in srgb,
378
+ var(--neba-primary-solid-hover) var(--neba-fill-alpha),
379
+ transparent
380
+ );
381
+ --neba-primary-fill-active: color-mix(
382
+ in srgb,
383
+ var(--neba-primary-solid-active) var(--neba-fill-alpha),
384
+ transparent
385
+ );
386
+ --neba-primary-soft: color-mix(in srgb, var(--neba-primary-accent) 10%, transparent);
387
+ --neba-primary-soft-hover: color-mix(in srgb, var(--neba-primary-accent) 17%, transparent);
388
+ --neba-primary-soft-press: color-mix(in srgb, var(--neba-primary-accent) 25%, transparent);
389
+ --neba-primary-panel: color-mix(in srgb, var(--neba-primary-accent) 8%, var(--neba-glass-bg));
390
+ --neba-primary-panel-hover: color-mix(
391
+ in srgb,
392
+ var(--neba-primary-accent) 16%,
393
+ var(--neba-glass-bg)
394
+ );
395
+ --neba-primary-panel-press: color-mix(
396
+ in srgb,
397
+ var(--neba-primary-accent) 24%,
398
+ var(--neba-glass-bg)
399
+ );
400
+ --neba-primary-line: color-mix(in srgb, var(--neba-primary-accent) 22%, transparent);
401
+ --neba-primary-line-hover: color-mix(in srgb, var(--neba-primary-accent) 40%, transparent);
402
+ --neba-primary-ring: color-mix(in srgb, var(--neba-primary-accent) 55%, transparent);
403
+
404
+ --neba-secondary-fill: color-mix(
405
+ in srgb,
406
+ var(--neba-secondary-solid) var(--neba-fill-alpha),
407
+ transparent
408
+ );
409
+ --neba-secondary-fill-hover: color-mix(
410
+ in srgb,
411
+ var(--neba-secondary-solid-hover) var(--neba-fill-alpha),
412
+ transparent
413
+ );
414
+ --neba-secondary-fill-active: color-mix(
415
+ in srgb,
416
+ var(--neba-secondary-solid-active) var(--neba-fill-alpha),
417
+ transparent
418
+ );
419
+ --neba-secondary-soft: color-mix(in srgb, var(--neba-secondary-accent) 10%, transparent);
420
+ --neba-secondary-soft-hover: color-mix(in srgb, var(--neba-secondary-accent) 17%, transparent);
421
+ --neba-secondary-soft-press: color-mix(in srgb, var(--neba-secondary-accent) 25%, transparent);
422
+ --neba-secondary-panel: color-mix(in srgb, var(--neba-secondary-accent) 8%, var(--neba-glass-bg));
423
+ --neba-secondary-panel-hover: color-mix(
424
+ in srgb,
425
+ var(--neba-secondary-accent) 16%,
426
+ var(--neba-glass-bg)
427
+ );
428
+ --neba-secondary-panel-press: color-mix(
429
+ in srgb,
430
+ var(--neba-secondary-accent) 24%,
431
+ var(--neba-glass-bg)
432
+ );
433
+ --neba-secondary-line: color-mix(in srgb, var(--neba-secondary-accent) 22%, transparent);
434
+ --neba-secondary-line-hover: color-mix(in srgb, var(--neba-secondary-accent) 40%, transparent);
435
+ --neba-secondary-ring: color-mix(in srgb, var(--neba-secondary-accent) 55%, transparent);
436
+
437
+ --neba-success-fill: color-mix(
438
+ in srgb,
439
+ var(--neba-success-solid) var(--neba-fill-alpha),
440
+ transparent
441
+ );
442
+ --neba-success-fill-hover: color-mix(
443
+ in srgb,
444
+ var(--neba-success-solid-hover) var(--neba-fill-alpha),
445
+ transparent
446
+ );
447
+ --neba-success-fill-active: color-mix(
448
+ in srgb,
449
+ var(--neba-success-solid-active) var(--neba-fill-alpha),
450
+ transparent
451
+ );
452
+ --neba-success-soft: color-mix(in srgb, var(--neba-success-accent) 10%, transparent);
453
+ --neba-success-soft-hover: color-mix(in srgb, var(--neba-success-accent) 17%, transparent);
454
+ --neba-success-soft-press: color-mix(in srgb, var(--neba-success-accent) 25%, transparent);
455
+ --neba-success-panel: color-mix(in srgb, var(--neba-success-accent) 8%, var(--neba-glass-bg));
456
+ --neba-success-panel-hover: color-mix(
457
+ in srgb,
458
+ var(--neba-success-accent) 16%,
459
+ var(--neba-glass-bg)
460
+ );
461
+ --neba-success-panel-press: color-mix(
462
+ in srgb,
463
+ var(--neba-success-accent) 24%,
464
+ var(--neba-glass-bg)
465
+ );
466
+ --neba-success-line: color-mix(in srgb, var(--neba-success-accent) 22%, transparent);
467
+ --neba-success-line-hover: color-mix(in srgb, var(--neba-success-accent) 40%, transparent);
468
+ --neba-success-ring: color-mix(in srgb, var(--neba-success-accent) 55%, transparent);
469
+
470
+ --neba-warning-fill: color-mix(
471
+ in srgb,
472
+ var(--neba-warning-solid) var(--neba-fill-alpha),
473
+ transparent
474
+ );
475
+ --neba-warning-fill-hover: color-mix(
476
+ in srgb,
477
+ var(--neba-warning-solid-hover) var(--neba-fill-alpha),
478
+ transparent
479
+ );
480
+ --neba-warning-fill-active: color-mix(
481
+ in srgb,
482
+ var(--neba-warning-solid-active) var(--neba-fill-alpha),
483
+ transparent
484
+ );
485
+ --neba-warning-soft: color-mix(in srgb, var(--neba-warning-accent) 12%, transparent);
486
+ --neba-warning-soft-hover: color-mix(in srgb, var(--neba-warning-accent) 20%, transparent);
487
+ --neba-warning-soft-press: color-mix(in srgb, var(--neba-warning-accent) 28%, transparent);
488
+ --neba-warning-panel: color-mix(in srgb, var(--neba-warning-accent) 8%, var(--neba-glass-bg));
489
+ --neba-warning-panel-hover: color-mix(
490
+ in srgb,
491
+ var(--neba-warning-accent) 16%,
492
+ var(--neba-glass-bg)
493
+ );
494
+ --neba-warning-panel-press: color-mix(
495
+ in srgb,
496
+ var(--neba-warning-accent) 24%,
497
+ var(--neba-glass-bg)
498
+ );
499
+ --neba-warning-line: color-mix(in srgb, var(--neba-warning-accent) 26%, transparent);
500
+ --neba-warning-line-hover: color-mix(in srgb, var(--neba-warning-accent) 44%, transparent);
501
+ --neba-warning-ring: color-mix(in srgb, var(--neba-warning-accent) 55%, transparent);
502
+
503
+ --neba-danger-fill: color-mix(
504
+ in srgb,
505
+ var(--neba-danger-solid) var(--neba-fill-alpha),
506
+ transparent
507
+ );
508
+ --neba-danger-fill-hover: color-mix(
509
+ in srgb,
510
+ var(--neba-danger-solid-hover) var(--neba-fill-alpha),
511
+ transparent
512
+ );
513
+ --neba-danger-fill-active: color-mix(
514
+ in srgb,
515
+ var(--neba-danger-solid-active) var(--neba-fill-alpha),
516
+ transparent
517
+ );
518
+ --neba-danger-soft: color-mix(in srgb, var(--neba-danger-accent) 10%, transparent);
519
+ --neba-danger-soft-hover: color-mix(in srgb, var(--neba-danger-accent) 17%, transparent);
520
+ --neba-danger-soft-press: color-mix(in srgb, var(--neba-danger-accent) 25%, transparent);
521
+ --neba-danger-panel: color-mix(in srgb, var(--neba-danger-accent) 8%, var(--neba-glass-bg));
522
+ --neba-danger-panel-hover: color-mix(
523
+ in srgb,
524
+ var(--neba-danger-accent) 16%,
525
+ var(--neba-glass-bg)
526
+ );
527
+ --neba-danger-panel-press: color-mix(
528
+ in srgb,
529
+ var(--neba-danger-accent) 24%,
530
+ var(--neba-glass-bg)
531
+ );
532
+ --neba-danger-line: color-mix(in srgb, var(--neba-danger-accent) 22%, transparent);
533
+ --neba-danger-line-hover: color-mix(in srgb, var(--neba-danger-accent) 40%, transparent);
534
+ --neba-danger-ring: color-mix(in srgb, var(--neba-danger-accent) 55%, transparent);
535
+
536
+ --neba-info-fill: color-mix(in srgb, var(--neba-info-solid) var(--neba-fill-alpha), transparent);
537
+ --neba-info-fill-hover: color-mix(
538
+ in srgb,
539
+ var(--neba-info-solid-hover) var(--neba-fill-alpha),
540
+ transparent
541
+ );
542
+ --neba-info-fill-active: color-mix(
543
+ in srgb,
544
+ var(--neba-info-solid-active) var(--neba-fill-alpha),
545
+ transparent
546
+ );
547
+ --neba-info-soft: color-mix(in srgb, var(--neba-info-accent) 10%, transparent);
548
+ --neba-info-soft-hover: color-mix(in srgb, var(--neba-info-accent) 17%, transparent);
549
+ --neba-info-soft-press: color-mix(in srgb, var(--neba-info-accent) 25%, transparent);
550
+ --neba-info-panel: color-mix(in srgb, var(--neba-info-accent) 8%, var(--neba-glass-bg));
551
+ --neba-info-panel-hover: color-mix(in srgb, var(--neba-info-accent) 16%, var(--neba-glass-bg));
552
+ --neba-info-panel-press: color-mix(in srgb, var(--neba-info-accent) 24%, var(--neba-glass-bg));
553
+ --neba-info-line: color-mix(in srgb, var(--neba-info-accent) 22%, transparent);
554
+ --neba-info-line-hover: color-mix(in srgb, var(--neba-info-accent) 40%, transparent);
555
+ --neba-info-ring: color-mix(in srgb, var(--neba-info-accent) 55%, transparent);
556
+ }
557
+
558
+ /* ---------------------------------------------------------------------------
559
+ * Sheet titles
560
+ *
561
+ * A `title` is styled by the element the component renders around it — Card,
562
+ * Alert and Dialog all do this — so a plain string needs nothing. But a sheet
563
+ * that belongs in the document outline has to be given a real heading,
564
+ * `title={<h2>…</h2>}`, and that heading arrives with the browser's own 1.5em
565
+ * bold and its margins, which would ignore the type scale just set.
566
+ *
567
+ * This is in CSS rather than in Tailwind because the selector is the point:
568
+ * `[&_:where(h1,h2,h3,h4,h5,h6)]:[font:inherit]` is expressible and nobody
569
+ * would want to read it twice.
570
+ * ------------------------------------------------------------------------- */
571
+
572
+ .neba-title :where(h1, h2, h3, h4, h5, h6) {
573
+ font: inherit;
574
+ margin: 0;
575
+ }
576
+
577
+ /* ---------------------------------------------------------------------------
578
+ * Interaction light
579
+ *
580
+ * Two stacked layers on an interactive surface, both reading `--n-mx`/`--n-my`
581
+ * — the pointer position, written straight to the element's inline style on
582
+ * `pointermove` — and both no-ops until those slots have colour in them.
583
+ *
584
+ * This lives in CSS rather than in Tailwind arbitrary variants because the
585
+ * selectors are pseudo-elements carrying gradients: `[&::before]:[background:
586
+ * radial-gradient(...)]` is technically expressible and completely unreadable.
587
+ *
588
+ * Cost per hovered button: two composited layers and a gradient repaint of one
589
+ * small box, on frames the pointer already moved in. Nothing reflows, and React
590
+ * never re-renders — the component writes the two custom properties directly.
591
+ * ------------------------------------------------------------------------- */
592
+
593
+ .neba-glow::before,
594
+ .neba-glow::after {
595
+ content: '';
596
+ position: absolute;
597
+ inset: 0;
598
+ border-radius: inherit;
599
+ pointer-events: none;
600
+ opacity: 0;
601
+ }
602
+
603
+ /* Pointer spotlight: a soft bloom trailing the cursor across the surface. */
604
+ .neba-glow::before {
605
+ background: radial-gradient(
606
+ 5.5rem circle at var(--n-mx, 50%) var(--n-my, 50%),
607
+ var(--n-glow, transparent),
608
+ transparent 70%
609
+ );
610
+ transition: opacity 240ms var(--neba-ease);
611
+ }
612
+
613
+ @media (hover: hover) {
614
+ .neba-glow:hover::before {
615
+ opacity: 1;
616
+ }
617
+ }
618
+
619
+ /*
620
+ * Release afterglow. The asymmetry is the whole trick: 0ms in, ~900ms out, so
621
+ * the flash lands on the frame of the press and is still visibly draining a
622
+ * beat after the finger lifts. No JavaScript, no ripple element, no timers.
623
+ */
624
+ .neba-glow::after {
625
+ background: radial-gradient(
626
+ 4.5rem circle at var(--n-mx, 50%) var(--n-my, 50%),
627
+ var(--n-flash, transparent),
628
+ transparent 72%
629
+ );
630
+ transition: opacity 900ms cubic-bezier(0.22, 1, 0.36, 1);
631
+ }
632
+
633
+ .neba-glow:active::after {
634
+ opacity: 1;
635
+ transition-duration: 0ms;
636
+ }
637
+
638
+ /* ---------------------------------------------------------------------------
639
+ * Loading
640
+ *
641
+ * The progress indicators are the one place in the library where something
642
+ * moves on its own, and the only place it is allowed to: an indeterminate
643
+ * indicator that does not move is a decoration.
644
+ *
645
+ * The rule against transforms still holds, because none of these moves anything
646
+ * with text in it. The sweep slides an empty box along a 4px groove, the ring
647
+ * turns a glyph — the same allowance the Select chevron takes — and the plates
648
+ * only change colour.
649
+ *
650
+ * They are real CSS rather than Tailwind arbitrary variants for the reason
651
+ * everything else in this file is: keyframes are not a variant, so there is
652
+ * nothing to express them with.
653
+ * ------------------------------------------------------------------------- */
654
+
655
+ /*
656
+ * The indeterminate bar: a short segment crossing the groove.
657
+ *
658
+ * `inset-inline-start` rather than a transform, so it runs the other way under
659
+ * RTL without being told. The cost is a layout pass per frame, confined to a
660
+ * box a few pixels tall — which is the trade that makes the rule affordable
661
+ * here rather than an exception to it.
662
+ */
663
+ @keyframes neba-sweep {
664
+ from {
665
+ inset-inline-start: -45%;
666
+ }
667
+ to {
668
+ inset-inline-start: 100%;
669
+ }
670
+ }
671
+
672
+ .neba-sweep {
673
+ position: absolute;
674
+ inset-block: 0;
675
+ inline-size: 45%;
676
+ animation: neba-sweep 1.15s var(--neba-ease) infinite;
677
+ }
678
+
679
+ @keyframes neba-ring-spin {
680
+ to {
681
+ rotate: 360deg;
682
+ }
683
+ }
684
+
685
+ .neba-ring-spin {
686
+ transform-origin: center;
687
+ animation: neba-ring-spin 900ms linear infinite;
688
+ }
689
+
690
+ /*
691
+ * The plates light in sequence, each one held back by its own index. Colour
692
+ * only — the plates never move, so a row of them reads as a surface being
693
+ * written to rather than as something bouncing.
694
+ */
695
+ @keyframes neba-plate-wave {
696
+ 0%,
697
+ 70%,
698
+ 100% {
699
+ background-color: var(--n-soft);
700
+ box-shadow: var(--neba-plate-glass);
701
+ }
702
+ 35% {
703
+ background-color: var(--n-fill);
704
+ box-shadow: var(--neba-plate-solid);
705
+ }
706
+ }
707
+
708
+ .neba-plate-wave {
709
+ animation: neba-plate-wave 1.2s var(--neba-ease) infinite;
710
+ animation-delay: calc(var(--n-i, 0) * 120ms);
711
+ }
712
+
713
+ /* The reduced-motion stand-in for the sweep: the segment stops travelling and
714
+ the whole groove pulses instead. Colour, which is the axis every other state
715
+ in the library already uses. */
716
+ @keyframes neba-progress-pulse {
717
+ 0%,
718
+ 100% {
719
+ background-color: var(--n-soft-press);
720
+ }
721
+ 50% {
722
+ background-color: var(--n-fill);
723
+ }
724
+ }
725
+
726
+ /* Respect a reduced-motion preference. Both layers track the pointer, so they
727
+ are motion in the sense that matters here — drop them entirely. */
728
+ @media (prefers-reduced-motion: reduce) {
729
+ :root {
730
+ --neba-duration: 0ms;
731
+ --neba-duration-fill: 0ms;
732
+ }
733
+
734
+ .neba-glow::before,
735
+ .neba-glow::after {
736
+ display: none;
737
+ }
738
+
739
+ /*
740
+ * The indicators are not switched off, because an indeterminate one that
741
+ * holds still says the opposite of what it is for. The travelling segment
742
+ * becomes a colour pulse, and the two that genuinely have to keep turning or
743
+ * cycling are slowed to where they stop reading as motion.
744
+ */
745
+ .neba-sweep {
746
+ inset-inline-start: 0;
747
+ inline-size: 100%;
748
+ animation: neba-progress-pulse 1.8s var(--neba-ease) infinite;
749
+ }
750
+
751
+ .neba-ring-spin {
752
+ animation-duration: 2.4s;
753
+ }
754
+
755
+ .neba-plate-wave {
756
+ animation-duration: 2.4s;
757
+ }
758
+ }