@zyncat/ui 0.8.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 (334) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +133 -0
  3. package/dist/alert.css +132 -0
  4. package/dist/alert.d.ts +46 -0
  5. package/dist/alert.js +80 -0
  6. package/dist/alert.js.map +1 -0
  7. package/dist/avatar-group.d.ts +24 -0
  8. package/dist/avatar-group.js +23 -0
  9. package/dist/avatar-group.js.map +1 -0
  10. package/dist/avatar.css +200 -0
  11. package/dist/avatar.d.ts +35 -0
  12. package/dist/avatar.js +67 -0
  13. package/dist/avatar.js.map +1 -0
  14. package/dist/badge.css +182 -0
  15. package/dist/badge.d.ts +34 -0
  16. package/dist/badge.js +4 -0
  17. package/dist/badge.js.map +1 -0
  18. package/dist/button.css +269 -0
  19. package/dist/button.d.ts +44 -0
  20. package/dist/button.js +4 -0
  21. package/dist/button.js.map +1 -0
  22. package/dist/checkbox.css +177 -0
  23. package/dist/checkbox.d.ts +35 -0
  24. package/dist/checkbox.js +5 -0
  25. package/dist/checkbox.js.map +1 -0
  26. package/dist/chunk-2ERBBFZS.js +12 -0
  27. package/dist/chunk-2ERBBFZS.js.map +1 -0
  28. package/dist/chunk-2HORSLXJ.js +191 -0
  29. package/dist/chunk-2HORSLXJ.js.map +1 -0
  30. package/dist/chunk-36WVAQKL.js +40 -0
  31. package/dist/chunk-36WVAQKL.js.map +1 -0
  32. package/dist/chunk-3SBCBYB7.js +575 -0
  33. package/dist/chunk-3SBCBYB7.js.map +1 -0
  34. package/dist/chunk-4R2GQO43.js +125 -0
  35. package/dist/chunk-4R2GQO43.js.map +1 -0
  36. package/dist/chunk-64UU4LGU.js +258 -0
  37. package/dist/chunk-64UU4LGU.js.map +1 -0
  38. package/dist/chunk-7SZCJ7WI.js +35 -0
  39. package/dist/chunk-7SZCJ7WI.js.map +1 -0
  40. package/dist/chunk-BIDVNLJO.js +38 -0
  41. package/dist/chunk-BIDVNLJO.js.map +1 -0
  42. package/dist/chunk-CONSWNIV.js +17 -0
  43. package/dist/chunk-CONSWNIV.js.map +1 -0
  44. package/dist/chunk-EQ32F2QJ.js +75 -0
  45. package/dist/chunk-EQ32F2QJ.js.map +1 -0
  46. package/dist/chunk-GY7JOPUQ.js +111 -0
  47. package/dist/chunk-GY7JOPUQ.js.map +1 -0
  48. package/dist/chunk-I4XQ4PV2.js +30 -0
  49. package/dist/chunk-I4XQ4PV2.js.map +1 -0
  50. package/dist/chunk-JSKXZMLH.js +67 -0
  51. package/dist/chunk-JSKXZMLH.js.map +1 -0
  52. package/dist/chunk-L3Q3VDIW.js +295 -0
  53. package/dist/chunk-L3Q3VDIW.js.map +1 -0
  54. package/dist/chunk-LDC3RIYA.js +62 -0
  55. package/dist/chunk-LDC3RIYA.js.map +1 -0
  56. package/dist/chunk-MKIQFK7S.js +43 -0
  57. package/dist/chunk-MKIQFK7S.js.map +1 -0
  58. package/dist/chunk-MMRIQNEN.js +20 -0
  59. package/dist/chunk-MMRIQNEN.js.map +1 -0
  60. package/dist/chunk-MXOPCFWR.js +59 -0
  61. package/dist/chunk-MXOPCFWR.js.map +1 -0
  62. package/dist/chunk-NEF5XAHG.js +17 -0
  63. package/dist/chunk-NEF5XAHG.js.map +1 -0
  64. package/dist/chunk-NNXJCLTV.js +22 -0
  65. package/dist/chunk-NNXJCLTV.js.map +1 -0
  66. package/dist/chunk-O4N2OBTR.js +94 -0
  67. package/dist/chunk-O4N2OBTR.js.map +1 -0
  68. package/dist/chunk-PMECHR3A.js +38 -0
  69. package/dist/chunk-PMECHR3A.js.map +1 -0
  70. package/dist/chunk-QDK66S4G.js +44 -0
  71. package/dist/chunk-QDK66S4G.js.map +1 -0
  72. package/dist/chunk-QGNC7LH5.js +8 -0
  73. package/dist/chunk-QGNC7LH5.js.map +1 -0
  74. package/dist/chunk-QMSJZCK3.js +398 -0
  75. package/dist/chunk-QMSJZCK3.js.map +1 -0
  76. package/dist/chunk-S5ZTZANS.js +57 -0
  77. package/dist/chunk-S5ZTZANS.js.map +1 -0
  78. package/dist/chunk-S7B34VGN.js +14 -0
  79. package/dist/chunk-S7B34VGN.js.map +1 -0
  80. package/dist/chunk-TCUCYFT7.js +25 -0
  81. package/dist/chunk-TCUCYFT7.js.map +1 -0
  82. package/dist/chunk-TYHR4VNM.js +193 -0
  83. package/dist/chunk-TYHR4VNM.js.map +1 -0
  84. package/dist/chunk-U5QQZC3U.js +56 -0
  85. package/dist/chunk-U5QQZC3U.js.map +1 -0
  86. package/dist/chunk-UE63OOVG.js +13 -0
  87. package/dist/chunk-UE63OOVG.js.map +1 -0
  88. package/dist/chunk-VTJTGVZZ.js +22 -0
  89. package/dist/chunk-VTJTGVZZ.js.map +1 -0
  90. package/dist/chunk-W3SHPKSB.js +49 -0
  91. package/dist/chunk-W3SHPKSB.js.map +1 -0
  92. package/dist/chunk-XZAEIQDR.js +188 -0
  93. package/dist/chunk-XZAEIQDR.js.map +1 -0
  94. package/dist/chunk-YGGCQNNU.js +92 -0
  95. package/dist/chunk-YGGCQNNU.js.map +1 -0
  96. package/dist/collapse.css +88 -0
  97. package/dist/collapse.d.ts +46 -0
  98. package/dist/collapse.js +5 -0
  99. package/dist/collapse.js.map +1 -0
  100. package/dist/count-badge.d.ts +13 -0
  101. package/dist/count-badge.js +13 -0
  102. package/dist/count-badge.js.map +1 -0
  103. package/dist/date-field.d.ts +25 -0
  104. package/dist/date-field.js +87 -0
  105. package/dist/date-field.js.map +1 -0
  106. package/dist/date-picker.css +595 -0
  107. package/dist/date-range-field.d.ts +33 -0
  108. package/dist/date-range-field.js +404 -0
  109. package/dist/date-range-field.js.map +1 -0
  110. package/dist/datetime-field.d.ts +29 -0
  111. package/dist/datetime-field.js +155 -0
  112. package/dist/datetime-field.js.map +1 -0
  113. package/dist/dialog.css +180 -0
  114. package/dist/dialog.d.ts +42 -0
  115. package/dist/dialog.js +128 -0
  116. package/dist/dialog.js.map +1 -0
  117. package/dist/dom-props-C0jrllBw.d.ts +5 -0
  118. package/dist/field-chrome-CMPOQSLs.d.ts +28 -0
  119. package/dist/field-shell-Cuda-feL.d.ts +24 -0
  120. package/dist/glide.d.ts +15 -0
  121. package/dist/glide.js +5 -0
  122. package/dist/glide.js.map +1 -0
  123. package/dist/icon.css +33 -0
  124. package/dist/input.css +333 -0
  125. package/dist/mcp.js +359 -0
  126. package/dist/motion-devtools.css +265 -0
  127. package/dist/motion-devtools.d.ts +51 -0
  128. package/dist/motion-devtools.js +354 -0
  129. package/dist/motion-devtools.js.map +1 -0
  130. package/dist/motion-scale-Canq4stZ.d.ts +18 -0
  131. package/dist/motion-tokens.d.ts +28 -0
  132. package/dist/motion-tokens.js +3 -0
  133. package/dist/motion-tokens.js.map +1 -0
  134. package/dist/multi-select.d.ts +44 -0
  135. package/dist/multi-select.js +109 -0
  136. package/dist/multi-select.js.map +1 -0
  137. package/dist/number-field.d.ts +36 -0
  138. package/dist/number-field.js +112 -0
  139. package/dist/number-field.js.map +1 -0
  140. package/dist/odometer.css +62 -0
  141. package/dist/otp-field.d.ts +31 -0
  142. package/dist/otp-field.js +98 -0
  143. package/dist/otp-field.js.map +1 -0
  144. package/dist/overlay.css +23 -0
  145. package/dist/pagination.css +43 -0
  146. package/dist/pagination.d.ts +29 -0
  147. package/dist/pagination.js +107 -0
  148. package/dist/pagination.js.map +1 -0
  149. package/dist/popover.css +69 -0
  150. package/dist/popover.d.ts +35 -0
  151. package/dist/popover.js +15 -0
  152. package/dist/popover.js.map +1 -0
  153. package/dist/radio-group.css +300 -0
  154. package/dist/radio-group.d.ts +52 -0
  155. package/dist/radio-group.js +115 -0
  156. package/dist/radio-group.js.map +1 -0
  157. package/dist/select.css +416 -0
  158. package/dist/select.d.ts +49 -0
  159. package/dist/select.js +99 -0
  160. package/dist/select.js.map +1 -0
  161. package/dist/sheet.css +26 -0
  162. package/dist/sheet.d.ts +31 -0
  163. package/dist/sheet.js +14 -0
  164. package/dist/sheet.js.map +1 -0
  165. package/dist/status-badge.d.ts +19 -0
  166. package/dist/status-badge.js +72 -0
  167. package/dist/status-badge.js.map +1 -0
  168. package/dist/table.css +348 -0
  169. package/dist/table.d.ts +75 -0
  170. package/dist/table.js +262 -0
  171. package/dist/table.js.map +1 -0
  172. package/dist/tabs.css +208 -0
  173. package/dist/tabs.d.ts +71 -0
  174. package/dist/tabs.js +181 -0
  175. package/dist/tabs.js.map +1 -0
  176. package/dist/tag.css +248 -0
  177. package/dist/tag.d.ts +52 -0
  178. package/dist/tag.js +72 -0
  179. package/dist/tag.js.map +1 -0
  180. package/dist/text-field.d.ts +28 -0
  181. package/dist/text-field.js +69 -0
  182. package/dist/text-field.js.map +1 -0
  183. package/dist/textarea.css +219 -0
  184. package/dist/textarea.d.ts +38 -0
  185. package/dist/textarea.js +144 -0
  186. package/dist/textarea.js.map +1 -0
  187. package/dist/time-field.d.ts +28 -0
  188. package/dist/time-field.js +66 -0
  189. package/dist/time-field.js.map +1 -0
  190. package/dist/timing-7JA_zQHP.d.ts +27 -0
  191. package/dist/toast-store.d.ts +86 -0
  192. package/dist/toast-store.js +3 -0
  193. package/dist/toast-store.js.map +1 -0
  194. package/dist/toast.css +257 -0
  195. package/dist/toast.d.ts +12 -0
  196. package/dist/toast.js +310 -0
  197. package/dist/toast.js.map +1 -0
  198. package/dist/toggle-tag.d.ts +34 -0
  199. package/dist/toggle-tag.js +57 -0
  200. package/dist/toggle-tag.js.map +1 -0
  201. package/dist/toggle.css +152 -0
  202. package/dist/toggle.d.ts +31 -0
  203. package/dist/toggle.js +75 -0
  204. package/dist/toggle.js.map +1 -0
  205. package/dist/tooltip.css +86 -0
  206. package/dist/tooltip.d.ts +30 -0
  207. package/dist/tooltip.js +268 -0
  208. package/dist/tooltip.js.map +1 -0
  209. package/dist/types-SGMShK7R.d.ts +22 -0
  210. package/llms.txt +196 -0
  211. package/next.d.mts +7 -0
  212. package/next.mjs +59 -0
  213. package/package.json +257 -0
  214. package/src/components/composites/alert/Alert.tsx +141 -0
  215. package/src/components/composites/alert/alert.css +132 -0
  216. package/src/components/composites/date-picker/DateField.tsx +80 -0
  217. package/src/components/composites/date-picker/DateRangeField.tsx +110 -0
  218. package/src/components/composites/date-picker/DateTimeField.tsx +162 -0
  219. package/src/components/composites/date-picker/TimeField.tsx +78 -0
  220. package/src/components/composites/date-picker/calendar-panel.tsx +216 -0
  221. package/src/components/composites/date-picker/date-picker.css +595 -0
  222. package/src/components/composites/date-picker/date-utils.ts +56 -0
  223. package/src/components/composites/date-picker/field-shell.tsx +85 -0
  224. package/src/components/composites/date-picker/range-panel.tsx +363 -0
  225. package/src/components/composites/date-picker/time-core.tsx +290 -0
  226. package/src/components/composites/date-picker/use-day-focus.ts +35 -0
  227. package/src/components/composites/dialog/Dialog.tsx +198 -0
  228. package/src/components/composites/dialog/dialog.css +180 -0
  229. package/src/components/composites/pagination/Pagination.tsx +131 -0
  230. package/src/components/composites/pagination/pagination.css +43 -0
  231. package/src/components/composites/popover/Popover.tsx +141 -0
  232. package/src/components/composites/popover/popover.css +69 -0
  233. package/src/components/composites/select/MultiSelect.tsx +133 -0
  234. package/src/components/composites/select/Select.tsx +122 -0
  235. package/src/components/composites/select/core/index.ts +4 -0
  236. package/src/components/composites/select/core/menu.tsx +100 -0
  237. package/src/components/composites/select/core/panel.tsx +154 -0
  238. package/src/components/composites/select/core/trigger.tsx +74 -0
  239. package/src/components/composites/select/core/types.ts +39 -0
  240. package/src/components/composites/select/core/use-listbox.ts +182 -0
  241. package/src/components/composites/select/select.css +416 -0
  242. package/src/components/composites/sheet/Sheet.tsx +135 -0
  243. package/src/components/composites/sheet/sheet.css +26 -0
  244. package/src/components/composites/sheet/use-sheet-drag.ts +126 -0
  245. package/src/components/composites/table/Table.tsx +377 -0
  246. package/src/components/composites/table/table.css +348 -0
  247. package/src/components/composites/tabs/Tabs.tsx +278 -0
  248. package/src/components/composites/tabs/tabs.css +208 -0
  249. package/src/components/composites/toast/Toast.tsx +380 -0
  250. package/src/components/composites/toast/toast-store.ts +295 -0
  251. package/src/components/composites/toast/toast.css +257 -0
  252. package/src/components/composites/tooltip/Tooltip.tsx +110 -0
  253. package/src/components/composites/tooltip/tooltip-host.tsx +184 -0
  254. package/src/components/composites/tooltip/tooltip-store.ts +81 -0
  255. package/src/components/composites/tooltip/tooltip.css +86 -0
  256. package/src/components/dev/MotionDevtools.tsx +200 -0
  257. package/src/components/dev/motion-devtools.css +265 -0
  258. package/src/components/dev/slowmo-engine.ts +230 -0
  259. package/src/components/internal/glass/glass.css +100 -0
  260. package/src/components/internal/glass/glint.ts +9 -0
  261. package/src/components/internal/hooks/use-controllable.ts +21 -0
  262. package/src/components/internal/hooks/use-scroll-edges.ts +46 -0
  263. package/src/components/internal/icon/Icon.tsx +66 -0
  264. package/src/components/internal/icon/IconSlot.tsx +10 -0
  265. package/src/components/internal/icon/icon.css +33 -0
  266. package/src/components/internal/overlay/focus.ts +68 -0
  267. package/src/components/internal/overlay/layer.tsx +149 -0
  268. package/src/components/internal/overlay/modal.tsx +141 -0
  269. package/src/components/internal/overlay/overlay.css +23 -0
  270. package/src/components/internal/overlay/position.ts +103 -0
  271. package/src/components/internal/utils/cx.ts +3 -0
  272. package/src/components/internal/utils/token-px.ts +8 -0
  273. package/src/components/primitives/avatar/Avatar.tsx +120 -0
  274. package/src/components/primitives/avatar/AvatarGroup.tsx +56 -0
  275. package/src/components/primitives/avatar/avatar.css +200 -0
  276. package/src/components/primitives/badge/Badge.tsx +75 -0
  277. package/src/components/primitives/badge/CountBadge.tsx +20 -0
  278. package/src/components/primitives/badge/StatusBadge.tsx +111 -0
  279. package/src/components/primitives/badge/badge.css +182 -0
  280. package/src/components/primitives/badge/odometer.css +62 -0
  281. package/src/components/primitives/badge/odometer.tsx +36 -0
  282. package/src/components/primitives/button/Button.tsx +102 -0
  283. package/src/components/primitives/button/button.css +269 -0
  284. package/src/components/primitives/checkbox/Checkbox.tsx +76 -0
  285. package/src/components/primitives/checkbox/check-glyph.tsx +28 -0
  286. package/src/components/primitives/checkbox/checkbox.css +177 -0
  287. package/src/components/primitives/collapse/Collapse.tsx +86 -0
  288. package/src/components/primitives/collapse/collapse.css +88 -0
  289. package/src/components/primitives/collapse/collapse.typecheck.tsx +29 -0
  290. package/src/components/primitives/input/NumberField.tsx +139 -0
  291. package/src/components/primitives/input/OtpField.tsx +134 -0
  292. package/src/components/primitives/input/TextField.tsx +110 -0
  293. package/src/components/primitives/input/field-chrome.tsx +83 -0
  294. package/src/components/primitives/input/input.css +333 -0
  295. package/src/components/primitives/radio-group/RadioGroup.tsx +183 -0
  296. package/src/components/primitives/radio-group/radio-group.css +300 -0
  297. package/src/components/primitives/tag/Tag.tsx +143 -0
  298. package/src/components/primitives/tag/ToggleTag.tsx +97 -0
  299. package/src/components/primitives/tag/tag.css +248 -0
  300. package/src/components/primitives/textarea/Textarea.tsx +190 -0
  301. package/src/components/primitives/textarea/textarea.css +219 -0
  302. package/src/components/primitives/toggle/Toggle.tsx +105 -0
  303. package/src/components/primitives/toggle/toggle.css +152 -0
  304. package/src/css.d.ts +1 -0
  305. package/src/dom-props.ts +1 -0
  306. package/src/engine/animate.ts +141 -0
  307. package/src/engine/drag.ts +53 -0
  308. package/src/engine/ease.ts +155 -0
  309. package/src/engine/flip.ts +59 -0
  310. package/src/engine/index.ts +13 -0
  311. package/src/mcp/server.ts +420 -0
  312. package/src/motion/element.tsx +53 -0
  313. package/src/motion/flip.tsx +87 -0
  314. package/src/motion/glide.tsx +47 -0
  315. package/src/motion/motion-timing.ts +33 -0
  316. package/src/motion/presence-context.ts +18 -0
  317. package/src/motion/presence.tsx +91 -0
  318. package/src/motion/timing.ts +49 -0
  319. package/src/motion/use-motion.ts +64 -0
  320. package/src/styles.css +26 -0
  321. package/src/tokens/avatar.css +20 -0
  322. package/src/tokens/color.css +58 -0
  323. package/src/tokens/elevation.css +22 -0
  324. package/src/tokens/fonts.css +3 -0
  325. package/src/tokens/glass.css +20 -0
  326. package/src/tokens/icons.css +7 -0
  327. package/src/tokens/layers.css +6 -0
  328. package/src/tokens/motion-scale.ts +17 -0
  329. package/src/tokens/motion-tokens.ts +92 -0
  330. package/src/tokens/motion.css +49 -0
  331. package/src/tokens/radius.css +11 -0
  332. package/src/tokens/semantic.css +65 -0
  333. package/src/tokens/spacing.css +26 -0
  334. package/src/tokens/typography.css +52 -0
@@ -0,0 +1,265 @@
1
+ /* MotionDevtools - a floating dev overlay, styled on-brand with the design system's own
2
+ surface tokens so the <Button>/<Checkbox>/<Collapse> it dogfoods render natively.
3
+
4
+ Collapsed it's a small pill (gauge + current factor); opening morphs it into the full panel.
5
+ The width eases (CSS transition) in step with the <Collapse>-driven body height, so it grows
6
+ as one piece and never teleports. Everything that would change size on open lives INSIDE the
7
+ Collapse, so nothing clips while the pill is still narrow.
8
+
9
+ The one non-negotiable trick: it RESETS --duration-* to their canonical values on its own
10
+ root. The engine scales those tokens globally to slow the app; without this local reset the
11
+ panel's own controls - and this very expand/collapse - would slow down with everything else.
12
+ A motion debugger must stay crisp while the app runs in slow-mo. Token fallbacks keep it
13
+ legible if dropped in without the base layer. */
14
+
15
+ .mdt {
16
+ /* immune to the global slow-mo the engine applies to --duration-* */
17
+ --duration-fast: 140ms;
18
+ --duration-base: 200ms;
19
+ --duration-slow: 300ms;
20
+ --duration-slower: 450ms;
21
+ --duration-slowest: 900ms;
22
+ --duration-spin: 600ms;
23
+ --duration-pulse: 1600ms;
24
+
25
+ --mdt-offset: 16px;
26
+ --mdt-surface: color-mix(in oklab, var(--bg-surface, #fff) 88%, transparent);
27
+
28
+ position: fixed;
29
+ z-index: 2147483000; /* above every app layer, including tooltips */
30
+ display: flex;
31
+ color: var(--text-body, oklch(0.35 0.01 264));
32
+ font-family: var(--font-sans, ui-sans-serif, system-ui, sans-serif);
33
+ -webkit-font-smoothing: antialiased;
34
+ }
35
+
36
+ .mdt--top-left {
37
+ top: var(--mdt-offset);
38
+ left: var(--mdt-offset);
39
+ }
40
+ .mdt--top-right {
41
+ top: var(--mdt-offset);
42
+ right: var(--mdt-offset);
43
+ }
44
+ .mdt--bottom-left {
45
+ bottom: var(--mdt-offset);
46
+ left: var(--mdt-offset);
47
+ }
48
+ .mdt--bottom-right {
49
+ bottom: var(--mdt-offset);
50
+ right: var(--mdt-offset);
51
+ }
52
+
53
+ /* ---------- card (pill when collapsed, panel when open) ---------- */
54
+
55
+ .mdt__panel {
56
+ box-sizing: border-box;
57
+ /* Compact pill when collapsed; eases to the full panel width in step with the <Collapse>
58
+ body height below, so the whole thing morphs open and never teleports. */
59
+ width: 128px;
60
+ padding: 5px;
61
+ border: 1px solid var(--border-subtle, oklch(0.92 0.004 264));
62
+ border-radius: var(--radius-xl, 0.75rem);
63
+ background: var(--mdt-surface);
64
+ backdrop-filter: blur(16px) saturate(1.6);
65
+ -webkit-backdrop-filter: blur(16px) saturate(1.6);
66
+ box-shadow: var(--shadow-lg, 0 12px 28px oklch(0 0 0 / 0.1));
67
+ animation: mdt-in var(--duration-base) var(--ease-entrance, ease);
68
+ transition:
69
+ width var(--duration-slow) var(--ease-entrance, ease),
70
+ box-shadow var(--duration-slow) var(--ease-entrance, ease);
71
+ }
72
+ .mdt__panel.is-open {
73
+ width: 232px;
74
+ box-shadow: var(--shadow-xl, 0 24px 48px oklch(0 0 0 / 0.16));
75
+ }
76
+
77
+ @keyframes mdt-in {
78
+ from {
79
+ opacity: 0;
80
+ transform: translateY(6px) scale(0.985);
81
+ }
82
+ }
83
+
84
+ /* ---------- pill / header (the toggle) ---------- */
85
+
86
+ .mdt__head {
87
+ appearance: none;
88
+ width: 100%;
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 0.5rem;
92
+ padding: 0.3125rem 0.4375rem;
93
+ border: 0;
94
+ border-radius: var(--radius-md, 0.375rem);
95
+ background: transparent;
96
+ color: inherit;
97
+ font: inherit;
98
+ text-align: left;
99
+ cursor: pointer;
100
+ transition: background var(--duration-fast) var(--ease-standard, ease);
101
+ }
102
+ .mdt__head:hover {
103
+ background: var(--neutral-wash, oklch(0.5 0.01 264 / 0.07));
104
+ }
105
+ .mdt__head:focus-visible {
106
+ outline: none;
107
+ box-shadow: var(--ring-accent, 0 0 0 3px oklch(0.7 0.12 185 / 0.35));
108
+ }
109
+
110
+ .mdt__glyph {
111
+ color: var(--text-subtle, oklch(0.6 0.01 264));
112
+ flex: none;
113
+ display: block;
114
+ }
115
+ .mdt--active .mdt__glyph {
116
+ color: var(--accent, oklch(0.62 0.12 185));
117
+ }
118
+ .mdt--paused .mdt__glyph {
119
+ color: var(--warning, oklch(0.72 0.13 75));
120
+ }
121
+ .mdt__spacer {
122
+ flex: 1;
123
+ }
124
+ .mdt__badge {
125
+ font-size: 0.75rem;
126
+ font-weight: 650;
127
+ font-variant-numeric: tabular-nums;
128
+ line-height: 1;
129
+ padding: 0.1875rem 0.4375rem;
130
+ border-radius: var(--radius-full, 999px);
131
+ color: var(--text-muted, oklch(0.55 0.01 264));
132
+ background: var(--neutral-wash, oklch(0.5 0.01 264 / 0.08));
133
+ white-space: nowrap;
134
+ }
135
+ .mdt--active .mdt__badge {
136
+ color: var(--text-on-accent, #fff);
137
+ background: var(--accent, oklch(0.62 0.12 185));
138
+ }
139
+ .mdt--paused .mdt__badge {
140
+ color: var(--text-on-accent, #fff);
141
+ background: var(--warning, oklch(0.72 0.13 75));
142
+ }
143
+ .mdt__chevron {
144
+ color: var(--text-subtle, oklch(0.6 0.01 264));
145
+ flex: none;
146
+ transition: transform var(--duration-base) var(--ease-standard, ease);
147
+ }
148
+ .mdt__chevron--open {
149
+ transform: rotate(180deg);
150
+ }
151
+
152
+ /* ---------- body (inside <Collapse>) ---------- */
153
+
154
+ .mdt__body {
155
+ box-sizing: border-box;
156
+ /* Hold a stable layout width so controls don't reflow as the card animates open; the
157
+ collapsed pill is narrower, so this overflows and is clipped by <Collapse>'s overflow. */
158
+ width: 100%;
159
+ min-width: 208px;
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 0.8125rem;
163
+ padding: 0.625rem 0.375rem 0.375rem;
164
+ }
165
+
166
+ .mdt__title {
167
+ font-size: 0.6875rem;
168
+ font-weight: 650;
169
+ letter-spacing: 0.06em;
170
+ text-transform: uppercase;
171
+ color: var(--text-subtle, oklch(0.6 0.01 264));
172
+ }
173
+ .mdt__readout {
174
+ display: flex;
175
+ align-items: baseline;
176
+ gap: 0.375rem;
177
+ margin-top: -0.375rem;
178
+ }
179
+ .mdt__value {
180
+ font-size: 1.5rem;
181
+ font-weight: 700;
182
+ font-variant-numeric: tabular-nums;
183
+ line-height: 1;
184
+ color: var(--text-strong, oklch(0.25 0.01 264));
185
+ }
186
+ .mdt__unit {
187
+ font-size: 0.75rem;
188
+ color: var(--text-muted, oklch(0.55 0.01 264));
189
+ font-weight: 500;
190
+ }
191
+
192
+ .mdt__slider {
193
+ -webkit-appearance: none;
194
+ appearance: none;
195
+ width: 100%;
196
+ height: 4px;
197
+ border-radius: 999px;
198
+ background: linear-gradient(
199
+ to right,
200
+ var(--accent, oklch(0.62 0.12 185)) 0%,
201
+ var(--accent, oklch(0.62 0.12 185)) var(--mdt-fill, 0%),
202
+ var(--bg-muted, oklch(0.93 0.004 264)) var(--mdt-fill, 0%)
203
+ );
204
+ cursor: pointer;
205
+ margin: 0;
206
+ }
207
+ .mdt__slider::-webkit-slider-thumb {
208
+ -webkit-appearance: none;
209
+ appearance: none;
210
+ width: 16px;
211
+ height: 16px;
212
+ border-radius: 50%;
213
+ background: var(--bg-surface, #fff);
214
+ border: 2px solid var(--accent, oklch(0.62 0.12 185));
215
+ box-shadow: var(--shadow-sm, 0 1px 2px oklch(0 0 0 / 0.15));
216
+ }
217
+ .mdt__slider::-moz-range-thumb {
218
+ width: 16px;
219
+ height: 16px;
220
+ border-radius: 50%;
221
+ background: var(--bg-surface, #fff);
222
+ border: 2px solid var(--accent, oklch(0.62 0.12 185));
223
+ box-shadow: var(--shadow-sm, 0 1px 2px oklch(0 0 0 / 0.15));
224
+ }
225
+ .mdt__slider:focus-visible {
226
+ outline: none;
227
+ }
228
+ .mdt__slider:focus-visible::-webkit-slider-thumb {
229
+ box-shadow: var(--ring-accent, 0 0 0 3px oklch(0.7 0.12 185 / 0.35));
230
+ }
231
+ .mdt__slider:focus-visible::-moz-range-thumb {
232
+ box-shadow: var(--ring-accent, 0 0 0 3px oklch(0.7 0.12 185 / 0.35));
233
+ }
234
+
235
+ /* preset chip grid + action row - children are DS <Button>s */
236
+ .mdt__chips {
237
+ display: grid;
238
+ grid-template-columns: repeat(5, 1fr);
239
+ gap: 0.25rem;
240
+ }
241
+ .mdt__chips .btn {
242
+ padding-inline: 0;
243
+ min-width: 0;
244
+ }
245
+ .mdt__row {
246
+ display: flex;
247
+ gap: 0.375rem;
248
+ }
249
+ .mdt__row .btn {
250
+ flex: 1;
251
+ }
252
+
253
+ .mdt__opt {
254
+ border-top: 1px solid var(--border-subtle, oklch(0.92 0.004 264));
255
+ padding-top: 0.75rem;
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: 0.5rem;
259
+ }
260
+ .mdt__hint {
261
+ font-size: 0.6875rem;
262
+ line-height: 1.45;
263
+ color: var(--text-subtle, oklch(0.6 0.01 264));
264
+ margin: 0;
265
+ }
@@ -0,0 +1,230 @@
1
+ import { clock } from '../../engine';
2
+
3
+ export interface SlowmoState {
4
+ /** 1 = real time. >1 = that many times slower (4 = quarter speed). */
5
+ factor: number;
6
+ /** Freeze motion in place - useful for inspecting a transition mid-flight. */
7
+ paused: boolean;
8
+ }
9
+
10
+ export interface SlowmoOptions {
11
+ /** Also scale setTimeout/setInterval so duration-coupled JS cleanups don't truncate
12
+ * slowed CSS. Turn off if stretched app timers get in your way. @default true */
13
+ scaleTimers: boolean;
14
+ }
15
+
16
+ const CANONICAL: Record<string, number> = {
17
+ '--duration-fast': 140,
18
+ '--duration-base': 200,
19
+ '--duration-slow': 300,
20
+ '--duration-slower': 450,
21
+ '--duration-slowest': 900,
22
+ '--duration-spin': 600,
23
+ '--duration-pulse': 1600,
24
+ };
25
+ const DURATION_TOKENS = Object.keys(CANONICAL);
26
+ const COLLAPSED_MS = 4;
27
+ const FROZEN_MS = 600_000;
28
+ const MAX_TIMER_DELAY = 2 ** 30;
29
+
30
+ const clamp = (min: number, max: number, v: number) => (v < min ? min : v > max ? max : v);
31
+ const hasDOM = typeof document !== 'undefined' && typeof window !== 'undefined';
32
+
33
+ const state: SlowmoState = { factor: 1, paused: false };
34
+ const options: SlowmoOptions = { scaleTimers: true };
35
+ const listeners = new Set<(s: SlowmoState) => void>();
36
+
37
+ let active = false;
38
+ let baseMs: Record<string, number> = {};
39
+
40
+ type SetTimer = (handler: TimerHandler, timeout?: number, ...args: unknown[]) => number;
41
+ let origSetTimeout: SetTimer | null = null;
42
+ let origSetInterval: SetTimer | null = null;
43
+
44
+ type AnimateFn = (this: Element, ...args: unknown[]) => Animation;
45
+ let origAnimate: AnimateFn | null = null;
46
+ const trackedRates = new Map<Animation, number>();
47
+
48
+ function readBaseMs(token: string): number {
49
+ const raw = getComputedStyle(document.documentElement).getPropertyValue(token).trim();
50
+ const n = raw.endsWith('ms') ? parseFloat(raw) : raw.endsWith('s') ? parseFloat(raw) * 1000 : parseFloat(raw);
51
+ if (isNaN(n) || n <= COLLAPSED_MS) return CANONICAL[token];
52
+ return n;
53
+ }
54
+
55
+ function applyCssTokens() {
56
+ const root = document.documentElement.style;
57
+ for (const token of DURATION_TOKENS) {
58
+ const ms = state.paused ? FROZEN_MS : baseMs[token] * state.factor;
59
+ root.setProperty(token, `${ms}ms`);
60
+ }
61
+ }
62
+
63
+ function clearCssTokens() {
64
+ const root = document.documentElement.style;
65
+ for (const token of DURATION_TOKENS) root.removeProperty(token);
66
+ }
67
+
68
+ function scaleDelay(delay?: number): number | undefined {
69
+ if (typeof delay !== 'number' || !isFinite(delay)) return delay;
70
+ return Math.min(delay * state.factor, MAX_TIMER_DELAY);
71
+ }
72
+
73
+ function patchTimers() {
74
+ if (origSetTimeout) return;
75
+ origSetTimeout = window.setTimeout as unknown as SetTimer;
76
+ origSetInterval = window.setInterval as unknown as SetTimer;
77
+ const nativeTimeout = origSetTimeout.bind(window);
78
+ const nativeInterval = origSetInterval.bind(window);
79
+ window.setTimeout = ((h: TimerHandler, t?: number, ...a: unknown[]) =>
80
+ nativeTimeout(h, scaleDelay(t), ...a)) as typeof window.setTimeout;
81
+ window.setInterval = ((h: TimerHandler, t?: number, ...a: unknown[]) =>
82
+ nativeInterval(h, scaleDelay(t), ...a)) as typeof window.setInterval;
83
+ }
84
+
85
+ function restoreTimers() {
86
+ if (origSetTimeout) window.setTimeout = origSetTimeout as typeof window.setTimeout;
87
+ if (origSetInterval) window.setInterval = origSetInterval as typeof window.setInterval;
88
+ origSetTimeout = origSetInterval = null;
89
+ }
90
+
91
+ function liveRate(): number {
92
+ return state.paused ? 0 : 1 / state.factor;
93
+ }
94
+
95
+ function track(anim: Animation, original: number) {
96
+ if (trackedRates.has(anim)) return;
97
+ trackedRates.set(anim, original);
98
+ const forget = () => trackedRates.delete(anim);
99
+ anim.finished.then(forget).catch(forget);
100
+ }
101
+
102
+ function patchAnimate() {
103
+ if (origAnimate) return;
104
+ origAnimate = Element.prototype.animate as unknown as AnimateFn;
105
+ const original = origAnimate;
106
+ Element.prototype.animate = function (this: Element, ...args: unknown[]): Animation {
107
+ const anim = original.apply(this, args);
108
+ try {
109
+ track(anim, anim.playbackRate);
110
+ anim.playbackRate = liveRate();
111
+ } catch {}
112
+ return anim;
113
+ } as typeof Element.prototype.animate;
114
+ }
115
+
116
+ function restoreAnimate() {
117
+ if (origAnimate) Element.prototype.animate = origAnimate as typeof Element.prototype.animate;
118
+ origAnimate = null;
119
+ }
120
+
121
+ function sweepExisting() {
122
+ const rate = liveRate();
123
+ for (const anim of document.getAnimations()) {
124
+ try {
125
+ track(anim, anim.playbackRate);
126
+ anim.playbackRate = rate;
127
+ } catch {}
128
+ }
129
+ }
130
+
131
+ function applyTrackedRates() {
132
+ const rate = liveRate();
133
+ for (const anim of trackedRates.keys()) {
134
+ try {
135
+ anim.playbackRate = rate;
136
+ } catch {}
137
+ }
138
+ }
139
+
140
+ function restoreTrackedRates() {
141
+ for (const [anim, original] of trackedRates) {
142
+ try {
143
+ anim.playbackRate = original;
144
+ } catch {}
145
+ }
146
+ trackedRates.clear();
147
+ }
148
+
149
+ function applyEngineClock() {
150
+ clock.scale = state.paused ? Infinity : state.factor;
151
+ }
152
+
153
+ function install() {
154
+ active = true;
155
+ baseMs = {};
156
+ for (const token of DURATION_TOKENS) baseMs[token] = readBaseMs(token);
157
+ applyCssTokens();
158
+ applyEngineClock();
159
+ if (options.scaleTimers) patchTimers();
160
+ patchAnimate();
161
+ sweepExisting();
162
+ }
163
+
164
+ function reconfigure() {
165
+ applyCssTokens();
166
+ applyEngineClock();
167
+ applyTrackedRates();
168
+ }
169
+
170
+ function uninstall() {
171
+ active = false;
172
+ clearCssTokens();
173
+ clock.scale = 1;
174
+ restoreTimers();
175
+ restoreAnimate();
176
+ restoreTrackedRates();
177
+ }
178
+
179
+ function notify() {
180
+ const snapshot = { ...state };
181
+ for (const cb of listeners) cb(snapshot);
182
+ }
183
+
184
+ export function shouldBeActive(factor: number, paused: boolean): boolean {
185
+ return factor > 1 || paused;
186
+ }
187
+
188
+ /** Imperative control surface - drive slow-mo from a console, a hotkey, or the panel.
189
+ * Safe to call during SSR (no-ops the DOM work, still tracks state for hydration). */
190
+ export const motionSlowmo = {
191
+ /** Current state (a copy). */
192
+ get(): SlowmoState {
193
+ return { ...state };
194
+ },
195
+
196
+ /** Merge a partial state. factor is clamped to [1, 64]. */
197
+ set(patch: Partial<SlowmoState>): void {
198
+ if (typeof patch.factor === 'number') state.factor = clamp(1, 64, patch.factor);
199
+ if (typeof patch.paused === 'boolean') state.paused = patch.paused;
200
+
201
+ if (hasDOM) {
202
+ const wantActive = shouldBeActive(state.factor, state.paused);
203
+ if (!active && wantActive) install();
204
+ else if (active && !wantActive) uninstall();
205
+ else if (active) reconfigure();
206
+ }
207
+ notify();
208
+ },
209
+
210
+ /** Tune engine behaviour (e.g. stop scaling timers). Applies immediately if active. */
211
+ configure(patch: Partial<SlowmoOptions>): void {
212
+ if (typeof patch.scaleTimers === 'boolean' && patch.scaleTimers !== options.scaleTimers) {
213
+ options.scaleTimers = patch.scaleTimers;
214
+ if (active) options.scaleTimers ? patchTimers() : restoreTimers();
215
+ }
216
+ },
217
+
218
+ /** Convenience: back to real time and unpaused. */
219
+ reset(): void {
220
+ this.set({ factor: 1, paused: false });
221
+ },
222
+
223
+ /** Subscribe to state changes; returns an unsubscribe fn. */
224
+ subscribe(cb: (s: SlowmoState) => void): () => void {
225
+ listeners.add(cb);
226
+ return () => listeners.delete(cb);
227
+ },
228
+ };
229
+
230
+ export type MotionSlowmo = typeof motionSlowmo;
@@ -0,0 +1,100 @@
1
+ /* Glass surface - translucent tint + backdrop blur + a sheen layer; consumer sets --glass-tint and --glass-fg. */
2
+
3
+ .glass {
4
+ --glass-fg: var(--text-body);
5
+ --glass-tint: var(--glass-tint-neutral);
6
+
7
+ position: relative;
8
+ isolation: isolate; /* local stacking for the sheen */
9
+ color: var(--glass-fg);
10
+
11
+ background: var(--glass-tint);
12
+ -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
13
+ backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
14
+ box-shadow:
15
+ var(--glass-highlight),
16
+ inset 0 0 0 var(--border-hairline) color-mix(in oklab, var(--glass-fg) 22%, transparent),
17
+ var(--glass-shadow);
18
+
19
+ transition:
20
+ transform var(--duration-base) var(--ease-standard),
21
+ box-shadow var(--duration-base) var(--ease-standard),
22
+ background-color var(--duration-base) var(--ease-standard);
23
+ }
24
+
25
+ /* Sheen on its own layer so hover animates opacity/transform - gradients can't transition directly. */
26
+ .glass::before {
27
+ content: '';
28
+ position: absolute;
29
+ inset: 0;
30
+ z-index: 0;
31
+ border-radius: inherit;
32
+ background: var(--glass-sheen);
33
+ opacity: var(--glass-sheen-rest);
34
+ pointer-events: none;
35
+ transition:
36
+ opacity var(--duration-base) var(--ease-standard),
37
+ transform var(--duration-slow) var(--ease-entrance);
38
+ }
39
+ .glass > * {
40
+ position: relative;
41
+ z-index: 1;
42
+ } /* content rides above the sheen */
43
+
44
+ .glass--strong {
45
+ --glass-blur: var(--glass-blur-strong);
46
+ }
47
+
48
+ .glass--interactive:hover {
49
+ transform: translateY(calc(var(--space-px) * -1));
50
+ box-shadow:
51
+ var(--glass-highlight),
52
+ inset 0 0 0 var(--border-hairline) color-mix(in oklab, var(--glass-fg) 26%, transparent),
53
+ var(--glass-shadow-hover);
54
+ }
55
+ .glass--interactive:hover::before {
56
+ opacity: var(--glass-sheen-hover);
57
+ transform: translateY(-8%);
58
+ }
59
+
60
+ /* Glint - one-shot light sweep; add .glass-glint to fire, remove after it ends (sits above content, z-index 2). */
61
+ .glass-glint::after {
62
+ content: '';
63
+ position: absolute;
64
+ inset: 0;
65
+ z-index: 2;
66
+ border-radius: inherit;
67
+ pointer-events: none;
68
+ opacity: 0; /* resting state is invisible - no mid-sweep flash */
69
+ background: linear-gradient(118deg, transparent 36%, rgb(255 255 255 / 0.7) 50%, transparent 64%);
70
+ animation: glass-glint var(--duration-slow) var(--ease-standard) 1 forwards;
71
+ }
72
+ @keyframes glass-glint {
73
+ 0% {
74
+ opacity: 0;
75
+ transform: translateX(-28%);
76
+ }
77
+ 40% {
78
+ opacity: 1;
79
+ }
80
+ 100% {
81
+ opacity: 0;
82
+ transform: translateX(28%);
83
+ }
84
+ }
85
+
86
+ @media (prefers-reduced-motion: reduce) {
87
+ .glass,
88
+ .glass::before {
89
+ transition: none;
90
+ }
91
+ .glass--interactive:hover {
92
+ transform: none;
93
+ }
94
+ .glass--interactive:hover::before {
95
+ transform: none;
96
+ }
97
+ .glass-glint::after {
98
+ animation: none;
99
+ }
100
+ }
@@ -0,0 +1,9 @@
1
+ import { UIMotion } from '../../../tokens/motion-tokens';
2
+
3
+ export function fireGlint(el: HTMLElement): () => void {
4
+ el.classList.remove('glass-glint');
5
+ void el.offsetWidth;
6
+ el.classList.add('glass-glint');
7
+ const id = setTimeout(() => el.classList.remove('glass-glint'), UIMotion.dur.slow * 1000 + 80);
8
+ return () => clearTimeout(id);
9
+ }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useState } from 'react';
4
+
5
+ export function useControllable<T, Meta = unknown>(
6
+ value: T | undefined,
7
+ defaultValue: T,
8
+ onChange?: (next: T, meta?: Meta) => void,
9
+ ): [T, (next: T, meta?: Meta) => void] {
10
+ const controlled = value !== undefined;
11
+ const [inner, setInner] = useState<T>(defaultValue);
12
+ const val = controlled ? (value as T) : inner;
13
+ const commit = useCallback(
14
+ (next: T, meta?: Meta) => {
15
+ if (!controlled) setInner(next);
16
+ if (onChange) onChange(next, meta);
17
+ },
18
+ [controlled, onChange],
19
+ );
20
+ return [val, commit];
21
+ }
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+
3
+ import { useCallback, useEffect, useRef, type RefObject } from 'react';
4
+
5
+ export interface ScrollEdges {
6
+ top: boolean;
7
+ bottom: boolean;
8
+ left: boolean;
9
+ right: boolean;
10
+ }
11
+
12
+ export function useScrollEdges(
13
+ ref: RefObject<HTMLElement | null>,
14
+ onChange: (edges: ScrollEdges, el: HTMLElement) => void,
15
+ { content = false }: { content?: boolean } = {},
16
+ ): () => void {
17
+ const cb = useRef(onChange);
18
+ cb.current = onChange;
19
+ const sync = useCallback(() => {
20
+ const el = ref.current;
21
+ if (!el) return;
22
+ cb.current(
23
+ {
24
+ top: el.scrollTop > 1,
25
+ bottom: el.scrollTop + el.clientHeight < el.scrollHeight - 1,
26
+ left: el.scrollLeft > 1,
27
+ right: el.scrollLeft + el.clientWidth < el.scrollWidth - 1,
28
+ },
29
+ el,
30
+ );
31
+ }, [ref]);
32
+ useEffect(() => {
33
+ const el = ref.current;
34
+ if (!el) return undefined;
35
+ sync();
36
+ el.addEventListener('scroll', sync, { passive: true });
37
+ const ro = new ResizeObserver(sync);
38
+ ro.observe(el);
39
+ if (content && el.firstElementChild) ro.observe(el.firstElementChild);
40
+ return () => {
41
+ el.removeEventListener('scroll', sync);
42
+ ro.disconnect();
43
+ };
44
+ }, [sync, content]);
45
+ return sync;
46
+ }