@waveso/ui 0.15.1 → 0.17.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 (87) hide show
  1. package/README.md +13 -12
  2. package/dist/accordion.js +2 -2
  3. package/dist/action-bar.js +4 -4
  4. package/dist/alert-dialog.js +5 -5
  5. package/dist/alert.d.ts +8 -1
  6. package/dist/alert.js +13 -4
  7. package/dist/animate.d.ts +1 -1
  8. package/dist/animate.js +2 -9
  9. package/dist/autocomplete.js +3 -3
  10. package/dist/avatar.d.ts +1 -1
  11. package/dist/avatar.js +4 -4
  12. package/dist/badge.d.ts +1 -1
  13. package/dist/badge.js +6 -6
  14. package/dist/button-group.d.ts +4 -2
  15. package/dist/button-group.js +5 -4
  16. package/dist/button.d.ts +1 -1
  17. package/dist/button.js +6 -6
  18. package/dist/card.d.ts +11 -4
  19. package/dist/card.js +27 -14
  20. package/dist/checkbox.js +2 -2
  21. package/dist/combobox.js +4 -4
  22. package/dist/context-menu.d.ts +2 -1
  23. package/dist/context-menu.js +14 -14
  24. package/dist/dialog.js +5 -5
  25. package/dist/drawer.js +1 -1
  26. package/dist/encrypted-text.d.ts +15 -1
  27. package/dist/encrypted-text.js +205 -67
  28. package/dist/field.d.ts +21 -3
  29. package/dist/field.js +36 -8
  30. package/dist/fieldset.d.ts +14 -0
  31. package/dist/fieldset.js +26 -0
  32. package/dist/form.d.ts +14 -14
  33. package/dist/form.js +7 -4
  34. package/dist/glyph-matrix.d.ts +25 -0
  35. package/dist/glyph-matrix.js +266 -0
  36. package/dist/gradient-reveal-text.d.ts +3 -3
  37. package/dist/gradient-reveal-text.js +8 -8
  38. package/dist/input-group.js +1 -1
  39. package/dist/input-otp.d.ts +3 -3
  40. package/dist/input-otp.js +2 -2
  41. package/dist/input.js +1 -1
  42. package/dist/item.d.ts +1 -1
  43. package/dist/item.js +17 -11
  44. package/dist/kbd.d.ts +1 -1
  45. package/dist/kbd.js +2 -2
  46. package/dist/label.js +1 -1
  47. package/dist/lib/merge-refs.d.ts +9 -0
  48. package/dist/lib/merge-refs.js +16 -0
  49. package/dist/lib/utils.js +7 -0
  50. package/dist/masonry.d.ts +1 -1
  51. package/dist/masonry.js +4 -2
  52. package/dist/menu.d.ts +2 -1
  53. package/dist/menu.js +7 -7
  54. package/dist/menubar.js +1 -1
  55. package/dist/pagination.js +1 -1
  56. package/dist/popover.js +1 -1
  57. package/dist/preview-card.js +1 -1
  58. package/dist/progress.js +2 -2
  59. package/dist/radio-group.js +1 -1
  60. package/dist/radio.js +2 -2
  61. package/dist/resizable.d.ts +43 -0
  62. package/dist/resizable.js +53 -0
  63. package/dist/scroll-area.d.ts +13 -2
  64. package/dist/scroll-area.js +26 -7
  65. package/dist/select.js +4 -4
  66. package/dist/separator.js +1 -1
  67. package/dist/sidebar.js +12 -12
  68. package/dist/slider.js +3 -3
  69. package/dist/spinner.d.ts +7 -0
  70. package/dist/spinner.js +24 -6
  71. package/dist/styles.css +581 -194
  72. package/dist/swipe-deck.d.ts +98 -0
  73. package/dist/swipe-deck.js +931 -0
  74. package/dist/switch.js +6 -6
  75. package/dist/table.d.ts +7 -1
  76. package/dist/table.js +40 -9
  77. package/dist/tabs.js +3 -3
  78. package/dist/textarea.js +1 -1
  79. package/dist/toast.js +4 -4
  80. package/dist/toggle-group.js +3 -3
  81. package/dist/toggle.d.ts +1 -1
  82. package/dist/toggle.js +11 -6
  83. package/dist/tooltip.d.ts +3 -1
  84. package/dist/tooltip.js +10 -6
  85. package/dist/video-text.d.ts +25 -0
  86. package/dist/video-text.js +147 -0
  87. package/package.json +6 -1
package/dist/styles.css CHANGED
@@ -42,7 +42,10 @@
42
42
  * circular arc: softer, friendlier. It composes with `border-radius` and
43
43
  * respects borders / rings / shadows, and degrades to normal rounding where
44
44
  * unsupported (Chrome 2025+ today; Safari/Firefox pending). Pills and circles
45
- * opt back out — a squircled circle reads as an app icon, not an avatar.
45
+ * opt back out — a squircled circle reads as an app icon, not an avatar — and
46
+ * the `::before`/`::after` variants matter: a pseudo-element circle (e.g. the
47
+ * radio dot's `after:rounded-full`) inherits squircle from the `::after` rule
48
+ * above, so it needs its own round opt-out or it renders as a rounded square.
46
49
  * `::file-selector-button` is listed too — `*` doesn't match pseudo-elements,
47
50
  * so the file-input button would otherwise stay round at the squircle radius. */
48
51
  @layer base {
@@ -52,7 +55,9 @@
52
55
  ::file-selector-button {
53
56
  corner-shape: squircle;
54
57
  }
55
- [class*="rounded-full"] {
58
+ [class*="rounded-full"],
59
+ [class*="rounded-full"]::before,
60
+ [class*="rounded-full"]::after {
56
61
  corner-shape: round;
57
62
  }
58
63
  }
@@ -65,115 +70,232 @@
65
70
  * `.graphite`, `.ink` — and it always beats the OS. A theme class sits on the
66
71
  * same <html> as `:root`, so every var() alias below (inverse, presence)
67
72
  * re-resolves to the active theme.
73
+ *
74
+ * Every default here sits in `:where(:root)` (zero specificity), so an app's
75
+ * own `:root` override wins whatever order the stylesheets load in.
68
76
  * ------------------------------------------------------------------------- */
69
77
 
70
- :root {
78
+ :where(:root) {
71
79
  /* ========================================================================
72
80
  * Palettes — raw primitives. No `--color-` prefix on purpose: these live in
73
81
  * `:root`, not `@theme`, so they generate NO utilities (no `bg-graphite-300`).
74
82
  * They're referenced by the semantic tokens below, never used directly.
75
83
  * ====================================================================== */
76
84
 
77
- /* Wave — brand ramp. 500 = Wave Blue #0074DE.
78
- * A true cobalt signal: calm, trustable, solid, constant. */
79
- --wave-50: oklch(0.974 0.014 238); /* #EEF8FF */
80
- --wave-100: oklch(0.937 0.034 239); /* #D6EEFF */
81
- --wave-200: oklch(0.876 0.068 240); /* #AEDDFF */
82
- --wave-300: oklch(0.793 0.112 245); /* #7BC3FF */
83
- --wave-400: oklch(0.700 0.164 250); /* #3EA3FF */
84
- --wave-500: oklch(0.566 0.184 255); /* #0074DE */
85
- --wave-600: oklch(0.506 0.175 256); /* #0061C6 */
86
- --wave-700: oklch(0.429 0.150 257); /* #004C9F */
87
- --wave-800: oklch(0.351 0.118 255); /* #003976 */
88
- --wave-900: oklch(0.284 0.088 255); /* #042954 */
89
- --wave-950: oklch(0.226 0.060 255); /* #061C37 */
90
-
91
- /* Status ramps — shared across every theme (mode-stable, like Wave).
92
- * 500 = the locked status colour; the semantic layer picks a swatch per
93
- * light/dark (base + hover) so components never carry a `dark:` for it. */
94
- --success-50: oklch(0.975 0.014 155); /* #F0FAF3 */
95
- --success-100: oklch(0.951 0.034 155); /* #DEF6E5 */
96
- --success-200: oklch(0.911 0.068 155); /* #BFF0CE */
97
- --success-300: oklch(0.857 0.113 155); /* #91E6B0 */
98
- --success-400: oklch(0.797 0.165 155); /* #4FDB8D */
99
- --success-500: oklch(0.710 0.185 155); /* #00C26B */
100
- --success-600: oklch(0.620 0.176 155); /* #00A354 */
101
- --success-700: oklch(0.504 0.151 155); /* #007C3B */
102
- --success-800: oklch(0.388 0.119 155); /* #005626 */
103
- --success-900: oklch(0.287 0.088 155); /* #003615 */
104
- --success-950: oklch(0.200 0.060 155); /* #001D09 */
105
-
106
- --warning-50: oklch(0.975 0.012 93); /* #F9F7EE */
107
- --warning-100: oklch(0.962 0.029 93); /* #F9F2DD */
108
- --warning-200: oklch(0.940 0.058 93); /* #F8EBC0 */
109
- --warning-300: oklch(0.911 0.095 93); /* #F7E198 */
110
- --warning-400: oklch(0.878 0.139 93); /* #F6D460 */
111
- --warning-500: oklch(0.830 0.156 93); /* #EAC434 */
112
- --warning-600: oklch(0.719 0.148 93); /* #C4A100 */
113
- --warning-700: oklch(0.576 0.127 93); /* #937600 */
114
- --warning-800: oklch(0.432 0.100 93); /* #634E00 */
115
- --warning-900: oklch(0.308 0.075 93); /* #3D2E00 */
116
- --warning-950: oklch(0.200 0.051 93); /* #1E1500 */
117
-
118
- --destructive-50: oklch(0.975 0.015 25); /* #FFF3F2 */
119
- --destructive-100: oklch(0.943 0.037 25); /* #FFE3E0 */
120
- --destructive-200: oklch(0.890 0.074 25); /* #FFC8C2 */
121
- --destructive-300: oklch(0.817 0.122 25); /* #FFA39B */
122
- --destructive-400: oklch(0.736 0.178 25); /* #FF756E */
123
- --destructive-500: oklch(0.620 0.200 25); /* #E64343 */
124
- --destructive-600: oklch(0.546 0.190 25); /* #C72D32 */
125
- --destructive-700: oklch(0.451 0.163 25); /* #9C1C21 */
126
- --destructive-800: oklch(0.355 0.128 25); /* #701115 */
127
- --destructive-900: oklch(0.272 0.096 25); /* #4C0A0C */
128
- --destructive-950: oklch(0.200 0.065 25); /* #2D0606 */
85
+ /* Wave Blue — the brand, and the first of the Wave Palettes. 500 = #0074DE.
86
+ * A true cobalt signal: calm, trustable, solid, constant. Grown on the Wave
87
+ * Light Curve with the Wave Hues Drift, like every Wave Palette below. */
88
+ --wave-blue-50: oklch(0.975 0.011 242.8); /* #F1F8FE */
89
+ --wave-blue-100: oklch(0.944 0.024 242.8); /* #DFEFFC */
90
+ --wave-blue-200: oklch(0.888 0.049 247.2); /* #C1DEFA */
91
+ --wave-blue-300: oklch(0.804 0.095 248.7); /* #8EC4FA */
92
+ --wave-blue-400: oklch(0.686 0.167 250.4); /* #389EFC */
93
+ --wave-blue-500: oklch(0.566 0.184 254.6); /* #0074DE */
94
+ --wave-blue-600: oklch(0.471 0.173 258.4); /* #0655B9 */
95
+ --wave-blue-700: oklch(0.391 0.145 259.8); /* #0A3F91 */
96
+ --wave-blue-800: oklch(0.320 0.115 260.3); /* #092F6C */
97
+ --wave-blue-900: oklch(0.258 0.087 260.3); /* #07214D */
98
+ --wave-blue-950: oklch(0.202 0.067 261); /* #041434 */
99
+
100
+ /* The Wave Palettes — Wave Blue's nine signature colours: Wave Indigo,
101
+ * Wave Purple, Wave Pink, Wave Red, Wave Orange, Wave Yellow, Wave Lime,
102
+ * Wave Green and Wave Teal. Each hue is placed by hand, in a space of its
103
+ * own on the colour wheel and evenly apart from its neighbours, and every
104
+ * step is grown on the Wave Light Curve with the Wave Hues Drift (see
105
+ * Colors → Wave Light Curve and Hues Drift). A step has the same luminance
106
+ * in every palette, so the same contrast in every colour. Shared across
107
+ * every theme; Display-P3 screens get the more vivid values in the block
108
+ * after this one, at the same luminance. */
109
+ /* Wave Indigo */
110
+ --wave-indigo-50: oklch(0.976 0.010 287.9); /* #F6F6FE */
111
+ --wave-indigo-100: oklch(0.946 0.022 287.9); /* #ECEBFC */
112
+ --wave-indigo-200: oklch(0.893 0.047 286.3); /* #D8D8FA */
113
+ --wave-indigo-300: oklch(0.811 0.091 285.9); /* #BCB9FA */
114
+ --wave-indigo-400: oklch(0.698 0.160 284.3); /* #948DFC */
115
+ --wave-indigo-500: oklch(0.587 0.234 283); /* #715AFF */
116
+ --wave-indigo-600: oklch(0.501 0.283 283); /* #5F1AFA */
117
+ --wave-indigo-700: oklch(0.412 0.225 283); /* #4619BD */
118
+ --wave-indigo-800: oklch(0.335 0.171 283); /* #321889 */
119
+ --wave-indigo-900: oklch(0.269 0.128 283); /* #231361 */
120
+ --wave-indigo-950: oklch(0.210 0.094 283); /* #150C41 */
121
+
122
+ /* Wave Purple */
123
+ --wave-purple-50: oklch(0.976 0.013 314.5); /* #FAF5FD */
124
+ --wave-purple-100: oklch(0.948 0.029 314.5); /* #F5E8FC */
125
+ --wave-purple-200: oklch(0.896 0.062 313.6); /* #ECD1FA */
126
+ --wave-purple-300: oklch(0.818 0.117 312); /* #DDADFB */
127
+ --wave-purple-400: oklch(0.709 0.200 310); /* #C777FC */
128
+ --wave-purple-500: oklch(0.604 0.289 309); /* #B323FF */
129
+ --wave-purple-600: oklch(0.498 0.246 309); /* #8B0FC9 */
130
+ --wave-purple-700: oklch(0.410 0.192 309); /* #681496 */
131
+ --wave-purple-800: oklch(0.334 0.146 309); /* #4C136D */
132
+ --wave-purple-900: oklch(0.269 0.110 309); /* #360F4E */
133
+ --wave-purple-950: oklch(0.210 0.081 309); /* #230934 */
134
+
135
+ /* Wave Pink */
136
+ --wave-pink-50: oklch(0.977 0.013 337.3); /* #FEF4FB */
137
+ --wave-pink-100: oklch(0.949 0.030 337.3); /* #FCE7F5 */
138
+ --wave-pink-200: oklch(0.898 0.065 337.3); /* #FBCEED */
139
+ --wave-pink-300: oklch(0.822 0.128 338.1); /* #FBA4E1 */
140
+ --wave-pink-400: oklch(0.717 0.238 339.2); /* #FD56D3 */
141
+ --wave-pink-500: oklch(0.600 0.258 342); /* #DC01AA */
142
+ --wave-pink-600: oklch(0.492 0.202 346.2); /* #A90C78 */
143
+ --wave-pink-700: oklch(0.406 0.157 348.7); /* #801157 */
144
+ --wave-pink-800: oklch(0.331 0.120 348.8); /* #5E103F */
145
+ --wave-pink-900: oklch(0.267 0.090 348.8); /* #430D2D */
146
+ --wave-pink-950: oklch(0.209 0.066 349.3); /* #2D081D */
147
+
148
+ /* Wave Red */
149
+ --wave-red-50: oklch(0.976 0.009 11.6); /* #FDF5F5 */
150
+ --wave-red-100: oklch(0.948 0.022 12.1); /* #FCE8EA */
151
+ --wave-red-200: oklch(0.896 0.047 13.8); /* #FAD1D3 */
152
+ --wave-red-300: oklch(0.818 0.095 14.5); /* #FBAAB0 */
153
+ --wave-red-400: oklch(0.708 0.180 16.8); /* #FC6978 */
154
+ --wave-red-500: oklch(0.592 0.217 21); /* #E22841 */
155
+ --wave-red-600: oklch(0.489 0.191 21); /* #B30D2C */
156
+ --wave-red-700: oklch(0.404 0.150 21); /* #871222 */
157
+ --wave-red-800: oklch(0.329 0.114 21); /* #63111A */
158
+ --wave-red-900: oklch(0.266 0.086 21); /* #470D12 */
159
+ --wave-red-950: oklch(0.208 0.063 21); /* #2F080B */
160
+
161
+ /* Wave Orange */
162
+ --wave-orange-50: oklch(0.975 0.022 85.9); /* #FDF6E7 */
163
+ --wave-orange-100: oklch(0.945 0.047 84.2); /* #FCEBCA */
164
+ --wave-orange-200: oklch(0.892 0.086 79.1); /* #FAD59A */
165
+ --wave-orange-300: oklch(0.813 0.133 67.8); /* #FAB05B */
166
+ --wave-orange-400: oklch(0.699 0.163 57.3); /* #E77F16 */
167
+ --wave-orange-500: oklch(0.579 0.154 49.5); /* #BF5801 */
168
+ --wave-orange-600: oklch(0.479 0.139 42.6); /* #9A3B07 */
169
+ --wave-orange-700: oklch(0.397 0.117 39.3); /* #782A0B */
170
+ --wave-orange-800: oklch(0.325 0.092 38.1); /* #591F0B */
171
+ --wave-orange-900: oklch(0.262 0.069 38.1); /* #3F1608 */
172
+ --wave-orange-950: oklch(0.206 0.052 37.1); /* #2B0D05 */
173
+
174
+ /* Wave Yellow */
175
+ --wave-yellow-50: oklch(0.974 0.031 107.1); /* #F8F8E1 */
176
+ --wave-yellow-100: oklch(0.942 0.071 104.9); /* #F2EFB8 */
177
+ --wave-yellow-200: oklch(0.887 0.129 101.5); /* #EBDD72 */
178
+ --wave-yellow-300: oklch(0.804 0.153 98.7); /* #D8BF31 */
179
+ --wave-yellow-400: oklch(0.686 0.136 93.8); /* #B69715 */
180
+ --wave-yellow-500: oklch(0.567 0.116 88); /* #937100 */
181
+ --wave-yellow-600: oklch(0.468 0.095 78.3); /* #775206 */
182
+ --wave-yellow-700: oklch(0.389 0.078 69.3); /* #5F3C0B */
183
+ --wave-yellow-800: oklch(0.319 0.062 65); /* #482B0A */
184
+ --wave-yellow-900: oklch(0.258 0.048 61); /* #341E08 */
185
+ --wave-yellow-950: oklch(0.203 0.037 57.3); /* #231205 */
186
+
187
+ /* Wave Lime */
188
+ --wave-lime-50: oklch(0.973 0.031 117.4); /* #F4F9E2 */
189
+ --wave-lime-100: oklch(0.940 0.071 117.8); /* #E7F2BD */
190
+ --wave-lime-200: oklch(0.882 0.129 119.9); /* #CDE580 */
191
+ --wave-lime-300: oklch(0.795 0.178 122.1); /* #A9CD30 */
192
+ --wave-lime-400: oklch(0.676 0.163 124); /* #82A713 */
193
+ --wave-lime-500: oklch(0.556 0.138 124); /* #638000 */
194
+ --wave-lime-600: oklch(0.458 0.110 124); /* #4B6108 */
195
+ --wave-lime-700: oklch(0.379 0.087 124); /* #39490A */
196
+ --wave-lime-800: oklch(0.311 0.067 124); /* #2A360A */
197
+ --wave-lime-900: oklch(0.252 0.050 124); /* #1D2608 */
198
+ --wave-lime-950: oklch(0.197 0.037 124); /* #121805 */
199
+
200
+ /* Wave Green */
201
+ --wave-green-50: oklch(0.971 0.031 156.1); /* #E6FCED */
202
+ --wave-green-100: oklch(0.936 0.071 156.4); /* #C4F9D7 */
203
+ --wave-green-200: oklch(0.875 0.129 157.7); /* #87F0B5 */
204
+ --wave-green-300: oklch(0.786 0.165 159.4); /* #36D993 */
205
+ --wave-green-400: oklch(0.668 0.147 160); /* #15AF75 */
206
+ --wave-green-500: oklch(0.550 0.125 160); /* #008758 */
207
+ --wave-green-600: oklch(0.453 0.099 160.5); /* #086643 */
208
+ --wave-green-700: oklch(0.376 0.075 162.6); /* #0D4D35 */
209
+ --wave-green-800: oklch(0.308 0.057 163.9); /* #0C3928 */
210
+ --wave-green-900: oklch(0.250 0.042 165.8); /* #09281D */
211
+ --wave-green-950: oklch(0.196 0.030 168.8); /* #061913 */
212
+
213
+ /* Wave Teal */
214
+ --wave-teal-50: oklch(0.971 0.031 197); /* #DEFCFD */
215
+ --wave-teal-100: oklch(0.937 0.071 197.9); /* #B1FAFB */
216
+ --wave-teal-200: oklch(0.876 0.128 200); /* #54EFF7 */
217
+ --wave-teal-300: oklch(0.793 0.124 202.4); /* #37D3DE */
218
+ --wave-teal-400: oklch(0.674 0.112 205.5); /* #16AAB8 */
219
+ --wave-teal-500: oklch(0.556 0.096 208.9); /* #008291 */
220
+ --wave-teal-600: oklch(0.458 0.078 214.6); /* #0A6272 */
221
+ --wave-teal-700: oklch(0.380 0.062 216); /* #0D4A57 */
222
+ --wave-teal-800: oklch(0.311 0.048 217.3); /* #0C3640 */
223
+ --wave-teal-900: oklch(0.252 0.037 219.9); /* #09262E */
224
+ --wave-teal-950: oklch(0.198 0.028 222.5); /* #06181F */
225
+
226
+ /* Wave Gray — pure grey, the Wave Light Curve itself: each step at the
227
+ * curve's lightness with no chroma, so its 500 holds white text like every
228
+ * Wave Palette's. With no hue there's nothing to drift, and it's the same on
229
+ * Display-P3 screens. */
230
+ --wave-gray-50: oklch(0.975 0 0); /* #F7F7F7 */
231
+ --wave-gray-100: oklch(0.945 0 0); /* #EDEDED */
232
+ --wave-gray-200: oklch(0.890 0 0); /* #DBDBDB */
233
+ --wave-gray-300: oklch(0.806 0 0); /* #BFBFBF */
234
+ --wave-gray-400: oklch(0.686 0 0); /* #9A9A9A */
235
+ --wave-gray-500: oklch(0.565 0 0); /* #767676 */
236
+ --wave-gray-600: oklch(0.465 0 0); /* #595959 */
237
+ --wave-gray-700: oklch(0.385 0 0); /* #444444 */
238
+ --wave-gray-800: oklch(0.315 0 0); /* #313131 */
239
+ --wave-gray-900: oklch(0.255 0 0); /* #232323 */
240
+ --wave-gray-950: oklch(0.200 0 0); /* #161616 */
129
241
 
130
242
  /* ── Neutral ramps (primitives) ───────────────────────────────────────────
131
243
  * Three complete neutral ramps. Each theme's semantic block below references
132
244
  * exactly one of them directly: Paper → light, Graphite → default dark, Ink →
133
- * night. `--wave-*` (brand) is shared by all three. Never used by components
134
- * directly — only through the semantic tokens (`--foundation`, `--contrast`…).
245
+ * night. The Wave Palettes (`--wave-*`) are shared by all three. Never used
246
+ * by components directly — only through the semantic tokens (`--foundation`,
247
+ * `--contrast`…).
135
248
  * ──────────────────────────────────────────────────────────────────────── */
136
249
 
137
- /* Graphite — neutral grey (default) */
138
- --graphite-50: oklch(0.985 0.001 286); /* #FAFAFB */
139
- --graphite-100: oklch(0.964 0.001 286); /* #F3F3F4 */
140
- --graphite-200: oklch(0.923 0.004 286); /* #E5E5E8 */
141
- --graphite-300: oklch(0.864 0.007 277); /* #D1D2D7 */
142
- --graphite-400: oklch(0.745 0.009 279); /* #ABACB2 */
143
- --graphite-500: oklch(0.642 0.012 280); /* #8B8C94 */
144
- --graphite-600: oklch(0.537 0.014 281); /* #6C6D76 */
145
- --graphite-700: oklch(0.422 0.013 280); /* #4C4D55 */
146
- --graphite-800: oklch(0.358 0.014 280); /* #3B3C44 */
147
- --graphite-900: oklch(0.322 0.011 278); /* #323339 */
148
- --graphite-950: oklch(0.298 0.009 277); /* #2C2D32 */
149
-
150
- /* Ink — deep navy surfaces, neutral-cool structure (page #020812, surface #050F1E).
151
- * Chroma fades from full navy at the dark surfaces to near-neutral at the light
152
- * end, so text/borders read as a distinct layer rather than melting into navy. */
250
+ /* Graphite — the mid-tone grey (default). Built around its card colour, the
251
+ * 900 (#2F3036): the page (950) and menus (800) one even contrast step
252
+ * either side, the steps above evenly spaced up to the text colour at the
253
+ * 50. One hue, 278.2; chroma arcs to 0.014 through the middle. */
254
+ --graphite-50: oklch(0.964 0.001 278.2); /* #F3F3F4 */
255
+ --graphite-100: oklch(0.911 0.004 278.2); /* #E1E1E4 */
256
+ --graphite-200: oklch(0.840 0.007 278.2); /* #C9CACF */
257
+ --graphite-300: oklch(0.757 0.009 278.2); /* #AEB0B6 */
258
+ --graphite-400: oklch(0.678 0.011 278.2); /* #96979F */
259
+ --graphite-500: oklch(0.589 0.013 278.2); /* #7B7C85 */
260
+ --graphite-600: oklch(0.498 0.014 278.2); /* #61626B */
261
+ --graphite-700: oklch(0.415 0.013 278.2); /* #4A4B53 */
262
+ --graphite-800: oklch(0.348 0.012 278.2); /* #383940 */
263
+ --graphite-900: oklch(0.311 0.011 278.2); /* #2F3036 */
264
+ --graphite-950: oklch(0.268 0.009 278.2); /* #25262A */
265
+
266
+ /* Ink — deep navy surfaces, neutral-cool structure (page #020812, surface #0B1627).
267
+ * Graphite's even lightness steps between Ink's 50 and its page. Chroma fades
268
+ * from full navy at the dark surfaces to near-neutral at the light end, so
269
+ * text/borders read as a distinct layer rather than melting into navy. */
153
270
  --ink-50: oklch(0.966 0.003 248); /* #F2F4F6 */
154
- --ink-100: oklch(0.909 0.006 265); /* #DFE1E5 */
155
- --ink-200: oklch(0.834 0.008 254); /* #C5C9CE */
156
- --ink-300: oklch(0.752 0.011 257); /* #AAAFB6 */
157
- --ink-400: oklch(0.658 0.014 256); /* #8C929A */
158
- --ink-500: oklch(0.557 0.016 258); /* #6E747D */
159
- --ink-600: oklch(0.454 0.022 258); /* #4F5763 */
160
- --ink-700: oklch(0.356 0.030 256); /* #323D4C */
161
- --ink-800: oklch(0.254 0.041 258); /* #162336 */
162
- --ink-900: oklch(0.168 0.036 257); /* #050F1E */
271
+ --ink-100: oklch(0.906 0.006 265); /* #DEE0E4 */
272
+ --ink-200: oklch(0.826 0.008 254); /* #C2C6CB */
273
+ --ink-300: oklch(0.732 0.012 257); /* #A4A9B0 */
274
+ --ink-400: oklch(0.643 0.014 256); /* #888E96 */
275
+ --ink-500: oklch(0.542 0.017 258); /* #697079 */
276
+ --ink-600: oklch(0.439 0.023 258); /* #4A535F */
277
+ --ink-700: oklch(0.346 0.031 256); /* #2F3A4A */
278
+ --ink-800: oklch(0.270 0.039 258); /* #1A2739 */
279
+ --ink-900: oklch(0.201 0.038 257); /* #0B1627 */
163
280
  --ink-950: oklch(0.132 0.027 252); /* #020812 */
164
281
 
165
- /* Paper — cool near-neutral (anchor #EFF0EB @ 200 = foundation) */
166
- --paper-50: oklch(0.992 0.004 122); /* #FCFDFA */
167
- --paper-100: oklch(0.974 0.005 118); /* #F6F7F3 */
168
- --paper-200: oklch(0.953 0.007 116); /* #EFF0EB */
169
- --paper-300: oklch(0.880 0.008 114); /* #D7D8D2 */
170
- --paper-400: oklch(0.741 0.010 120); /* #AAACA5 */
171
- --paper-500: oklch(0.610 0.010 113); /* #83847D */
172
- --paper-600: oklch(0.500 0.009 114); /* #63645E */
173
- --paper-700: oklch(0.406 0.010 115); /* #494A44 */
174
- --paper-800: oklch(0.310 0.009 116); /* #30312C */
175
- --paper-900: oklch(0.224 0.008 118); /* #1B1C18 */
176
- --paper-950: oklch(0.156 0.008 119); /* #0C0D09 */
282
+ /* Paper — warm stone on a white page. Built around its own colours: the
283
+ * cards (50), the neutral fill (100), a warm 300, the secondary text (600)
284
+ * and the text (950). The steps between are filled with even contrast,
285
+ * about 1.37 a step from the 100 down. The warmth peaks at the 300 and eases
286
+ * into the text greys. The page is white, so it isn't a step; these are
287
+ * sRGB colours, the same on Display-P3 screens. */
288
+ --paper-50: oklch(0.989 0.002 67.8); /* #FCFBFA */
289
+ --paper-100: oklch(0.959 0.002 67.8); /* #F2F1F0 */
290
+ --paper-200: oklch(0.860 0.007 67.7); /* #D4D0CC */
291
+ --paper-300: oklch(0.769 0.010 72.6); /* #B8B3AD */
292
+ --paper-400: oklch(0.683 0.008 64.9); /* #9D9894 */
293
+ --paper-500: oklch(0.603 0.006 52.9); /* #84807E */
294
+ --paper-600: oklch(0.528 0.005 34.3); /* #6E6A69 */
295
+ --paper-700: oklch(0.452 0.004 34.3); /* #585554 */
296
+ --paper-800: oklch(0.376 0.003 34.3); /* #434140 */
297
+ --paper-900: oklch(0.291 0.001 34.3); /* #2C2B2B */
298
+ --paper-950: oklch(0.159 0 0); /* #0D0D0D */
177
299
 
178
300
  /* ========================================================================
179
301
  * Theme-independent tokens — identical in Paper / Graphite / Ink. The
@@ -190,8 +312,8 @@
190
312
  /* Brand — `primary` = the Wave ramp; `accent` = the bright signal (Wave Blue
191
313
  * 300, the strand now-glow). `secondary` (the neutral fill) is per-theme, so
192
314
  * it lives in the theme blocks. Text on the saturated fills is `text-white`. */
193
- --primary: var(--wave-500);
194
- --accent: var(--wave-300); /* the bright signal — the strand's "now" cursor glow */
315
+ --primary: var(--wave-blue-500);
316
+ --accent: var(--wave-blue-300); /* the bright signal — the strand's "now" cursor glow */
195
317
 
196
318
  /* Rings = brand themed */
197
319
  --focus: var(--primary);
@@ -203,7 +325,7 @@
203
325
  * stay in sync and follow a consumer's `--success`/etc. override). */
204
326
  --presence-online: var(--success);
205
327
  --presence-away: var(--warning);
206
- --presence-busy: var(--destructive);
328
+ --presence-busy: var(--danger);
207
329
  --presence-invisible: var(--foundation); /* invisible = the page itself; pair with a border so the dot reads as a hollow ring */
208
330
 
209
331
  /* Radius */
@@ -251,12 +373,163 @@
251
373
  --stagger-lg: 90ms;
252
374
  }
253
375
 
376
+ /* Display-P3 screens: the Wave Palettes and the Graphite and Ink ramps more
377
+ * vivid, at the same luminance, so every contrast holds. */
378
+ @media (color-gamut: p3) {
379
+ :where(:root) {
380
+ /* Wave Blue */
381
+ --wave-blue-50: oklch(0.974 0.012 242.8);
382
+ --wave-blue-100: oklch(0.944 0.026 242.8);
383
+ --wave-blue-200: oklch(0.888 0.054 247.2);
384
+ --wave-blue-300: oklch(0.804 0.104 248.7);
385
+ --wave-blue-400: oklch(0.686 0.180 250.4);
386
+ --wave-blue-500: oklch(0.570 0.237 254.6);
387
+ --wave-blue-600: oklch(0.476 0.221 258.4);
388
+ --wave-blue-700: oklch(0.395 0.185 259.8);
389
+ --wave-blue-800: oklch(0.323 0.146 260.3);
390
+ --wave-blue-900: oklch(0.260 0.110 260.3);
391
+ --wave-blue-950: oklch(0.204 0.085 261);
392
+ /* Wave Indigo */
393
+ --wave-indigo-50: oklch(0.976 0.011 287.9);
394
+ --wave-indigo-100: oklch(0.946 0.024 287.9);
395
+ --wave-indigo-200: oklch(0.893 0.052 286.3);
396
+ --wave-indigo-300: oklch(0.812 0.099 285.9);
397
+ --wave-indigo-400: oklch(0.699 0.173 284.3);
398
+ --wave-indigo-500: oklch(0.590 0.250 283);
399
+ --wave-indigo-600: oklch(0.503 0.294 283);
400
+ --wave-indigo-700: oklch(0.413 0.229 283);
401
+ --wave-indigo-800: oklch(0.335 0.174 283);
402
+ --wave-indigo-900: oklch(0.269 0.130 283);
403
+ --wave-indigo-950: oklch(0.210 0.095 283);
404
+ /* Wave Purple */
405
+ --wave-purple-50: oklch(0.976 0.014 314.5);
406
+ --wave-purple-100: oklch(0.948 0.033 314.5);
407
+ --wave-purple-200: oklch(0.897 0.068 313.6);
408
+ --wave-purple-300: oklch(0.820 0.128 312);
409
+ --wave-purple-400: oklch(0.711 0.216 310);
410
+ --wave-purple-500: oklch(0.607 0.309 309);
411
+ --wave-purple-600: oklch(0.501 0.261 309);
412
+ --wave-purple-700: oklch(0.413 0.204 309);
413
+ --wave-purple-800: oklch(0.336 0.155 309);
414
+ --wave-purple-900: oklch(0.270 0.116 309);
415
+ --wave-purple-950: oklch(0.211 0.086 309);
416
+ /* Wave Pink */
417
+ --wave-pink-50: oklch(0.977 0.017 337.3);
418
+ --wave-pink-100: oklch(0.950 0.039 337.3);
419
+ --wave-pink-200: oklch(0.901 0.084 337.3);
420
+ --wave-pink-300: oklch(0.827 0.165 338.1);
421
+ --wave-pink-400: oklch(0.717 0.238 339.2);
422
+ --wave-pink-500: oklch(0.604 0.290 342);
423
+ --wave-pink-600: oklch(0.496 0.227 346.2);
424
+ --wave-pink-700: oklch(0.409 0.177 348.7);
425
+ --wave-pink-800: oklch(0.333 0.135 348.8);
426
+ --wave-pink-900: oklch(0.268 0.101 348.8);
427
+ --wave-pink-950: oklch(0.210 0.074 349.3);
428
+ /* Wave Red */
429
+ --wave-red-50: oklch(0.976 0.012 11.6);
430
+ --wave-red-100: oklch(0.948 0.028 12.1);
431
+ --wave-red-200: oklch(0.897 0.061 13.8);
432
+ --wave-red-300: oklch(0.821 0.121 14.5);
433
+ --wave-red-400: oklch(0.714 0.224 16.8);
434
+ --wave-red-500: oklch(0.596 0.247 21);
435
+ --wave-red-600: oklch(0.492 0.217 21);
436
+ --wave-red-700: oklch(0.406 0.170 21);
437
+ --wave-red-800: oklch(0.331 0.129 21);
438
+ --wave-red-900: oklch(0.267 0.097 21);
439
+ --wave-red-950: oklch(0.209 0.072 21);
440
+ /* Wave Orange */
441
+ --wave-orange-50: oklch(0.975 0.027 85.9);
442
+ --wave-orange-100: oklch(0.945 0.057 84.2);
443
+ --wave-orange-200: oklch(0.893 0.106 79.1);
444
+ --wave-orange-300: oklch(0.815 0.165 67.8);
445
+ --wave-orange-400: oklch(0.701 0.187 57.3);
446
+ --wave-orange-500: oklch(0.582 0.176 49.5);
447
+ --wave-orange-600: oklch(0.482 0.158 42.6);
448
+ --wave-orange-700: oklch(0.399 0.132 39.3);
449
+ --wave-orange-800: oklch(0.326 0.104 38.1);
450
+ --wave-orange-900: oklch(0.263 0.078 38.1);
451
+ --wave-orange-950: oklch(0.206 0.059 37.1);
452
+ /* Wave Yellow */
453
+ --wave-yellow-50: oklch(0.974 0.031 107.1);
454
+ --wave-yellow-100: oklch(0.942 0.071 104.9);
455
+ --wave-yellow-200: oklch(0.887 0.129 101.5);
456
+ --wave-yellow-300: oklch(0.804 0.177 98.7);
457
+ --wave-yellow-400: oklch(0.686 0.158 93.8);
458
+ --wave-yellow-500: oklch(0.567 0.134 88);
459
+ --wave-yellow-600: oklch(0.469 0.109 78.3);
460
+ --wave-yellow-700: oklch(0.390 0.090 69.3);
461
+ --wave-yellow-800: oklch(0.319 0.071 65);
462
+ --wave-yellow-900: oklch(0.259 0.056 61);
463
+ --wave-yellow-950: oklch(0.203 0.043 57.3);
464
+ /* Wave Lime */
465
+ --wave-lime-50: oklch(0.973 0.031 117.4);
466
+ --wave-lime-100: oklch(0.940 0.071 117.8);
467
+ --wave-lime-200: oklch(0.882 0.129 119.9);
468
+ --wave-lime-300: oklch(0.794 0.196 122.1);
469
+ --wave-lime-400: oklch(0.674 0.190 124);
470
+ --wave-lime-500: oklch(0.555 0.161 124);
471
+ --wave-lime-600: oklch(0.457 0.129 124);
472
+ --wave-lime-700: oklch(0.379 0.101 124);
473
+ --wave-lime-800: oklch(0.310 0.078 124);
474
+ --wave-lime-900: oklch(0.251 0.059 124);
475
+ --wave-lime-950: oklch(0.197 0.044 124);
476
+ /* Wave Green */
477
+ --wave-green-50: oklch(0.971 0.031 156.1);
478
+ --wave-green-100: oklch(0.936 0.071 156.4);
479
+ --wave-green-200: oklch(0.875 0.129 157.7);
480
+ --wave-green-300: oklch(0.783 0.196 159.4);
481
+ --wave-green-400: oklch(0.662 0.200 160);
482
+ --wave-green-500: oklch(0.545 0.170 160);
483
+ --wave-green-600: oklch(0.449 0.135 160.5);
484
+ --wave-green-700: oklch(0.373 0.103 162.6);
485
+ --wave-green-800: oklch(0.306 0.077 163.9);
486
+ --wave-green-900: oklch(0.248 0.057 165.8);
487
+ --wave-green-950: oklch(0.195 0.041 168.8);
488
+ /* Wave Teal */
489
+ --wave-teal-50: oklch(0.971 0.031 197);
490
+ --wave-teal-100: oklch(0.937 0.071 197.9);
491
+ --wave-teal-200: oklch(0.876 0.129 200);
492
+ --wave-teal-300: oklch(0.788 0.165 202.4);
493
+ --wave-teal-400: oklch(0.671 0.149 205.5);
494
+ --wave-teal-500: oklch(0.553 0.127 208.9);
495
+ --wave-teal-600: oklch(0.456 0.104 214.6);
496
+ --wave-teal-700: oklch(0.378 0.082 216);
497
+ --wave-teal-800: oklch(0.310 0.064 217.3);
498
+ --wave-teal-900: oklch(0.251 0.049 219.9);
499
+ --wave-teal-950: oklch(0.197 0.037 222.5);
500
+ /* Graphite */
501
+ --graphite-50: oklch(0.964 0.001 278.2);
502
+ --graphite-100: oklch(0.911 0.004 278.2);
503
+ --graphite-200: oklch(0.840 0.007 278.2);
504
+ --graphite-300: oklch(0.757 0.009 278.2);
505
+ --graphite-400: oklch(0.678 0.011 278.2);
506
+ --graphite-500: oklch(0.589 0.013 278.2);
507
+ --graphite-600: oklch(0.498 0.014 278.2);
508
+ --graphite-700: oklch(0.415 0.013 278.2);
509
+ --graphite-800: oklch(0.348 0.012 278.2);
510
+ --graphite-900: oklch(0.311 0.011 278.2);
511
+ --graphite-950: oklch(0.268 0.009 278.2);
512
+ /* Ink */
513
+ --ink-50: oklch(0.966 0.003 248);
514
+ --ink-100: oklch(0.906 0.007 265);
515
+ --ink-200: oklch(0.826 0.009 254);
516
+ --ink-300: oklch(0.732 0.013 257);
517
+ --ink-400: oklch(0.643 0.015 256);
518
+ --ink-500: oklch(0.542 0.021 258);
519
+ --ink-600: oklch(0.439 0.029 258);
520
+ --ink-700: oklch(0.346 0.039 256);
521
+ --ink-800: oklch(0.270 0.050 258);
522
+ --ink-900: oklch(0.201 0.049 257);
523
+ --ink-950: oklch(0.132 0.035 252);
524
+ }
525
+ }
526
+
254
527
  /* A squircle reads tighter than a circular arc at the same radius, so where
255
528
  * `corner-shape` is active we bump the radius to restore the intended roundness.
256
529
  * Scoped via @supports — browsers without squircles keep the original values.
257
530
  * Starting value; nudge by eye in Chrome. Feeds --radius-sm/md/lg (all calc off it). */
258
531
  @supports (corner-shape: squircle) {
259
- :root {
532
+ :where(:root) {
260
533
  --radius: 1rem;
261
534
  }
262
535
  }
@@ -266,25 +539,37 @@
266
539
  * per-theme tokens, shared by `:root` (the default) and `.paper` (explicit —
267
540
  * what a theme toggle sets, or to override an inherited dark theme in a nested
268
541
  * scope).
542
+ *
543
+ * `:where()` keeps the default at zero specificity, here and on the OS-dark
544
+ * block below, so both are defaults in the cascade too: any rule an app writes
545
+ * after this file wins, a plain `:root { --foundation: … }` included, on a
546
+ * dark system as on a light one. A pinned theme class still beats both.
547
+ *
548
+ * The page and popups are white; cards and the sidebar sit on the 50, a faint
549
+ * warm tint, so the contour does most of the separating. The marks are its
550
+ * text colour, the 950, made translucent, as in every theme.
269
551
  * ------------------------------------------------------------------------- */
270
- :root,
552
+ :where(:root),
271
553
  .paper {
272
- --foundation: var(--paper-200);
273
- --surface: var(--paper-100);
274
- --elevated: var(--paper-50);
554
+ --foundation: white;
555
+ --surface: var(--paper-50);
556
+ --elevated: white;
275
557
  --contrast: var(--paper-950);
276
558
  --muted: var(--paper-600);
277
559
  --soft: var(--paper-500);
278
- --secondary: var(--paper-300);
279
- --line: color-mix(in srgb, var(--paper-950) 7%, transparent);
280
- --edge: color-mix(in srgb, var(--paper-950) 14%, transparent);
281
- --solid: var(--paper-300);
282
- --shadow-color: 0.159 0.021 263;
283
- --shadow-strength: 0.22;
284
- --destructive: oklch(0.620 0.200 25);
285
- --success: oklch(0.710 0.185 155);
286
- --warning: oklch(0.830 0.156 93);
287
- --info: var(--wave-500);
560
+ --secondary: var(--paper-100);
561
+ --contour: color-mix(in srgb, var(--paper-950) 5%, transparent);
562
+ --highlight: color-mix(in srgb, var(--paper-950) 10%, transparent);
563
+ --shade: color-mix(in srgb, var(--paper-950) 15%, transparent);
564
+ --shadow-strength: 0.055;
565
+ --danger: var(--wave-red-500);
566
+ --danger-text: var(--wave-red-600);
567
+ --success: var(--wave-green-500);
568
+ --success-text: var(--wave-green-600);
569
+ --warning: var(--wave-orange-500);
570
+ --warning-text: var(--wave-orange-600);
571
+ --info: var(--wave-blue-500);
572
+ --info-text: var(--wave-blue-600);
288
573
  }
289
574
 
290
575
  /* ---------------------------------------------------------------------------
@@ -293,26 +578,31 @@
293
578
  * `.graphite` (what a theme toggle sets). Same tokens both ways — a media query
294
579
  * can't be folded into a plain class selector, so they're stated once each.
295
580
  * Status brightens and the shadow dims for the dark surface; any pinned theme
296
- * beats the OS.
581
+ * beats the OS. The OS-dark default is wrapped in `:where()`, as Paper's is:
582
+ * written bare, its three `:not()`s outweighed an app's own `:root` override,
583
+ * which was then ignored on every dark system.
297
584
  * ------------------------------------------------------------------------- */
298
585
  @media (prefers-color-scheme: dark) {
299
- :root:not(.paper):not(.graphite):not(.ink) {
586
+ :where(:root:not(.paper):not(.graphite):not(.ink)) {
300
587
  --foundation: var(--graphite-950);
301
588
  --surface: var(--graphite-900);
302
589
  --elevated: var(--graphite-800);
303
- --contrast: var(--graphite-100);
304
- --muted: var(--graphite-400);
305
- --soft: var(--graphite-600);
590
+ --contrast: var(--graphite-50);
591
+ --muted: var(--graphite-300);
592
+ --soft: var(--graphite-500);
306
593
  --secondary: var(--graphite-700);
307
- --line: color-mix(in srgb, var(--graphite-50) 5%, transparent);
308
- --edge: color-mix(in srgb, var(--graphite-50) 13%, transparent);
309
- --solid: var(--graphite-700);
310
- --shadow-color: 0.125 0.011 259;
311
- --shadow-strength: 0.18;
312
- --destructive: oklch(0.630 0.200 25);
313
- --success: oklch(0.735 0.185 155);
314
- --warning: oklch(0.850 0.156 93);
315
- --info: var(--wave-400);
594
+ --contour: color-mix(in srgb, var(--graphite-50) 5%, transparent);
595
+ --highlight: color-mix(in srgb, var(--graphite-50) 10%, transparent);
596
+ --shade: color-mix(in srgb, var(--graphite-50) 15%, transparent);
597
+ --shadow-strength: 0.045;
598
+ --danger: var(--wave-red-500);
599
+ --danger-text: oklch(0.738 0.158 16.8); /* #FE7C86, see below */
600
+ --success: var(--wave-green-500);
601
+ --success-text: oklch(0.700 0.155 160); /* #14BB7C, see below */
602
+ --warning: var(--wave-orange-500);
603
+ --warning-text: oklch(0.732 0.173 57.3); /* #F78711, see below */
604
+ --info: var(--wave-blue-500);
605
+ --info-text: oklch(0.717 0.150 250.4); /* #52A8FD, see below */
316
606
  }
317
607
  }
318
608
 
@@ -320,50 +610,80 @@
320
610
  --foundation: var(--graphite-950);
321
611
  --surface: var(--graphite-900);
322
612
  --elevated: var(--graphite-800);
323
- --contrast: var(--graphite-100);
324
- --muted: var(--graphite-400);
325
- --soft: var(--graphite-600);
613
+ --contrast: var(--graphite-50);
614
+ --muted: var(--graphite-300);
615
+ --soft: var(--graphite-500);
326
616
  --secondary: var(--graphite-700);
327
- --line: color-mix(in srgb, var(--graphite-50) 5%, transparent);
328
- --edge: color-mix(in srgb, var(--graphite-50) 13%, transparent);
329
- --solid: var(--graphite-700);
330
- --shadow-color: 0.125 0.011 259;
331
- --shadow-strength: 0.18;
332
- --destructive: oklch(0.630 0.200 25);
333
- --success: oklch(0.735 0.185 155);
334
- --warning: oklch(0.850 0.156 93);
335
- --info: var(--wave-400);
617
+ --contour: color-mix(in srgb, var(--graphite-50) 5%, transparent);
618
+ --highlight: color-mix(in srgb, var(--graphite-50) 10%, transparent);
619
+ --shade: color-mix(in srgb, var(--graphite-50) 15%, transparent);
620
+ --shadow-strength: 0.045;
621
+ --danger: var(--wave-red-500);
622
+ --danger-text: oklch(0.738 0.158 16.8); /* #FE7C86, see below */
623
+ --success: var(--wave-green-500);
624
+ --success-text: oklch(0.700 0.155 160); /* #14BB7C, see below */
625
+ --warning: var(--wave-orange-500);
626
+ --warning-text: oklch(0.732 0.173 57.3); /* #F78711, see below */
627
+ --info: var(--wave-blue-500);
628
+ --info-text: oklch(0.717 0.150 250.4); /* #52A8FD, see below */
629
+ }
630
+
631
+ /* Graphite's status text. Its mid-tone menus need each status a touch lighter
632
+ * than its 400 for words to read there (4.6), so Graphite sets those four
633
+ * itself rather than moving the palettes: each keeps its 400's hue, lifted to
634
+ * that lightness at the most chroma the gamut holds. On Display-P3 screens
635
+ * they take the P3 versions, as the palettes do. Ink and Paper keep their
636
+ * palette steps. */
637
+ @media (color-gamut: p3) {
638
+ .graphite {
639
+ --danger-text: oklch(0.743 0.198 16.8);
640
+ --success-text: oklch(0.693 0.212 160);
641
+ --warning-text: oklch(0.734 0.198 57.3);
642
+ --info-text: oklch(0.717 0.162 250.4);
643
+ }
644
+ }
645
+
646
+ @media (prefers-color-scheme: dark) and (color-gamut: p3) {
647
+ :where(:root:not(.paper):not(.graphite):not(.ink)) {
648
+ --danger-text: oklch(0.743 0.198 16.8);
649
+ --success-text: oklch(0.693 0.212 160);
650
+ --warning-text: oklch(0.734 0.198 57.3);
651
+ --info-text: oklch(0.717 0.162 250.4);
652
+ }
336
653
  }
337
654
 
338
655
  /* ---------------------------------------------------------------------------
339
656
  * Ink — the premium night theme. Opt-in only: `<html class="ink">`.
340
657
  * A complete, self-contained dark theme (deep navy), separate from the Graphite
341
- * default dark. It's dark, so it drives the `dark:` variant too. primary /
658
+ * default dark. primary /
342
659
  * accent / focus / inverse / presence stay constant or re-resolve via `:root`.
343
660
  * ------------------------------------------------------------------------- */
344
661
  .ink {
345
662
  --foundation: var(--ink-950);
346
663
  --surface: var(--ink-900);
347
664
  --elevated: var(--ink-800);
348
- --contrast: var(--ink-100);
665
+ --contrast: var(--ink-50);
349
666
  --muted: var(--ink-400);
350
667
  --soft: var(--ink-600);
351
668
  --secondary: var(--ink-700);
352
- --line: color-mix(in srgb, var(--ink-50) 5%, transparent);
353
- --edge: color-mix(in srgb, var(--ink-50) 13%, transparent);
354
- --solid: var(--ink-700);
355
- --shadow-color: 0.125 0.011 259;
356
- --shadow-strength: 0.18;
357
- --destructive: oklch(0.630 0.200 25);
358
- --success: oklch(0.735 0.185 155);
359
- --warning: oklch(0.850 0.156 93);
360
- --info: var(--wave-400);
669
+ --contour: color-mix(in srgb, var(--ink-50) 5%, transparent);
670
+ --highlight: color-mix(in srgb, var(--ink-50) 10%, transparent);
671
+ --shade: color-mix(in srgb, var(--ink-50) 15%, transparent);
672
+ --shadow-strength: 0.045;
673
+ --danger: var(--wave-red-500);
674
+ --danger-text: var(--wave-red-400);
675
+ --success: var(--wave-green-500);
676
+ --success-text: var(--wave-green-400);
677
+ --warning: var(--wave-orange-500);
678
+ --warning-text: var(--wave-orange-400);
679
+ --info: var(--wave-blue-500);
680
+ --info-text: var(--wave-blue-400);
361
681
  }
362
682
 
363
683
  /* ---------------------------------------------------------------------------
364
684
  * Tailwind v4 theme mapping — maps CSS variables to Tailwind color tokens.
365
685
  * Consumers MUST include this for Tailwind classes like `bg-primary`,
366
- * `text-muted`, `border-line` etc. to work.
686
+ * `text-muted`, `border-contour` etc. to work.
367
687
  * ------------------------------------------------------------------------- */
368
688
 
369
689
  @theme inline {
@@ -387,19 +707,35 @@
387
707
  --color-secondary: var(--secondary);
388
708
  --color-accent: var(--accent);
389
709
 
390
- /* Structure — `line` (faint divider), `edge` (input border), `solid` (opaque
391
- * border), `focus` (brand-themed ring = primary). */
392
- --color-line: var(--line);
393
- --color-edge: var(--edge);
394
- --color-solid: var(--solid);
710
+ /* Form — the theme's foreground (its text colour) made translucent, at
711
+ * three strengths, the way a form is drawn: `contour` marks where a shape
712
+ * ends: hairline borders and separators. `highlight` marks where attention
713
+ * lands: hovered, keyboard-highlighted and open items. `shade` gives a
714
+ * control its body: field and track fills and outline-control borders.
715
+ * Every theme uses the same strengths: contour 5%, highlight 10%, shade
716
+ * 15% of its text colour (Graphite's and Ink's 50, Paper's 950).
717
+ * All three are translucent, so each reads the same on any surface beneath
718
+ * it, and `shade` doubles as the intense border (layout regions, control
719
+ * outlines). `focus` is the brand-themed ring (= primary). */
720
+ --color-contour: var(--contour);
721
+ --color-highlight: var(--highlight);
722
+ --color-shade: var(--shade);
395
723
  --color-focus: var(--focus);
396
724
  --color-scrim: var(--scrim);
397
725
 
398
- /* Status */
399
- --color-destructive: var(--destructive);
726
+ /* Status. Each status is a fill (the 500, holding white text) everywhere
727
+ * but in text: Tailwind reads `--text-color-*` before `--color-*`, so
728
+ * `text-danger` (words and icons) takes `--danger-text`, a colour that
729
+ * reads on the theme's surfaces, while `bg-`, `border-` and `ring-danger` keep
730
+ * the fill. The same for `success`, `warning` and `info`. */
731
+ --color-danger: var(--danger);
732
+ --text-color-danger: var(--danger-text);
400
733
  --color-success: var(--success);
734
+ --text-color-success: var(--success-text);
401
735
  --color-warning: var(--warning);
736
+ --text-color-warning: var(--warning-text);
402
737
  --color-info: var(--info);
738
+ --text-color-info: var(--info-text);
403
739
 
404
740
  /* Presence */
405
741
  --color-presence-online: var(--presence-online);
@@ -421,29 +757,31 @@
421
757
  --font-mono: var(--font-mono);
422
758
  }
423
759
 
424
- /* Shadow scale — layered, tinted by `--shadow-color` and dimmed by
425
- * `--shadow-strength` (both resolve per theme, so retinting or dimming
426
- * every shadow is one token). Tiers differ by layer count + spread, not alpha.
760
+ /* Shadow scale — layered, in a dark version of the page's own colour: the
761
+ * active theme's `--foundation` at 16% of its lightness, same hue and chroma,
762
+ * so a shadow belongs to its surface in every theme (an app's own and nested
763
+ * ones included, as it resolves where it's used). Set `--shadow-color` to
764
+ * tint them from another colour instead. `--shadow-strength` sets each
765
+ * theme's opacity. Tiers differ by layer count + spread, not alpha: each adds
766
+ * one layer and reaches about 2.5–3× further (sm 3 layers / 3px, md 4 /
767
+ * 10px, lg 5 / 24px), so the darkness under an element steps evenly.
427
768
  * Overrides Tailwind's shadow-sm/md/lg. */
428
769
  @theme {
429
770
  --shadow-sm:
430
- 0px 0.5px 0.7px oklch(var(--shadow-color) / var(--shadow-strength)),
431
- 0px 0.8px 1px -1.2px oklch(var(--shadow-color) / var(--shadow-strength)),
432
- 0px 2px 2.5px -2.5px oklch(var(--shadow-color) / var(--shadow-strength));
771
+ 0px 0.5px 0.9px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
772
+ 0px 1.2px 1.8px -0.9px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
773
+ 0px 3px 4.3px -2px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength));
433
774
  --shadow-md:
434
- 0px 0.5px 0.7px oklch(var(--shadow-color) / var(--shadow-strength)),
435
- 0px 1.6px 2px -0.8px oklch(var(--shadow-color) / var(--shadow-strength)),
436
- 0px 4.1px 5.2px -1.7px oklch(var(--shadow-color) / var(--shadow-strength)),
437
- 0px 10px 12.6px -2.5px oklch(var(--shadow-color) / var(--shadow-strength));
775
+ 0px 0.5px 0.7px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
776
+ 0px 1.6px 2px -0.8px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
777
+ 0px 4.1px 5.2px -1.7px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
778
+ 0px 10px 12.6px -2.5px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength));
438
779
  --shadow-lg:
439
- 0px 0.5px 0.7px oklch(var(--shadow-color) / var(--shadow-strength)),
440
- 0px 2.9px 3.7px -0.4px oklch(var(--shadow-color) / var(--shadow-strength)),
441
- 0px 5.4px 6.8px -0.7px oklch(var(--shadow-color) / var(--shadow-strength)),
442
- 0px 8.9px 11.2px -1.1px oklch(var(--shadow-color) / var(--shadow-strength)),
443
- 0px 14.3px 18px -1.4px oklch(var(--shadow-color) / var(--shadow-strength)),
444
- 0px 22.3px 28.1px -1.8px oklch(var(--shadow-color) / var(--shadow-strength)),
445
- 0px 33.9px 42.7px -2.1px oklch(var(--shadow-color) / var(--shadow-strength)),
446
- 0px 50px 62.9px -2.5px oklch(var(--shadow-color) / var(--shadow-strength));
780
+ 0px 0.5px 0.7px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
781
+ 0px 2.2px 2.8px -0.6px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
782
+ 0px 5px 6.3px -1.2px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
783
+ 0px 11px 13.9px -1.9px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength)),
784
+ 0px 24px 30.2px -2.5px oklch(from var(--shadow-color, var(--foundation)) calc(l * 0.16) c h / var(--shadow-strength));
447
785
  }
448
786
 
449
787
  /* ---------------------------------------------------------------------------
@@ -468,7 +806,7 @@
468
806
  background: transparent;
469
807
  }
470
808
  &:hover {
471
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Cpath d='M12 0C12 5.4 11.5 8.5 10 10C8.5 11.5 5.4 12 0 12' stroke='white' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
809
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14' fill='none'%3E%3Cpath d='M12 0C12 5.4 11.5 8.5 10 10C8.5 11.5 5.4 12 0 12' stroke='%2383847D' stroke-width='1.5' stroke-linecap='round'/%3E%3C/svg%3E");
472
810
  background-repeat: no-repeat;
473
811
  background-position: right 5px bottom 5px;
474
812
  background-size: 12px 12px;
@@ -476,7 +814,7 @@
476
814
  }
477
815
 
478
816
  /* `motion-color` — interaction-state transition for controls: smoothly
479
- * animates color / background / border / ring (box-shadow) on hover, focus,
817
+ * animates color / background / border / ring (box-shadow) / backdrop blur on hover, focus,
480
818
  * pressed, checked, etc. Runs at the `sm` tier (controls are small + fast). A
481
819
  * plain layered `@utility`, so the unlayered enter/exit recipes below win over
482
820
  * it when both land on one element (e.g. a button that also carries `motion-scale-sm`). */
@@ -484,7 +822,9 @@
484
822
  transition: color var(--duration-sm) var(--ease),
485
823
  background-color var(--duration-sm) var(--ease),
486
824
  border-color var(--duration-sm) var(--ease),
487
- box-shadow var(--duration-sm) var(--ease);
825
+ box-shadow var(--duration-sm) var(--ease),
826
+ -webkit-backdrop-filter var(--duration-sm) var(--ease),
827
+ backdrop-filter var(--duration-sm) var(--ease);
488
828
  }
489
829
 
490
830
  /* Motion recipes — Base UI primitives emit `data-starting-style` (enter)
@@ -502,8 +842,12 @@
502
842
  * cascade, with NO `!important` (banned — see feedback_security). The
503
843
  * resting/enter/exit declarations stay layered so `group-has` still wins.
504
844
  *
505
- * `0.001px` resting blur defeats Lightning CSS's `blur(0) → blur()`
506
- * minification (which emits invalid CSS); it is visually equivalent to 0. */
845
+ * The resting state declares NO `filter`. It stays `none`, and `none` blends
846
+ * with the starting/ending `blur()` as a zero blur (how CSS interpolates
847
+ * `none` with a filter list), so the transitions still run. Don't add a
848
+ * resting `blur()`: Lightning CSS minifies `blur(0)` to the invalid `blur()`,
849
+ * and a stand-in like `blur(0.001px)` is not invisible in WebKit, which draws
850
+ * any non-zero blur at a minimum radius and softens the whole surface. */
507
851
 
508
852
  /* `motion-scrim` — opacity only, no blur. For full-screen backdrops: animating
509
853
  * blur across the whole viewport janks (a flat dim shows none anyway). The
@@ -525,7 +869,6 @@
525
869
  * tier per surface (don't compose — each declares its own unlayered transition).
526
870
  * Companion idioms: `motion-color` (controls) and `motion-scrim` (backdrops). */
527
871
  @utility motion-scale-sm {
528
- filter: blur(0.001px);
529
872
  scale: 1;
530
873
  &[data-starting-style],
531
874
  &[data-ending-style] {
@@ -535,7 +878,6 @@
535
878
  }
536
879
  }
537
880
  @utility motion-scale-md {
538
- filter: blur(0.001px);
539
881
  scale: 1;
540
882
  &[data-starting-style],
541
883
  &[data-ending-style] {
@@ -545,7 +887,6 @@
545
887
  }
546
888
  }
547
889
  @utility motion-scale-lg {
548
- filter: blur(0.001px);
549
890
  scale: 1;
550
891
  &[data-starting-style],
551
892
  &[data-ending-style] {
@@ -559,7 +900,6 @@
559
900
  * trigger's `data-[side]` (Base UI sets it) and settles toward it; default /
560
901
  * side=bottom rises up. Opacity + blur + translate (`--offset-*`), NO scale. */
561
902
  @utility motion-slide-sm {
562
- filter: blur(0.001px);
563
903
  translate: 0;
564
904
  &[data-starting-style],
565
905
  &[data-ending-style] {
@@ -579,7 +919,6 @@
579
919
  &[data-side=inline-end][data-ending-style] { translate: var(--offset-sm) 0; }
580
920
  }
581
921
  @utility motion-slide-md {
582
- filter: blur(0.001px);
583
922
  translate: 0;
584
923
  &[data-starting-style],
585
924
  &[data-ending-style] {
@@ -599,7 +938,6 @@
599
938
  &[data-side=inline-end][data-ending-style] { translate: var(--offset-md) 0; }
600
939
  }
601
940
  @utility motion-slide-lg {
602
- filter: blur(0.001px);
603
941
  translate: 0;
604
942
  &[data-starting-style],
605
943
  &[data-ending-style] {
@@ -622,7 +960,6 @@
622
960
  /* `motion-fade-*` — fade in place: opacity + blur, no transform. The signature
623
961
  * blur-fade for content that should appear without moving or scaling. */
624
962
  @utility motion-fade-sm {
625
- filter: blur(0.001px);
626
963
  &[data-starting-style],
627
964
  &[data-ending-style] {
628
965
  opacity: 0;
@@ -630,7 +967,6 @@
630
967
  }
631
968
  }
632
969
  @utility motion-fade-md {
633
- filter: blur(0.001px);
634
970
  &[data-starting-style],
635
971
  &[data-ending-style] {
636
972
  opacity: 0;
@@ -638,7 +974,6 @@
638
974
  }
639
975
  }
640
976
  @utility motion-fade-lg {
641
- filter: blur(0.001px);
642
977
  &[data-starting-style],
643
978
  &[data-ending-style] {
644
979
  opacity: 0;
@@ -651,9 +986,29 @@
651
986
  * recipe with one transition. Don't try `motion-scale-md motion-slide-md`: their
652
987
  * transitions collide and scale won't animate. md only (no sm/lg consumer). */
653
988
  @utility motion-pop-md {
654
- filter: blur(0.001px);
655
989
  scale: 1;
656
990
  translate: 0;
991
+ /* Pop from the anchored EDGE, centered along it — symmetric left/right for
992
+ * top/bottom dropdowns — regardless of start/end alignment. Base UI 1.8
993
+ * changed its `--transform-origin` to pin to the aligned corner (#5015),
994
+ * which reads as a left-to-right grow; these per-side rules restore the
995
+ * symmetric pop and replace the old `origin-(--transform-origin)` that each
996
+ * popup used to carry. */
997
+ transform-origin: center top;
998
+ &[data-side="bottom"] {
999
+ transform-origin: center top;
1000
+ }
1001
+ &[data-side="top"] {
1002
+ transform-origin: center bottom;
1003
+ }
1004
+ &[data-side="left"],
1005
+ &[data-side="inline-start"] {
1006
+ transform-origin: right center;
1007
+ }
1008
+ &[data-side="right"],
1009
+ &[data-side="inline-end"] {
1010
+ transform-origin: left center;
1011
+ }
657
1012
  &[data-starting-style],
658
1013
  &[data-ending-style] {
659
1014
  opacity: 0;
@@ -673,6 +1028,31 @@
673
1028
  &[data-side=inline-end][data-ending-style] { translate: var(--offset-md) 0; }
674
1029
  }
675
1030
 
1031
+ /* `motion-spin-{sm,md,lg}` — continuous rotation for loaders, three speeds (sm
1032
+ * fastest → lg calmest). LINEAR (a spinner rotates at a constant rate) and
1033
+ * literal durations (a visible spin needs ~0.5–1s, not the 150–250ms
1034
+ * transition tiers). Reduced motion slows every tier to a calm 1.5s instead of
1035
+ * stopping it: a frozen spinner reads as broken. A plain layered `@utility`, so
1036
+ * variants work and a consumer's own utility can still override it. */
1037
+ @utility motion-spin-sm {
1038
+ animation: motion-spin 0.5s linear infinite;
1039
+ @media (prefers-reduced-motion: reduce) {
1040
+ animation-duration: 1.5s;
1041
+ }
1042
+ }
1043
+ @utility motion-spin-md {
1044
+ animation: motion-spin 0.75s linear infinite;
1045
+ @media (prefers-reduced-motion: reduce) {
1046
+ animation-duration: 1.5s;
1047
+ }
1048
+ }
1049
+ @utility motion-spin-lg {
1050
+ animation: motion-spin 1s linear infinite;
1051
+ @media (prefers-reduced-motion: reduce) {
1052
+ animation-duration: 1.5s;
1053
+ }
1054
+ }
1055
+
676
1056
  /* Recipe transitions — UNLAYERED on purpose (see note above): beats any
677
1057
  * layered `transition-*` utility on the same element without `!important`. */
678
1058
  .motion-scrim {
@@ -728,10 +1108,13 @@
728
1108
  }
729
1109
 
730
1110
  /* ---------------------------------------------------------------------------
731
- * Reduced motion — one block, every token zeroes, every recipe collapses.
1111
+ * Reduced motion — every token zeroes, so every transition recipe collapses.
1112
+ * Spinners slow down instead of stopping (inside `motion-spin-*`). `html:root`
1113
+ * on purpose: one step stronger than a plain `:root`, so an app that tunes the
1114
+ * motion tokens in its own `:root` can't switch reduced motion back on.
732
1115
  * ------------------------------------------------------------------------- */
733
1116
  @media (prefers-reduced-motion: reduce) {
734
- :root {
1117
+ html:root {
735
1118
  --duration-sm: 0ms; --duration-md: 0ms; --duration-lg: 0ms;
736
1119
  --blur-sm: 0px; --blur-md: 0px; --blur-lg: 0px;
737
1120
  --scale-sm: 1; --scale-md: 1; --scale-lg: 1;
@@ -749,10 +1132,11 @@
749
1132
 
750
1133
  /* ---------------------------------------------------------------------------
751
1134
  * Keyframe utilities — self-hosted (replaces the former `tw-animate-css` dep,
752
- * now removed). Only the three the library actually uses; `animate-spin` /
1135
+ * now removed). Only the four the library actually uses; `animate-spin` /
753
1136
  * `animate-pulse` come from Tailwind core. Registering via `@theme` generates
754
1137
  * the `animate-accordion-down` / `animate-accordion-up` / `animate-caret-blink`
755
- * utilities. Accordion height runs on our motion tokens (`md` tier + the curve)
1138
+ * utilities; `motion-spin` drives the `motion-spin-*` utilities and is named
1139
+ * apart from Tailwind's `spin` so an app's own `spin` keyframe can't replace it. Accordion height runs on our motion tokens (`md` tier + the curve)
756
1140
  * and uses Base UI's `--accordion-panel-height` (the Radix var tw-animate
757
1141
  * targeted is never set under Base UI). Collapses under reduced motion via
758
1142
  * `--duration-md`. */
@@ -769,6 +1153,9 @@
769
1153
  from { height: var(--accordion-panel-height); }
770
1154
  to { height: 0; }
771
1155
  }
1156
+ @keyframes motion-spin {
1157
+ to { transform: rotate(360deg); }
1158
+ }
772
1159
  @keyframes caret-blink {
773
1160
  0%, 70%, 100% { opacity: 1; }
774
1161
  20%, 50% { opacity: 0; }