@rinzai/zen 0.3.2 → 0.4.1

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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/actions-menu/index.d.ts.map +1 -1
  4. package/dist/animated-number/index.d.ts +25 -0
  5. package/dist/animated-number/index.d.ts.map +1 -0
  6. package/dist/avatar/index.d.ts +24 -0
  7. package/dist/avatar/index.d.ts.map +1 -0
  8. package/dist/backdrop/index.d.ts.map +1 -1
  9. package/dist/badge/index.d.ts +1 -1
  10. package/dist/base.css +100 -0
  11. package/dist/button/index.d.ts +2 -2
  12. package/dist/button/index.d.ts.map +1 -1
  13. package/dist/collapse/index.d.ts.map +1 -1
  14. package/dist/dialog/index.d.ts.map +1 -1
  15. package/dist/dropdown/index.d.ts.map +1 -1
  16. package/dist/editable-cell/index.d.ts +24 -0
  17. package/dist/editable-cell/index.d.ts.map +1 -0
  18. package/dist/field/index.d.ts +25 -0
  19. package/dist/field/index.d.ts.map +1 -0
  20. package/dist/header/index.d.ts.map +1 -1
  21. package/dist/icons/chevron-left.d.ts +3 -0
  22. package/dist/icons/chevron-left.d.ts.map +1 -0
  23. package/dist/icons/chevron-right.d.ts +3 -0
  24. package/dist/icons/chevron-right.d.ts.map +1 -0
  25. package/dist/icons/index.d.ts +4 -0
  26. package/dist/icons/index.d.ts.map +1 -1
  27. package/dist/icons/status.d.ts +2 -0
  28. package/dist/icons/status.d.ts.map +1 -0
  29. package/dist/index.d.ts +21 -2
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/input-group/index.d.ts +17 -0
  32. package/dist/input-group/index.d.ts.map +1 -0
  33. package/dist/lib.d.ts +2 -0
  34. package/dist/lib.d.ts.map +1 -0
  35. package/dist/meter/index.d.ts +25 -0
  36. package/dist/meter/index.d.ts.map +1 -0
  37. package/dist/money-input/index.d.ts +70 -0
  38. package/dist/money-input/index.d.ts.map +1 -0
  39. package/dist/month-picker/index.d.ts +28 -0
  40. package/dist/month-picker/index.d.ts.map +1 -0
  41. package/dist/popover/index.d.ts.map +1 -1
  42. package/dist/segmented/index.d.ts +2 -1
  43. package/dist/segmented/index.d.ts.map +1 -1
  44. package/dist/select/index.d.ts +32 -0
  45. package/dist/select/index.d.ts.map +1 -0
  46. package/dist/slider/index.d.ts +25 -0
  47. package/dist/slider/index.d.ts.map +1 -0
  48. package/dist/tab-bar/index.d.ts +23 -0
  49. package/dist/tab-bar/index.d.ts.map +1 -0
  50. package/dist/table/index.d.ts +76 -7
  51. package/dist/table/index.d.ts.map +1 -1
  52. package/dist/tailwind.css +7 -0
  53. package/dist/theme.css +888 -0
  54. package/dist/toast/index.d.ts +11 -3
  55. package/dist/toast/index.d.ts.map +1 -1
  56. package/dist/tree/index.d.ts +79 -0
  57. package/dist/tree/index.d.ts.map +1 -0
  58. package/dist/utils/cx.d.ts +5 -0
  59. package/dist/utils/cx.d.ts.map +1 -1
  60. package/dist/utils/field-chevron.d.ts +6 -0
  61. package/dist/utils/field-chevron.d.ts.map +1 -0
  62. package/dist/utils/graphics.d.ts +20 -0
  63. package/dist/utils/graphics.d.ts.map +1 -0
  64. package/dist/utils/merge.d.ts +12 -0
  65. package/dist/utils/merge.d.ts.map +1 -0
  66. package/dist/utils/money.d.ts +30 -0
  67. package/dist/utils/money.d.ts.map +1 -0
  68. package/dist/utils/month.d.ts +16 -0
  69. package/dist/utils/month.d.ts.map +1 -0
  70. package/dist/utils/status-icons.d.ts +11 -0
  71. package/dist/utils/status-icons.d.ts.map +1 -0
  72. package/dist/utils/styles.d.ts +6 -0
  73. package/dist/utils/styles.d.ts.map +1 -1
  74. package/dist/utils/theme.d.ts +92 -0
  75. package/dist/utils/theme.d.ts.map +1 -0
  76. package/dist/utils/useAnchoredPopup.d.ts +36 -0
  77. package/dist/utils/useAnchoredPopup.d.ts.map +1 -0
  78. package/dist/utils/useHydrated.d.ts +3 -0
  79. package/dist/utils/useHydrated.d.ts.map +1 -0
  80. package/dist/utils/useVirtualList.d.ts +4 -0
  81. package/dist/utils/useVirtualList.d.ts.map +1 -1
  82. package/dist/variables.css +90 -0
  83. package/dist/zen.css +1 -1
  84. package/dist/zen.js +2107 -532
  85. package/package.json +38 -1
package/dist/theme.css ADDED
@@ -0,0 +1,888 @@
1
+ /*
2
+ * Zen's theme for Tailwind: tokens, custom utilities (glass, glow-edge, …) and
3
+ * component styles. Nothing here styles an app's own elements; that's the
4
+ * opt-in base.css.
5
+ */
6
+
7
+ /* Default theme, layered so an app's own (unlayered) token overrides always win. */
8
+ @import './variables.css' layer(base);
9
+
10
+ @custom-variant dark (&:is(.dark *, [data-theme='dark'] *));
11
+ @custom-variant light (&:is(.light *, [data-theme='light'] *));
12
+
13
+ /*
14
+ * inline: utilities use oklch(var(--primary)) directly rather than through
15
+ * --color-primary, so tokens set on any element (applyTheme's target, a .light
16
+ * section) recolour everything inside it, not only when set on :root.
17
+ */
18
+ @theme inline {
19
+ --color-border: oklch(var(--border));
20
+ --color-input: oklch(var(--input));
21
+ --color-ring: oklch(var(--ring));
22
+ --color-background: oklch(var(--background));
23
+ --color-foreground: oklch(var(--foreground));
24
+
25
+ --color-primary: oklch(var(--primary));
26
+ --color-primary-foreground: oklch(var(--primary-foreground));
27
+
28
+ --color-secondary: oklch(var(--secondary));
29
+ --color-secondary-foreground: oklch(var(--secondary-foreground));
30
+
31
+ --color-destructive: oklch(var(--destructive));
32
+ --color-destructive-foreground: oklch(var(--destructive-foreground));
33
+
34
+ --color-muted: oklch(var(--muted));
35
+ --color-muted-foreground: oklch(var(--muted-foreground));
36
+
37
+ --color-accent: oklch(var(--accent));
38
+ --color-accent-foreground: oklch(var(--accent-foreground));
39
+
40
+ --color-popover: oklch(var(--popover));
41
+ --color-popover-foreground: oklch(var(--popover-foreground));
42
+
43
+ --color-card: oklch(var(--card));
44
+ --color-card-foreground: oklch(var(--card-foreground));
45
+
46
+ --color-glow: oklch(var(--glow));
47
+ --color-glow-2: oklch(var(--glow-2));
48
+
49
+ --color-tint: oklch(var(--tint));
50
+
51
+ --radius-lg: var(--radius);
52
+ --radius-md: calc(var(--radius) - 2px);
53
+ --radius-sm: calc(var(--radius) - 4px);
54
+
55
+ --font-sans: 'Inter Variable', Inter, ui-sans-serif, system-ui, sans-serif;
56
+
57
+ --ease-out-soft: cubic-bezier(0.2, 0.8, 0.2, 1);
58
+
59
+ --animate-accordion-down: accordion-down 0.2s ease-out;
60
+ --animate-accordion-up: accordion-up 0.2s ease-out;
61
+
62
+ @keyframes accordion-down {
63
+ from {
64
+ height: 0;
65
+ }
66
+ to {
67
+ height: var(--accordion-content-height);
68
+ }
69
+ }
70
+ @keyframes accordion-up {
71
+ from {
72
+ height: var(--accordion-content-height);
73
+ }
74
+ to {
75
+ height: 0;
76
+ }
77
+ }
78
+ }
79
+
80
+ /* ───────────────────────── Glass surfaces ───────────────────────── */
81
+
82
+ /*
83
+ * Tinted glass without a live blur: a backdrop that animates would force a
84
+ * blur to be recomputed every frame under every card. Overlays that sit above
85
+ * content (dialogs, popups, toasts) add `glass-blur`.
86
+ */
87
+ @utility glass {
88
+ background: linear-gradient(180deg, oklch(var(--card) / 0.82), oklch(var(--card) / 0.7));
89
+ border: 1px solid oklch(var(--tint) / 0.07);
90
+ box-shadow:
91
+ inset 0 1px 0 hsl(0 0% 100% / 0.06),
92
+ var(--glass-shadow);
93
+ }
94
+
95
+ /*
96
+ * Border that lights up near the pointer. <Backdrop> writes the pointer
97
+ * position into each element's --gx/--gy, so neighbouring cards light up
98
+ * consistently as if from one light source.
99
+ */
100
+ /*
101
+ * The pointer position for glow-edge, per element. Not inherited: Backdrop
102
+ * updates it every pointer or scroll frame, and an inherited property would
103
+ * restyle everything inside the card each time. The edge layer takes it
104
+ * explicitly (inherit) instead.
105
+ */
106
+ @property --gx {
107
+ syntax: '<length>';
108
+ inherits: false;
109
+ initial-value: -999px;
110
+ }
111
+ @property --gy {
112
+ syntax: '<length>';
113
+ inherits: false;
114
+ initial-value: -999px;
115
+ }
116
+
117
+ @utility glow-edge {
118
+ position: relative;
119
+ isolation: isolate;
120
+ &::before {
121
+ content: '';
122
+ --gx: inherit;
123
+ --gy: inherit;
124
+ position: absolute;
125
+ /* Inside the box (over its 1px border), so it never causes overflow or scrollbars. */
126
+ inset: 0;
127
+ border-radius: inherit;
128
+ padding: 1px;
129
+ background: radial-gradient(
130
+ 360px circle at var(--gx, -999px) var(--gy, -999px),
131
+ oklch(var(--glow) / calc(0.7 * var(--glow-strength))),
132
+ oklch(var(--glow-2) / calc(0.2 * var(--glow-strength))) 40%,
133
+ transparent 65%
134
+ );
135
+ -webkit-mask:
136
+ linear-gradient(#000 0 0) content-box,
137
+ linear-gradient(#000 0 0);
138
+ -webkit-mask-composite: xor;
139
+ mask:
140
+ linear-gradient(#000 0 0) content-box exclude,
141
+ linear-gradient(#000 0 0);
142
+ pointer-events: none;
143
+ z-index: 1;
144
+ }
145
+ }
146
+
147
+ /*
148
+ * Built from Tailwind's backdrop utilities, which emit the standard and -webkit-
149
+ * properties correctly. Phones: a live blur over scrolling content is the main
150
+ * cause of jank, so touch screens get a solid tint instead.
151
+ */
152
+ @utility glass-blur {
153
+ @apply backdrop-blur-[18px] backdrop-saturate-140;
154
+ /* !: glass's `background` shorthand would otherwise reset the colour. */
155
+ @apply pointer-coarse:bg-card/96! pointer-coarse:backdrop-filter-none;
156
+ @apply [@media(hover:none)]:bg-card/96! [@media(hover:none)]:backdrop-filter-none;
157
+ }
158
+
159
+ @utility text-glow {
160
+ text-shadow: 0 0 18px oklch(var(--glow) / calc(0.55 * var(--glow-strength)));
161
+ }
162
+
163
+ /* Gradient headline text. */
164
+ @utility text-aurora {
165
+ background: linear-gradient(100deg, oklch(0.98 0 0), oklch(var(--primary)) 85%);
166
+ -webkit-background-clip: text;
167
+ background-clip: text;
168
+ color: transparent;
169
+ /* Light theme: start from the ink colour, since near-white would vanish. */
170
+ &:where(.light *, [data-theme='light'] *) {
171
+ background-image: linear-gradient(100deg, oklch(var(--foreground)), oklch(var(--primary)) 85%);
172
+ }
173
+ }
174
+
175
+ /* ───────────────────────── Lite graphics ───────────────────────── */
176
+
177
+ /*
178
+ * Without hardware acceleration (html[data-zen-graphics='lite'], set by
179
+ * utils/graphics.ts) every blur, full-screen mask and animation is drawn on the
180
+ * CPU each frame. Keep the look, drop the cost: solid tints instead of live
181
+ * blur (as on phones), a still backdrop, no pointer light. The OS "reduce
182
+ * transparency" setting gets the same tints.
183
+ */
184
+ html[data-zen-graphics='lite'] .glass-blur {
185
+ -webkit-backdrop-filter: none !important;
186
+ backdrop-filter: none !important;
187
+ background: oklch(var(--card) / 0.96) !important;
188
+ }
189
+ html[data-zen-graphics='lite'] .zen__header {
190
+ -webkit-backdrop-filter: none;
191
+ backdrop-filter: none;
192
+ background-color: oklch(var(--background) / 0.95);
193
+ }
194
+ html[data-zen-graphics='lite'] .zen__dialog::backdrop {
195
+ -webkit-backdrop-filter: none;
196
+ backdrop-filter: none;
197
+ }
198
+ html[data-zen-graphics='lite'] :is(.zen-aurora, .zen-dots--ambient) {
199
+ animation: none;
200
+ will-change: auto;
201
+ }
202
+ html[data-zen-graphics='lite'] :is(.zen-topo--lit, .zen-dots--lit) {
203
+ display: none;
204
+ }
205
+ @media (prefers-reduced-transparency: reduce) {
206
+ .glass-blur {
207
+ -webkit-backdrop-filter: none !important;
208
+ backdrop-filter: none !important;
209
+ background: oklch(var(--card) / 0.96) !important;
210
+ }
211
+ .zen__header {
212
+ -webkit-backdrop-filter: none;
213
+ backdrop-filter: none;
214
+ background-color: oklch(var(--background) / 0.95);
215
+ }
216
+ }
217
+
218
+ /*
219
+ * Sticky table cells: clear glass at rest; frosted while content passes under
220
+ * their edge (TableContainer sets data-under-*), so it blurs instead of showing
221
+ * through. Where there's no blur (no GPU, touch, reduced transparency), solid.
222
+ * No transition: animating every sticky cell's frost costs frames mid-scroll.
223
+ */
224
+ .zen__table-container[data-under-top] .zen__sticky-top,
225
+ .zen__table-container[data-under-left] .zen__sticky-left,
226
+ .zen__table-container[data-under-bottom] .zen__sticky-bottom {
227
+ background-color: oklch(var(--card) / 0.6);
228
+ -webkit-backdrop-filter: blur(18px) saturate(1.4);
229
+ backdrop-filter: blur(18px) saturate(1.4);
230
+ }
231
+ html[data-zen-graphics='lite'] .zen__table-container[data-under-top] .zen__sticky-top,
232
+ html[data-zen-graphics='lite'] .zen__table-container[data-under-left] .zen__sticky-left,
233
+ html[data-zen-graphics='lite'] .zen__table-container[data-under-bottom] .zen__sticky-bottom {
234
+ -webkit-backdrop-filter: none;
235
+ backdrop-filter: none;
236
+ background-color: oklch(var(--card));
237
+ }
238
+ @media (hover: none), (pointer: coarse), (prefers-reduced-transparency: reduce) {
239
+ .zen__table-container[data-under-top] .zen__sticky-top,
240
+ .zen__table-container[data-under-left] .zen__sticky-left,
241
+ .zen__table-container[data-under-bottom] .zen__sticky-bottom {
242
+ -webkit-backdrop-filter: none;
243
+ backdrop-filter: none;
244
+ background-color: oklch(var(--card));
245
+ }
246
+ }
247
+
248
+ /* ───────────────────────── Code ───────────────────────── */
249
+
250
+ /*
251
+ * Syntax colours for CodeBlock, in the --sh-* variables sugar-high reads
252
+ * (other highlighters can map their own classes to these).
253
+ */
254
+ .zen__code-block {
255
+ --sh-class: oklch(var(--primary));
256
+ --sh-identifier: oklch(var(--foreground));
257
+ --sh-sign: oklch(var(--muted-foreground));
258
+ --sh-property: oklch(0.8 0.1 250);
259
+ --sh-entity: oklch(0.78 0.12 200);
260
+ --sh-jsxliterals: oklch(var(--foreground) / 0.85);
261
+ --sh-string: oklch(0.8 0.12 150);
262
+ --sh-keyword: oklch(0.75 0.14 330);
263
+ --sh-comment: oklch(var(--muted-foreground) / 0.8);
264
+ }
265
+ :is(.light, [data-theme='light']) .zen__code-block {
266
+ --sh-property: oklch(0.5 0.15 250);
267
+ --sh-entity: oklch(0.5 0.12 200);
268
+ --sh-string: oklch(0.5 0.14 150);
269
+ --sh-keyword: oklch(0.5 0.2 330);
270
+ }
271
+
272
+ /* ───────────────────────── Backdrop: contours / dots + light ───────────────────────── */
273
+
274
+ .zen-backdrop {
275
+ position: fixed;
276
+ inset: 0;
277
+ z-index: -1;
278
+ overflow: hidden;
279
+ pointer-events: none;
280
+ background: oklch(var(--background));
281
+ }
282
+
283
+ /*
284
+ * Slow-moving aurora glows: three soft lights in one layer. They drift along an
285
+ * ease-in-out path in small steps (about 6 a second, a pixel or two each,
286
+ * invisible on a soft glow) rather than every frame, and they are painted as
287
+ * gradients of one element instead of three large layers of their own: extra
288
+ * full-screen layers are what the GPU pays for on every frame, and anything
289
+ * moving behind the page makes the whole screen redraw, blurs included.
290
+ * (--zen-*-dx/dy/s: each light's drift and scale, animated below. Every step lasts
291
+ * exactly 1/6s in all three, so they repaint together, about 6 times a second.)
292
+ */
293
+ @property --zen-a-dx {
294
+ syntax: '<length>';
295
+ inherits: false;
296
+ initial-value: 0px;
297
+ }
298
+ @property --zen-a-dy {
299
+ syntax: '<length>';
300
+ inherits: false;
301
+ initial-value: 0px;
302
+ }
303
+ @property --zen-a-s {
304
+ syntax: '<number>';
305
+ inherits: false;
306
+ initial-value: 1;
307
+ }
308
+ @property --zen-b-dx {
309
+ syntax: '<length>';
310
+ inherits: false;
311
+ initial-value: 0px;
312
+ }
313
+ @property --zen-b-dy {
314
+ syntax: '<length>';
315
+ inherits: false;
316
+ initial-value: 0px;
317
+ }
318
+ @property --zen-b-s {
319
+ syntax: '<number>';
320
+ inherits: false;
321
+ initial-value: 1;
322
+ }
323
+ @property --zen-c-dx {
324
+ syntax: '<length>';
325
+ inherits: false;
326
+ initial-value: 0px;
327
+ }
328
+ @property --zen-c-dy {
329
+ syntax: '<length>';
330
+ inherits: false;
331
+ initial-value: 0px;
332
+ }
333
+ @property --zen-c-s {
334
+ syntax: '<number>';
335
+ inherits: false;
336
+ initial-value: 1;
337
+ }
338
+ .zen-aurora {
339
+ position: absolute;
340
+ inset: 0;
341
+ /* Faded as one layer, like the separate glows it replaces were, so it renders the same. */
342
+ opacity: calc(0.16 * var(--glow-strength));
343
+ background-repeat: no-repeat;
344
+ /* Last listed is drawn first: a, then b, then c on top. */
345
+ background-image:
346
+ radial-gradient(
347
+ circle calc(22.5vmax * var(--zen-c-s)) at calc(40% + 22.5vmax + var(--zen-c-dx))
348
+ calc(30% + 22.5vmax + var(--zen-c-dy)),
349
+ oklch(var(--glow) / 0.6),
350
+ transparent
351
+ ),
352
+ radial-gradient(
353
+ circle calc(35vmax * var(--zen-b-s)) at calc(100% - 10vmax + var(--zen-b-dx)) calc(100% + var(--zen-b-dy)),
354
+ oklch(var(--glow-2) / 0.7),
355
+ transparent
356
+ ),
357
+ radial-gradient(
358
+ circle calc(35vmax * var(--zen-a-s)) at calc(25vmax + var(--zen-a-dx)) calc(-13vmax + var(--zen-a-dy)),
359
+ oklch(var(--glow) / 0.9),
360
+ transparent
361
+ );
362
+ animation:
363
+ zen-aurora-a 38s steps(19) infinite alternate,
364
+ zen-aurora-b 46s steps(23) infinite alternate,
365
+ zen-aurora-c 52s steps(26) infinite alternate;
366
+ }
367
+
368
+ /*
369
+ * Contour lines (image from <Backdrop topoSrc>), faint everywhere… The image
370
+ * is used as a mask over the tint, so its strokes are white on dark and ink
371
+ * on light.
372
+ */
373
+ .zen-topo {
374
+ position: absolute;
375
+ inset: 0;
376
+ background: oklch(var(--tint));
377
+ -webkit-mask: var(--zen-topo) center / cover no-repeat;
378
+ mask: var(--zen-topo) center / cover no-repeat;
379
+ opacity: calc(0.1 * var(--topo-strength));
380
+ }
381
+ /* …and lit up around the pointer, tinted with the glow colour. */
382
+ .zen-topo--lit {
383
+ opacity: calc(0.6 * var(--topo-strength) * var(--glow-strength));
384
+ background: none;
385
+ transition: opacity 200ms;
386
+ }
387
+ /*
388
+ * The light is a box the size of its gradient that Backdrop moves to the
389
+ * pointer (left/top), not a full-screen gradient re-aimed at it, so a pointer
390
+ * move repaints only where the light was and is. It starts off-screen.
391
+ */
392
+ .zen-light {
393
+ position: absolute;
394
+ left: -9999px;
395
+ top: -9999px;
396
+ }
397
+ .zen-topo--lit > .zen-light {
398
+ width: 840px;
399
+ height: 840px;
400
+ margin: -420px 0 0 -420px;
401
+ background: radial-gradient(
402
+ 420px circle at center,
403
+ oklch(var(--glow) / 0.9),
404
+ oklch(var(--glow-2) / 0.3) 45%,
405
+ transparent 70%
406
+ );
407
+ }
408
+
409
+ /*
410
+ * Dot grid: a dense 12px grid of 1px dots, barely there; the aurora's drifting
411
+ * colours show through the same dot mask; near the pointer a small light
412
+ * brings the dots up in the glow colour.
413
+ */
414
+ .zen-dots {
415
+ position: absolute;
416
+ inset: 0;
417
+ opacity: var(--topo-strength);
418
+ background-image: radial-gradient(circle, oklch(var(--muted-foreground) / 0.13) 1px, transparent 1px);
419
+ background-size: 12px 12px;
420
+ }
421
+ .zen-dots--ambient,
422
+ .zen-dots--lit {
423
+ background-size: auto;
424
+ -webkit-mask: radial-gradient(circle, #000 1px, transparent 1px) 0 0 / 12px 12px;
425
+ mask: radial-gradient(circle, #000 1px, transparent 1px) 0 0 / 12px 12px;
426
+ }
427
+ /* Only a faint tint (never as bright as the pointer light, so it can't be mistaken for it). */
428
+ .zen-dots--ambient {
429
+ opacity: min(0.6, calc(1.1 * var(--topo-strength) * var(--glow-strength)));
430
+ background-image:
431
+ radial-gradient(60vmax 45vmax at 15% 10%, oklch(var(--glow) / 0.45), transparent 75%),
432
+ radial-gradient(55vmax 42vmax at 90% 95%, oklch(var(--glow-2) / 0.4), transparent 75%);
433
+ background-repeat: no-repeat;
434
+ /* The colours drift; the dot mask stays put, so these dots never slide off the grid. */
435
+ animation: zen-dots-drift 60s steps(360) infinite alternate;
436
+ }
437
+ .zen-dots--lit {
438
+ opacity: min(1, calc(100 * var(--topo-strength) * var(--glow-strength)));
439
+ background-image: none;
440
+ transition: opacity 200ms;
441
+ }
442
+ /* The dots' pointer light: a box moved to the pointer, like the contour light. */
443
+ .zen-dots--lit > .zen-light {
444
+ width: 220px;
445
+ height: 220px;
446
+ margin: -110px 0 0 -110px;
447
+ background-image: radial-gradient(
448
+ 110px circle at center,
449
+ oklch(var(--glow)),
450
+ oklch(var(--glow) / 0.35) 45%,
451
+ transparent 75%
452
+ );
453
+ }
454
+ /* Pointer left the window: the lights fade out where they are. */
455
+ .zen-backdrop[data-pointer='out'] .zen-dots--lit,
456
+ .zen-backdrop[data-pointer='out'] .zen-topo--lit {
457
+ opacity: 0;
458
+ }
459
+
460
+ /* Vignette so content edges stay calm. */
461
+ .zen-vignette {
462
+ position: absolute;
463
+ inset: 0;
464
+ background: radial-gradient(ellipse at 50% 20%, transparent 40%, oklch(var(--background) / 0.85) 100%);
465
+ }
466
+
467
+ /* Phones: a still background (no drifting glows, no pointer light) keeps scrolling smooth. */
468
+ @media (hover: none), (pointer: coarse) {
469
+ .zen-aurora {
470
+ animation: none;
471
+ will-change: auto;
472
+ }
473
+ .zen-topo--lit,
474
+ .zen-dots--lit {
475
+ display: none;
476
+ }
477
+ .zen-dots--ambient {
478
+ animation: none;
479
+ }
480
+ }
481
+
482
+ @keyframes zen-aurora-a {
483
+ 0% {
484
+ --zen-a-dx: 0vmax;
485
+ --zen-a-dy: 0vmax;
486
+ --zen-a-s: 1;
487
+ }
488
+ 8.3333% {
489
+ --zen-a-dx: 0.1633vmax;
490
+ --zen-a-dy: 0.1088vmax;
491
+ --zen-a-s: 1.0014;
492
+ }
493
+ 16.6667% {
494
+ --zen-a-dx: 0.6736vmax;
495
+ --zen-a-dy: 0.449vmax;
496
+ --zen-a-s: 1.0056;
497
+ }
498
+ 25% {
499
+ --zen-a-dx: 1.5499vmax;
500
+ --zen-a-dy: 1.0333vmax;
501
+ --zen-a-s: 1.0129;
502
+ }
503
+ 33.3333% {
504
+ --zen-a-dx: 2.7813vmax;
505
+ --zen-a-dy: 1.8542vmax;
506
+ --zen-a-s: 1.0232;
507
+ }
508
+ 41.6667% {
509
+ --zen-a-dx: 4.3059vmax;
510
+ --zen-a-dy: 2.8706vmax;
511
+ --zen-a-s: 1.0359;
512
+ }
513
+ 50% {
514
+ --zen-a-dx: 6vmax;
515
+ --zen-a-dy: 4vmax;
516
+ --zen-a-s: 1.05;
517
+ }
518
+ 58.3333% {
519
+ --zen-a-dx: 7.6941vmax;
520
+ --zen-a-dy: 5.1294vmax;
521
+ --zen-a-s: 1.0641;
522
+ }
523
+ 66.6667% {
524
+ --zen-a-dx: 9.2187vmax;
525
+ --zen-a-dy: 6.1458vmax;
526
+ --zen-a-s: 1.0768;
527
+ }
528
+ 75% {
529
+ --zen-a-dx: 10.4501vmax;
530
+ --zen-a-dy: 6.9667vmax;
531
+ --zen-a-s: 1.0871;
532
+ }
533
+ 83.3333% {
534
+ --zen-a-dx: 11.3264vmax;
535
+ --zen-a-dy: 7.551vmax;
536
+ --zen-a-s: 1.0944;
537
+ }
538
+ 91.6667% {
539
+ --zen-a-dx: 11.8367vmax;
540
+ --zen-a-dy: 7.8912vmax;
541
+ --zen-a-s: 1.0986;
542
+ }
543
+ 100% {
544
+ --zen-a-dx: 12vmax;
545
+ --zen-a-dy: 8vmax;
546
+ --zen-a-s: 1.1;
547
+ }
548
+ }
549
+ @keyframes zen-aurora-b {
550
+ 0% {
551
+ --zen-b-dx: -0vmax;
552
+ --zen-b-dy: -0vmax;
553
+ --zen-b-s: 1;
554
+ }
555
+ 8.3333% {
556
+ --zen-b-dx: -0.1905vmax;
557
+ --zen-b-dy: -0.0816vmax;
558
+ --zen-b-s: 0.9993;
559
+ }
560
+ 16.6667% {
561
+ --zen-b-dx: -0.7858vmax;
562
+ --zen-b-dy: -0.3368vmax;
563
+ --zen-b-s: 0.9972;
564
+ }
565
+ 25% {
566
+ --zen-b-dx: -1.8083vmax;
567
+ --zen-b-dy: -0.775vmax;
568
+ --zen-b-s: 0.9935;
569
+ }
570
+ 33.3333% {
571
+ --zen-b-dx: -3.2448vmax;
572
+ --zen-b-dy: -1.3906vmax;
573
+ --zen-b-s: 0.9884;
574
+ }
575
+ 41.6667% {
576
+ --zen-b-dx: -5.0236vmax;
577
+ --zen-b-dy: -2.153vmax;
578
+ --zen-b-s: 0.9821;
579
+ }
580
+ 50% {
581
+ --zen-b-dx: -7vmax;
582
+ --zen-b-dy: -3vmax;
583
+ --zen-b-s: 0.975;
584
+ }
585
+ 58.3333% {
586
+ --zen-b-dx: -8.9764vmax;
587
+ --zen-b-dy: -3.847vmax;
588
+ --zen-b-s: 0.9679;
589
+ }
590
+ 66.6667% {
591
+ --zen-b-dx: -10.7552vmax;
592
+ --zen-b-dy: -4.6094vmax;
593
+ --zen-b-s: 0.9616;
594
+ }
595
+ 75% {
596
+ --zen-b-dx: -12.1917vmax;
597
+ --zen-b-dy: -5.225vmax;
598
+ --zen-b-s: 0.9565;
599
+ }
600
+ 83.3333% {
601
+ --zen-b-dx: -13.2142vmax;
602
+ --zen-b-dy: -5.6632vmax;
603
+ --zen-b-s: 0.9528;
604
+ }
605
+ 91.6667% {
606
+ --zen-b-dx: -13.8095vmax;
607
+ --zen-b-dy: -5.9184vmax;
608
+ --zen-b-s: 0.9507;
609
+ }
610
+ 100% {
611
+ --zen-b-dx: -14vmax;
612
+ --zen-b-dy: -6vmax;
613
+ --zen-b-s: 0.95;
614
+ }
615
+ }
616
+ @keyframes zen-aurora-c {
617
+ 0% {
618
+ --zen-c-dx: -0vmax;
619
+ --zen-c-dy: 0vmax;
620
+ --zen-c-s: 1;
621
+ }
622
+ 8.3333% {
623
+ --zen-c-dx: -0.136vmax;
624
+ --zen-c-dy: 0.0816vmax;
625
+ --zen-c-s: 1.002;
626
+ }
627
+ 16.6667% {
628
+ --zen-c-dx: -0.5613vmax;
629
+ --zen-c-dy: 0.3368vmax;
630
+ --zen-c-s: 1.0084;
631
+ }
632
+ 25% {
633
+ --zen-c-dx: -1.2916vmax;
634
+ --zen-c-dy: 0.775vmax;
635
+ --zen-c-s: 1.0194;
636
+ }
637
+ 33.3333% {
638
+ --zen-c-dx: -2.3177vmax;
639
+ --zen-c-dy: 1.3906vmax;
640
+ --zen-c-s: 1.0348;
641
+ }
642
+ 41.6667% {
643
+ --zen-c-dx: -3.5883vmax;
644
+ --zen-c-dy: 2.153vmax;
645
+ --zen-c-s: 1.0538;
646
+ }
647
+ 50% {
648
+ --zen-c-dx: -5vmax;
649
+ --zen-c-dy: 3vmax;
650
+ --zen-c-s: 1.075;
651
+ }
652
+ 58.3333% {
653
+ --zen-c-dx: -6.4117vmax;
654
+ --zen-c-dy: 3.847vmax;
655
+ --zen-c-s: 1.0962;
656
+ }
657
+ 66.6667% {
658
+ --zen-c-dx: -7.6823vmax;
659
+ --zen-c-dy: 4.6094vmax;
660
+ --zen-c-s: 1.1152;
661
+ }
662
+ 75% {
663
+ --zen-c-dx: -8.7084vmax;
664
+ --zen-c-dy: 5.225vmax;
665
+ --zen-c-s: 1.1306;
666
+ }
667
+ 83.3333% {
668
+ --zen-c-dx: -9.4387vmax;
669
+ --zen-c-dy: 5.6632vmax;
670
+ --zen-c-s: 1.1416;
671
+ }
672
+ 91.6667% {
673
+ --zen-c-dx: -9.864vmax;
674
+ --zen-c-dy: 5.9184vmax;
675
+ --zen-c-s: 1.148;
676
+ }
677
+ 100% {
678
+ --zen-c-dx: -10vmax;
679
+ --zen-c-dy: 6vmax;
680
+ --zen-c-s: 1.15;
681
+ }
682
+ }
683
+ @keyframes zen-dots-drift {
684
+ to {
685
+ background-position:
686
+ 6vmax 4vmax,
687
+ -6vmax -3vmax;
688
+ }
689
+ }
690
+
691
+ /* ───────────────────────── Motion ───────────────────────── */
692
+
693
+ /* Popovers flip to stay on screen where anchor positioning is supported. */
694
+ @supports (position-area: block-end) {
695
+ .zen__popover {
696
+ position-try-options:
697
+ flip-block,
698
+ flip-inline,
699
+ flip-block flip-inline;
700
+ position-visibility: anchors-visible;
701
+ }
702
+ }
703
+
704
+ /* Popovers scale and fade in from their top-layer entry, and back out. */
705
+ .zen__popover {
706
+ transition:
707
+ opacity 150ms,
708
+ scale 150ms,
709
+ overlay 150ms allow-discrete,
710
+ display 150ms allow-discrete;
711
+ }
712
+ .zen__dialog {
713
+ transition:
714
+ opacity 200ms var(--ease-out-soft),
715
+ scale 200ms var(--ease-out-soft),
716
+ overlay 200ms allow-discrete,
717
+ display 200ms allow-discrete;
718
+ }
719
+ .zen__dialog::backdrop {
720
+ transition:
721
+ opacity 150ms,
722
+ overlay 150ms allow-discrete,
723
+ display 150ms allow-discrete;
724
+ }
725
+ .zen__popover:not(:popover-open),
726
+ .zen__dialog:not([open]) {
727
+ opacity: 0;
728
+ scale: 0.95;
729
+ }
730
+ .zen__dialog:not([open])::backdrop {
731
+ opacity: 0;
732
+ }
733
+ @starting-style {
734
+ .zen__popover:popover-open,
735
+ .zen__dialog[open] {
736
+ opacity: 0;
737
+ scale: 0.95;
738
+ }
739
+ .zen__dialog[open]::backdrop {
740
+ opacity: 0;
741
+ }
742
+ }
743
+ /*
744
+ * The page behind a modal dialog doesn't scroll. Hiding the overflow removes
745
+ * the scrollbar, so where the page had one (Dialog sets data-zen-scrollbar),
746
+ * its space stays reserved and the page doesn't shift sideways.
747
+ */
748
+ :root:has(.zen__dialog[open]) {
749
+ overflow: hidden;
750
+ }
751
+ :root[data-zen-scrollbar]:has(.zen__dialog[open]) {
752
+ scrollbar-gutter: stable;
753
+ }
754
+
755
+ /*
756
+ * Tree rows opening and closing (useTree): each cell's content box grows from 0
757
+ * to the row height (or shrinks back), so rows below glide instead of jumping.
758
+ * Content fades in a little after the space opens, and out before it closes.
759
+ */
760
+ @keyframes zen-row-open {
761
+ from {
762
+ height: 0;
763
+ opacity: 0;
764
+ }
765
+ 60% {
766
+ opacity: 0;
767
+ }
768
+ to {
769
+ height: var(--row-h);
770
+ opacity: 1;
771
+ }
772
+ }
773
+ @keyframes zen-row-close {
774
+ from {
775
+ height: var(--row-h);
776
+ opacity: 1;
777
+ }
778
+ 40% {
779
+ opacity: 0;
780
+ }
781
+ to {
782
+ height: 0;
783
+ opacity: 0;
784
+ }
785
+ }
786
+ .zen__tree-row-enter .zen__tree-cell,
787
+ .zen__tree-row-exit .zen__tree-cell {
788
+ overflow: hidden;
789
+ }
790
+ .zen__tree-row-enter .zen__tree-cell {
791
+ animation: zen-row-open 260ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
792
+ }
793
+ .zen__tree-row-exit .zen__tree-cell {
794
+ animation: zen-row-close 220ms cubic-bezier(0.4, 0, 0.2, 1) both;
795
+ }
796
+ /* While a row is 0 tall its border would still show as a stacked line. */
797
+ .zen__tree-row-enter > td,
798
+ .zen__tree-row-exit > td {
799
+ border-bottom-color: transparent;
800
+ transition: border-color 200ms;
801
+ }
802
+
803
+ /* Direct children of a .rise container enter one after another. */
804
+ @keyframes zen-rise {
805
+ from {
806
+ opacity: 0;
807
+ transform: translateY(8px);
808
+ }
809
+ }
810
+ .rise > * {
811
+ animation: zen-rise 0.5s var(--ease-out-soft) both;
812
+ }
813
+ .rise > *:nth-child(2) {
814
+ animation-delay: 50ms;
815
+ }
816
+ .rise > *:nth-child(3) {
817
+ animation-delay: 100ms;
818
+ }
819
+ .rise > *:nth-child(4) {
820
+ animation-delay: 150ms;
821
+ }
822
+ .rise > *:nth-child(n + 5) {
823
+ animation-delay: 200ms;
824
+ }
825
+
826
+ /* Loading placeholders: a soft light sweeping across. */
827
+ .zen__skeleton {
828
+ background: linear-gradient(
829
+ 100deg,
830
+ oklch(var(--tint) / 0.04) 30%,
831
+ oklch(var(--tint) / 0.09) 50%,
832
+ oklch(var(--tint) / 0.04) 70%
833
+ )
834
+ 0 0 / 300% 100%;
835
+ animation: zen-shimmer 1.6s ease-in-out infinite;
836
+ }
837
+ @keyframes zen-shimmer {
838
+ from {
839
+ background-position: 100% 0;
840
+ }
841
+ to {
842
+ background-position: 0 0;
843
+ }
844
+ }
845
+
846
+ /* Time left before a toast goes away. */
847
+ .zen__toast-timer {
848
+ animation: zen-toast-timer linear forwards;
849
+ }
850
+ @keyframes zen-toast-timer {
851
+ to {
852
+ transform: scaleX(0);
853
+ }
854
+ }
855
+
856
+ @keyframes zen-pop {
857
+ 0% {
858
+ transform: scale(0.4);
859
+ opacity: 0;
860
+ }
861
+ 70% {
862
+ transform: scale(1.2);
863
+ }
864
+ }
865
+
866
+ /* Reduced motion: the OS setting, or opt in with html.reduce-motion. */
867
+ @media (prefers-reduced-motion: reduce) {
868
+ .zen-backdrop *,
869
+ .zen-backdrop *::before,
870
+ [class*='zen__'],
871
+ [class*='zen__']::before,
872
+ [class*='zen__']::backdrop,
873
+ .rise > * {
874
+ animation-duration: 0.01ms !important;
875
+ animation-iteration-count: 1 !important;
876
+ transition-duration: 0.01ms !important;
877
+ }
878
+ }
879
+ html.reduce-motion .zen-backdrop *,
880
+ html.reduce-motion .zen-backdrop *::before,
881
+ html.reduce-motion [class*='zen__'],
882
+ html.reduce-motion [class*='zen__']::before,
883
+ html.reduce-motion [class*='zen__']::backdrop,
884
+ html.reduce-motion .rise > * {
885
+ animation-duration: 0.01ms !important;
886
+ animation-iteration-count: 1 !important;
887
+ transition-duration: 0.01ms !important;
888
+ }