@yewdiel21/motion-kit 0.9.0 → 0.10.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 (399) hide show
  1. package/NOTICE +3 -35
  2. package/README.md +93 -81
  3. package/dist/auto/index.d.ts +22 -3
  4. package/dist/auto/index.js +43 -5
  5. package/dist/auto/index.js.map +1 -1
  6. package/dist/{chunk-KLY5BRRI.js → chunk-34EZ7DF4.js} +1 -1
  7. package/dist/chunk-34EZ7DF4.js.map +1 -0
  8. package/dist/{chunk-RFDIZYFX.js → chunk-DOOMULVV.js} +20 -20
  9. package/dist/chunk-DOOMULVV.js.map +1 -0
  10. package/dist/{chunk-ABNRDDO7.js → chunk-I3DXF2KQ.js} +2 -2
  11. package/dist/{chunk-Q4LVFNLN.js → chunk-IKQ5FODN.js} +4 -4
  12. package/dist/chunk-IKQ5FODN.js.map +1 -0
  13. package/dist/{chunk-2BQGFOA7.js → chunk-J6OCFNGU.js} +2 -2
  14. package/dist/chunk-J6OCFNGU.js.map +1 -0
  15. package/dist/{chunk-6OJAP6ZL.js → chunk-QFBOHPWZ.js} +3 -3
  16. package/dist/chunk-QFBOHPWZ.js.map +1 -0
  17. package/dist/{chunk-J27KH2A2.js → chunk-RVSKEBIC.js} +1 -1
  18. package/dist/chunk-RVSKEBIC.js.map +1 -0
  19. package/dist/{chunk-34XYOXHG.js → chunk-TFNRIZHU.js} +1615 -12
  20. package/dist/chunk-TFNRIZHU.js.map +1 -0
  21. package/dist/{chunk-FS2HJVVY.js → chunk-XAKC2QY7.js} +1204 -5
  22. package/dist/chunk-XAKC2QY7.js.map +1 -0
  23. package/dist/css/components/calendar.css +159 -0
  24. package/dist/css/components/chat-background.css +2 -2
  25. package/dist/css/components/chat.css +1 -1
  26. package/dist/css/components/cursor.css +3 -3
  27. package/dist/css/components/dialog.css +3 -3
  28. package/dist/css/components/hover-card.css +14 -0
  29. package/dist/css/components/input-otp.css +10 -0
  30. package/dist/css/components/island.css +1 -1
  31. package/dist/css/components/menu.css +1 -1
  32. package/dist/css/components/nav.css +1 -5
  33. package/dist/css/components/pagination.css +11 -0
  34. package/dist/css/components/quantity.css +16 -0
  35. package/dist/css/components/slot-picker.css +126 -0
  36. package/dist/css/components/steps.css +2 -2
  37. package/dist/css/components/studio-dock.css +35 -0
  38. package/dist/css/components/theme-picker.css +1 -1
  39. package/dist/css/components/toast.css +1 -1
  40. package/dist/css/components/trailer.css +37 -0
  41. package/dist/css/controls.css +78 -13
  42. package/dist/css/effects.css +8 -8
  43. package/dist/css/fonts.css +1 -1
  44. package/dist/css/themes/black-green.css +1 -1
  45. package/dist/css/themes/black.css +1 -1
  46. package/dist/css/themes/blue.css +1 -1
  47. package/dist/css/themes/brown.css +1 -1
  48. package/dist/css/themes/cold-blue.css +1 -1
  49. package/dist/css/themes/funny.css +1 -1
  50. package/dist/css/themes/gray.css +1 -1
  51. package/dist/css/themes/green.css +1 -1
  52. package/dist/css/themes/modern-1.css +1 -1
  53. package/dist/css/themes/neogreen.css +1 -1
  54. package/dist/css/themes/new.css +1 -1
  55. package/dist/css/themes/new2.css +1 -1
  56. package/dist/css/themes/oled.css +1 -1
  57. package/dist/css/themes/orange.css +1 -1
  58. package/dist/css/themes/pastel-pink.css +1 -1
  59. package/dist/css/themes/pink.css +1 -1
  60. package/dist/css/themes/purple.css +1 -1
  61. package/dist/css/themes/red.css +1 -1
  62. package/dist/css/themes/super-blue.css +1 -1
  63. package/dist/css/themes.css +19 -19
  64. package/dist/css/tokens.css +9 -9
  65. package/dist/elements/index.d.ts +75 -7
  66. package/dist/elements/index.js +172 -6
  67. package/dist/elements/index.js.map +1 -1
  68. package/dist/{engine-CUr9QijM.d.ts → engine-GDGwQnJE.d.ts} +2 -2
  69. package/dist/gl/index.js +3 -3
  70. package/dist/index.css +1076 -63
  71. package/dist/index.d.ts +17 -17
  72. package/dist/index.js +49 -7
  73. package/dist/index.js.map +1 -1
  74. package/dist/react/index.d.ts +139 -40
  75. package/dist/react/index.js +355 -8
  76. package/dist/react/index.js.map +1 -1
  77. package/dist/{reduced-BM4Ls0g5.d.ts → reduced-BmBzKH94.d.ts} +215 -18
  78. package/dist/{reveal-WFUWDrei.d.ts → reveal-jsX-uwUz.d.ts} +233 -24
  79. package/dist/{store-Bqy_obnD.d.ts → store-DYb3tTsZ.d.ts} +5 -5
  80. package/dist/theme/generate.d.ts +1 -1
  81. package/dist/theme/generate.js +2 -2
  82. package/dist/theme/generate.js.map +1 -1
  83. package/dist/theme/index.d.ts +2 -2
  84. package/dist/theme/index.js +2 -2
  85. package/dist/{theme-BFMoCzi6.d.ts → theme-Bjf9qIIy.d.ts} +1 -1
  86. package/package.json +2 -2
  87. package/registry.json +230 -156
  88. package/skills/motion-kit/SKILL.md +209 -189
  89. package/skills/motion-kit/reference/components.md +5 -5
  90. package/skills/motion-kit/reference/design.md +14 -14
  91. package/skills/motion-kit/reference/recipes.md +48 -49
  92. package/src/auto/auto-init.ts +38 -0
  93. package/src/components/calendar/README.md +82 -0
  94. package/src/components/calendar/calendar.css +158 -0
  95. package/src/components/calendar/element.ts +27 -0
  96. package/src/components/calendar/engine.ts +412 -0
  97. package/src/components/calendar/react.tsx +69 -0
  98. package/src/components/chat/README.md +1 -3
  99. package/src/components/chat/chat.css +1 -1
  100. package/src/components/chat/engine.ts +1 -1
  101. package/src/components/chat/react.tsx +1 -1
  102. package/src/components/chat-background/README.md +1 -3
  103. package/src/components/chat-background/audio.ts +1 -1
  104. package/src/components/chat-background/chat-background.css +2 -2
  105. package/src/components/chat-background/engine.ts +1 -1
  106. package/src/components/chat-background/react.tsx +1 -1
  107. package/src/components/cursor/README.md +3 -5
  108. package/src/components/cursor/cursor.css +3 -3
  109. package/src/components/cursor/element.ts +2 -2
  110. package/src/components/cursor/engine.ts +5 -5
  111. package/src/components/cursor/react.tsx +1 -1
  112. package/src/components/dialog/README.md +4 -8
  113. package/src/components/dialog/dialog.css +3 -3
  114. package/src/components/dialog/engine.ts +2 -2
  115. package/src/components/dialog/react.tsx +1 -1
  116. package/src/components/hover-card/README.md +16 -0
  117. package/src/components/hover-card/element.ts +8 -0
  118. package/src/components/hover-card/engine.ts +69 -0
  119. package/src/components/hover-card/hover-card.css +13 -0
  120. package/src/components/hover-card/react.tsx +19 -0
  121. package/src/components/input-otp/README.md +18 -0
  122. package/src/components/input-otp/element.ts +14 -0
  123. package/src/components/input-otp/engine.ts +106 -0
  124. package/src/components/input-otp/input-otp.css +9 -0
  125. package/src/components/input-otp/react.tsx +21 -0
  126. package/src/components/island/README.md +7 -7
  127. package/src/components/island/engine.ts +3 -3
  128. package/src/components/island/island.css +1 -1
  129. package/src/components/island/react.tsx +3 -3
  130. package/src/components/menu/README.md +2 -4
  131. package/src/components/menu/engine.ts +1 -1
  132. package/src/components/menu/menu.css +1 -1
  133. package/src/components/menu/react.tsx +1 -1
  134. package/src/components/nav/README.md +12 -14
  135. package/src/components/nav/engine.ts +5 -5
  136. package/src/components/nav/nav.css +1 -5
  137. package/src/components/nav/react.tsx +3 -3
  138. package/src/components/pagination/README.md +17 -0
  139. package/src/components/pagination/element.ts +14 -0
  140. package/src/components/pagination/engine.ts +102 -0
  141. package/src/components/pagination/pagination.css +10 -0
  142. package/src/components/pagination/react.tsx +22 -0
  143. package/src/components/quantity/README.md +20 -0
  144. package/src/components/quantity/element.ts +14 -0
  145. package/src/components/quantity/engine.ts +85 -0
  146. package/src/components/quantity/quantity.css +15 -0
  147. package/src/components/quantity/react.tsx +21 -0
  148. package/src/components/slot-picker/README.md +68 -0
  149. package/src/components/slot-picker/element.ts +27 -0
  150. package/src/components/slot-picker/engine.ts +412 -0
  151. package/src/components/slot-picker/react.tsx +76 -0
  152. package/src/components/slot-picker/slot-picker.css +125 -0
  153. package/src/components/steps/README.md +1 -3
  154. package/src/components/steps/engine.ts +3 -3
  155. package/src/components/steps/react.tsx +2 -2
  156. package/src/components/steps/steps.css +2 -2
  157. package/src/components/studio-dock/README.md +44 -0
  158. package/src/components/studio-dock/element.ts +11 -0
  159. package/src/components/studio-dock/engine.ts +212 -0
  160. package/src/components/studio-dock/react.tsx +23 -0
  161. package/src/components/studio-dock/studio-dock.css +34 -0
  162. package/src/components/theme-picker/README.md +3 -5
  163. package/src/components/theme-picker/engine.ts +2 -2
  164. package/src/components/theme-picker/react.tsx +1 -1
  165. package/src/components/theme-picker/theme-picker.css +1 -1
  166. package/src/components/toast/README.md +1 -3
  167. package/src/components/toast/engine.ts +2 -2
  168. package/src/components/toast/react.tsx +1 -1
  169. package/src/components/toast/store.ts +1 -2
  170. package/src/components/toast/toast.css +1 -1
  171. package/src/components/trailer/README.md +28 -0
  172. package/src/components/trailer/element.ts +13 -0
  173. package/src/components/trailer/engine.ts +189 -0
  174. package/src/components/trailer/react.tsx +16 -0
  175. package/src/components/trailer/trailer.css +36 -0
  176. package/src/core/channels.ts +3 -3
  177. package/src/core/curves.ts +3 -3
  178. package/src/core/dates.ts +98 -0
  179. package/src/core/gsap.ts +2 -2
  180. package/src/core/loop.ts +1 -1
  181. package/src/core/math.ts +1 -1
  182. package/src/core/physics-spring.ts +2 -2
  183. package/src/core/scroll-track.ts +2 -4
  184. package/src/core/spring.ts +1 -1
  185. package/src/core/timing.ts +21 -22
  186. package/src/css/controls/README.md +24 -20
  187. package/src/css/controls/burger.css +1 -1
  188. package/src/css/controls/button.css +4 -4
  189. package/src/css/controls/checkbox.css +1 -1
  190. package/src/css/controls/chip.css +1 -1
  191. package/src/css/controls/field.css +2 -2
  192. package/src/css/controls/glass.css +4 -4
  193. package/src/css/controls/status.css +63 -0
  194. package/src/css/default-theme.css +1 -1
  195. package/src/css/effects/README.md +19 -21
  196. package/src/css/effects/anim.css +1 -1
  197. package/src/css/effects/fade.css +1 -1
  198. package/src/css/effects/float.css +1 -1
  199. package/src/css/effects/frost.css +1 -1
  200. package/src/css/effects/grain.css +1 -1
  201. package/src/css/effects/loading.css +1 -1
  202. package/src/css/effects/success.css +1 -1
  203. package/src/css/effects/text.css +1 -1
  204. package/src/css/fonts.css +1 -1
  205. package/src/css/states.css +1 -1
  206. package/src/css/themes/black-green.css +1 -1
  207. package/src/css/themes/black.css +1 -1
  208. package/src/css/themes/blue.css +1 -1
  209. package/src/css/themes/brown.css +1 -1
  210. package/src/css/themes/cold-blue.css +1 -1
  211. package/src/css/themes/funny.css +1 -1
  212. package/src/css/themes/gray.css +1 -1
  213. package/src/css/themes/green.css +1 -1
  214. package/src/css/themes/modern-1.css +1 -1
  215. package/src/css/themes/neogreen.css +1 -1
  216. package/src/css/themes/new.css +1 -1
  217. package/src/css/themes/new2.css +1 -1
  218. package/src/css/themes/oled.css +1 -1
  219. package/src/css/themes/orange.css +1 -1
  220. package/src/css/themes/pastel-pink.css +1 -1
  221. package/src/css/themes/pink.css +1 -1
  222. package/src/css/themes/purple.css +1 -1
  223. package/src/css/themes/red.css +1 -1
  224. package/src/css/themes/super-blue.css +1 -1
  225. package/src/css/tokens.css +8 -8
  226. package/src/elements/index.ts +24 -0
  227. package/src/gl/liquid-blend/README.md +3 -3
  228. package/src/gl/matcap/README.md +3 -3
  229. package/src/gl/particle-hero/README.md +1 -1
  230. package/src/gl/particle-hero/create-particle-hero.ts +3 -5
  231. package/src/gl/particle-hero/shaders.ts +1 -1
  232. package/src/gl/shader-blur/README.md +3 -3
  233. package/src/gl/shader-grain/README.md +3 -3
  234. package/src/index.ts +25 -0
  235. package/src/motion/chat.ts +2 -2
  236. package/src/motion/confetti.ts +1 -1
  237. package/src/motion/count.ts +1 -1
  238. package/src/motion/details.ts +1 -1
  239. package/src/motion/drag-dismiss.ts +1 -1
  240. package/src/motion/effects.ts +1 -1
  241. package/src/motion/entrances.ts +1 -1
  242. package/src/motion/floating.ts +1 -1
  243. package/src/motion/flow.ts +2 -2
  244. package/src/motion/indicator.ts +1 -1
  245. package/src/motion/layout.ts +1 -1
  246. package/src/motion/liquid.ts +1 -1
  247. package/src/motion/morph.ts +2 -2
  248. package/src/motion/reveal.ts +1 -1
  249. package/src/motion/sheet.ts +1 -1
  250. package/src/motion/surface.ts +1 -1
  251. package/src/react/animated-number.tsx +2 -2
  252. package/src/react/flip-group.tsx +1 -1
  253. package/src/react/frost.tsx +1 -1
  254. package/src/react/index.ts +21 -0
  255. package/src/react/slot-text.tsx +1 -1
  256. package/src/react/use-action-button.ts +6 -0
  257. package/src/react/use-card-dock.ts +1 -1
  258. package/src/react/use-cascade.ts +1 -1
  259. package/src/react/use-contrast.ts +1 -1
  260. package/src/react/use-depth-grid.ts +1 -1
  261. package/src/react/use-editorial-reveal.ts +7 -0
  262. package/src/react/use-fill-text.ts +1 -1
  263. package/src/react/use-film-hero.ts +6 -0
  264. package/src/react/use-font-cycle.ts +7 -0
  265. package/src/react/use-frame-zoom.ts +1 -1
  266. package/src/react/use-image-zoom.ts +5 -0
  267. package/src/react/use-joined-cta.ts +5 -0
  268. package/src/react/use-letters.ts +1 -1
  269. package/src/react/use-local-clock.ts +5 -0
  270. package/src/react/use-marquee.ts +1 -1
  271. package/src/react/use-mask-text.ts +1 -1
  272. package/src/react/use-parallax.ts +1 -1
  273. package/src/react/use-particle-hero.ts +1 -1
  274. package/src/react/use-scroll-dock.ts +5 -0
  275. package/src/react/use-snap-strip.ts +1 -1
  276. package/src/react/use-sticker.ts +5 -0
  277. package/src/react/use-studio-dock.ts +8 -0
  278. package/src/react/use-text-roll.ts +1 -1
  279. package/src/react/use-thumbnail-nav.ts +7 -0
  280. package/src/react/use-trailer.ts +6 -0
  281. package/src/react/use-weight-hover.ts +5 -0
  282. package/src/recipes/action-button/README.md +31 -0
  283. package/src/recipes/action-button/action-button.css +113 -0
  284. package/src/recipes/action-button/create-action-button.ts +56 -0
  285. package/src/recipes/card-dock/README.md +1 -1
  286. package/src/recipes/card-dock/card-dock.css +1 -1
  287. package/src/recipes/card-dock/create-card-dock.ts +2 -2
  288. package/src/recipes/cascade/README.md +1 -3
  289. package/src/recipes/cascade/create-cascade.ts +3 -3
  290. package/src/recipes/contrast/README.md +0 -2
  291. package/src/recipes/contrast/create-contrast.ts +1 -1
  292. package/src/recipes/counter/README.md +0 -2
  293. package/src/recipes/counter/create-counter.ts +1 -1
  294. package/src/recipes/curved-carousel/README.md +0 -2
  295. package/src/recipes/curved-carousel/create-curved-carousel.ts +2 -2
  296. package/src/recipes/curved-carousel/curved-carousel.css +1 -1
  297. package/src/recipes/depth-grid/README.md +0 -2
  298. package/src/recipes/depth-grid/create-depth-grid.ts +3 -4
  299. package/src/recipes/details/README.md +0 -2
  300. package/src/recipes/details/create-details.ts +1 -1
  301. package/src/recipes/dock-pill/README.md +3 -3
  302. package/src/recipes/drag-ruler/README.md +3 -3
  303. package/src/recipes/editorial-reveal/README.md +35 -0
  304. package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
  305. package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
  306. package/src/recipes/fill-text/README.md +1 -1
  307. package/src/recipes/fill-text/create-fill-text.ts +1 -1
  308. package/src/recipes/film-hero/README.md +23 -0
  309. package/src/recipes/film-hero/create-film-hero.ts +65 -0
  310. package/src/recipes/film-hero/film-hero.css +29 -0
  311. package/src/recipes/fit-text/README.md +0 -2
  312. package/src/recipes/flow/README.md +0 -2
  313. package/src/recipes/flow/create-flow.ts +1 -1
  314. package/src/recipes/font-cycle/README.md +11 -0
  315. package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
  316. package/src/recipes/font-cycle/font-cycle.css +36 -0
  317. package/src/recipes/frame-zoom/README.md +1 -3
  318. package/src/recipes/frame-zoom/create-frame-zoom.ts +2 -2
  319. package/src/recipes/image-zoom/README.md +16 -0
  320. package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
  321. package/src/recipes/image-zoom/image-zoom.css +7 -0
  322. package/src/recipes/infinite-reel/README.md +3 -3
  323. package/src/recipes/joined-cta/README.md +13 -0
  324. package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
  325. package/src/recipes/joined-cta/joined-cta.css +17 -0
  326. package/src/recipes/letters/README.md +2 -4
  327. package/src/recipes/letters/create-letters.ts +3 -3
  328. package/src/recipes/local-clock/README.md +31 -0
  329. package/src/recipes/local-clock/create-local-clock.ts +61 -0
  330. package/src/recipes/marquee/README.md +0 -2
  331. package/src/recipes/marquee/create-marquee.ts +3 -3
  332. package/src/recipes/marquee/marquee.css +1 -1
  333. package/src/recipes/mask-text/README.md +3 -5
  334. package/src/recipes/mask-text/create-mask-text.ts +4 -4
  335. package/src/recipes/parallax/README.md +1 -3
  336. package/src/recipes/parallax/create-parallax.ts +1 -1
  337. package/src/recipes/pile/README.md +0 -2
  338. package/src/recipes/pile/create-pile.ts +1 -1
  339. package/src/recipes/pile/pile-math.ts +1 -1
  340. package/src/recipes/pile/pile.css +1 -1
  341. package/src/recipes/pill/README.md +0 -2
  342. package/src/recipes/pill/pill.css +2 -2
  343. package/src/recipes/pop/README.md +0 -2
  344. package/src/recipes/pop/create-pop.ts +1 -1
  345. package/src/recipes/radial-menu/README.md +0 -2
  346. package/src/recipes/radial-menu/create-radial-menu.ts +1 -1
  347. package/src/recipes/radial-menu/radial-menu.css +1 -1
  348. package/src/recipes/reflow/README.md +3 -5
  349. package/src/recipes/reflow/create-reflow.ts +1 -1
  350. package/src/recipes/repel-bento/README.md +3 -3
  351. package/src/recipes/reveal/README.md +0 -2
  352. package/src/recipes/reveal/create-reveal.ts +1 -1
  353. package/src/recipes/rotator/README.md +3 -3
  354. package/src/recipes/scroll-dock/README.md +46 -0
  355. package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
  356. package/src/recipes/scroll-dock/scroll-dock.css +70 -0
  357. package/src/recipes/slot-text/README.md +0 -2
  358. package/src/recipes/slot-text/create-slot-text.ts +1 -1
  359. package/src/recipes/snap-strip/README.md +1 -1
  360. package/src/recipes/snap-strip/create-snap-strip.ts +5 -6
  361. package/src/recipes/snap-strip/snap-strip.css +1 -1
  362. package/src/recipes/sortable/README.md +3 -5
  363. package/src/recipes/sortable/create-sortable.ts +4 -4
  364. package/src/recipes/sortable/sortable.css +1 -1
  365. package/src/recipes/spring-cards/README.md +0 -2
  366. package/src/recipes/spring-cards/create-spring-cards.ts +1 -1
  367. package/src/recipes/spring-cards/spring-cards.css +1 -1
  368. package/src/recipes/sticker/README.md +59 -0
  369. package/src/recipes/sticker/create-sticker.ts +490 -0
  370. package/src/recipes/sticker/sticker.css +112 -0
  371. package/src/recipes/stretch-footer/README.md +0 -2
  372. package/src/recipes/stretch-footer/create-stretch-footer.ts +1 -1
  373. package/src/recipes/stretch-footer/stretch-footer.css +1 -1
  374. package/src/recipes/tabs/README.md +0 -2
  375. package/src/recipes/tabs/create-tabs.ts +1 -1
  376. package/src/recipes/text-roll/README.md +1 -3
  377. package/src/recipes/text-roll/create-text-roll.ts +3 -3
  378. package/src/recipes/text-roll/text-roll.css +1 -1
  379. package/src/recipes/thumbnail-nav/README.md +16 -0
  380. package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
  381. package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
  382. package/src/recipes/ticket-menu/README.md +3 -3
  383. package/src/recipes/weight-hover/README.md +37 -0
  384. package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
  385. package/src/recipes/weight-hover/weight-hover.css +7 -0
  386. package/src/recipes/words/README.md +0 -2
  387. package/src/recipes/words/create-words.ts +1 -1
  388. package/src/theme/generate.ts +1 -1
  389. package/src/theme/presets.ts +2 -2
  390. package/src/theme/theme.ts +1 -2
  391. package/dist/chunk-2BQGFOA7.js.map +0 -1
  392. package/dist/chunk-34XYOXHG.js.map +0 -1
  393. package/dist/chunk-6OJAP6ZL.js.map +0 -1
  394. package/dist/chunk-FS2HJVVY.js.map +0 -1
  395. package/dist/chunk-J27KH2A2.js.map +0 -1
  396. package/dist/chunk-KLY5BRRI.js.map +0 -1
  397. package/dist/chunk-Q4LVFNLN.js.map +0 -1
  398. package/dist/chunk-RFDIZYFX.js.map +0 -1
  399. /package/dist/{chunk-ABNRDDO7.js.map → chunk-I3DXF2KQ.js.map} +0 -0
@@ -9,12 +9,12 @@ import { ICON_PATHS } from '../icons';
9
9
 
10
10
  /*
11
11
  * Two cursors.
12
- * chip (Dousan): the system cursor stays; over [data-mk-cursor="Label"] a glass
12
+ * chip: the system cursor stays; over [data-mk-cursor="Label"] a glass
13
13
  * chip with the label follows the pointer on a Motion 800/35 spring, popping in
14
14
  * from scale .8 (500/30); data-mk-cursor-shape="circle" makes it a 96 px disc;
15
15
  * it stays 10 px inside the window; its ink is the accent on light backgrounds
16
- * and white on dark ones (Dousan's luminance test).
17
- * dot (Siena): inside its zone a disc replaces the cursor and eases after it
16
+ * and white on dark ones.
17
+ * dot: inside its zone a disc replaces the cursor and eases after it
18
18
  * (lerp .2 a frame) with a word (data-mk-cursor or `text`); pressing shrinks it
19
19
  * to .7, drops the word and slides two arrows in; [data-mk-cursor-hide] hides it.
20
20
  * Off on touch screens and with reduced motion.
@@ -44,7 +44,7 @@ export interface CursorInstance extends Instance {
44
44
  const svg = (extra = '') =>
45
45
  `<svg aria-hidden="true" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"${extra}><path d="${ICON_PATHS.send}"/></svg>`;
46
46
 
47
- /** Dousan: the first background with alpha > .1 up the tree; light when (299R + 587G + 114B) / 1000 > 180. */
47
+ /** the first background with alpha > .1 up the tree; light when (299R + 587G + 114B) / 1000 > 180. */
48
48
  function backgroundTone(el: Element | null): 'light' | 'dark' {
49
49
  if (!el) return 'light';
50
50
  if (el.closest('img, video, picture, canvas')) return 'dark';
@@ -199,7 +199,7 @@ export function createCursor(host: HTMLElement | null, options: CursorOptions =
199
199
  const [stiffness, damping] = TIMING.springs.cursorFollow;
200
200
  const update = (dt = 1) => {
201
201
  if (variant === 'chip') {
202
- // Dousan keeps the chip inside the window, 10 px from its edges.
202
+ // Keeps the chip inside the window, 10 px from its edges.
203
203
  const halfW = size.width / 2 + t.gutter;
204
204
  const halfH = size.height / 2 + t.gutter;
205
205
  const tx = size.width ? Math.min(Math.max(pointer.x, halfW), innerWidth - halfW) : pointer.x;
@@ -5,7 +5,7 @@ import { useIsomorphicLayoutEffect } from '../../react/use-recipe';
5
5
  import { createCursor, type CursorOptions, type CursorVariant } from './engine';
6
6
 
7
7
  export interface CursorProps {
8
- /** 'chip' (Dousan, default) or 'dot' (Siena). */
8
+ /** 'chip' (default) or 'dot'. */
9
9
  variant?: CursorVariant;
10
10
  /** dot: the word when nothing under the pointer gives one. */
11
11
  text?: string;
@@ -1,21 +1,19 @@
1
1
  # dialog
2
2
 
3
- El diálogo de Stepbro sobre `<dialog>` nativo (foco atrapado, Escape, capa superior). El botón que lo abre se convierte en el diálogo y al cerrar vuelve a él.
3
+ El diálogo sobre `<dialog>` nativo (foco atrapado, Escape, capa superior). El botón que lo abre se convierte en el diálogo y al cerrar vuelve a él.
4
4
 
5
5
  - **Abrir desde el botón (morph):** la superficie viaja recortada desde el rect del botón en 0.7 s con la curva signature y toma su color en 0.3 s; el contenido crece como grupo desde la caja del botón con blur 8 → 0. El botón se oculta en 80 ms mientras está "dentro".
6
6
  - **Elementos compartidos:** `data-mk-shared="titulo"` en una pieza del botón y otra del diálogo: el texto vuela de una a otra (0.7 s al abrir, 0.5 s al cerrar).
7
7
  - **Sin botón en pantalla:** escala .92 → 1 en 0.35 s (`power2.out`).
8
8
  - **Cerrar:** el diálogo se cierra al instante (el foco vuelve y la página responde) mientras una copia inerte se pliega al botón: forma 0.5 s, contenido con blur → 32 px (`.37,.35,0,1`) y fundido (`.56,.27,0,1`), velo `power3.in`; el botón reaparece 0.3 s tras 0.2 s. Sin botón: escala .9 + blur 32.
9
9
  - **Líquido** (`variant="liquid"`): dos blobs bajo filtro goo; posición con resorte 144/14 y patada de 2400 px/s, tamaño 0.3 s `cubic(.8,.3,.5,.8)`, radio 0.7 s, contenido `scale 2 → 1` y blur 8 → 0, color 0.4 s tras 0.15 s. Cierre con resorte 130/18.
10
- - **Backdrop:** tinte 0.5 s con la curva signature (`linear()` de Stepbro).
10
+ - **Backdrop:** tinte 0.5 s con la curva signature (`linear()`).
11
11
  - **Auto-resize:** si el contenido crece o se encoge, la altura se ajusta en 0.45 s (signature).
12
12
  - **Arrastrar para cerrar:** sigue al puntero a la mitad y encoge (`1 − y/400`); pasados 100 px se cierra donde quedó, si no vuelve con `elastic.out(.5,.5)` 0.5 s. También con la rueda. Ignora controles, áreas con scroll y `[data-mk-no-drag]`.
13
13
  - **Ventana flotante** (`float="clave"`, escritorio ≥ 680 px): el asa la levanta (la página sigue usable), se mueve con resorte 1100/38 e inercia, se queda dentro de la pantalla, se redimensiona por las esquinas (mín. 320 × 240) y recuerda su sitio. Un clic en el asa la devuelve.
14
14
  - **Móvil (< 680 px):** pantalla completa, radio 0.
15
15
 
16
- Origen: Stepbro ModalHost (`modal-origin`, `modal-close-origin`, `modal-fallback`, `modal-liquid`, `modal-auto-resize`, `modal-drag`, `modal-float` en el kit); motores portados de Kayro. Medidas observadas: 400 px de ancho y radio 40 en escritorio.
17
-
18
- - **Sheet** (`variant="sheet"`, de Dousan Miao): la hoja sube desde abajo (`y 100% → 0`, 0.72 s `cubic-bezier(.16,1,.3,1)`) y lo marcado con `[data-mk-underlay]` se hunde a escala .98 y opacidad .4 con un resorte 300/30. Al cerrar, una copia inerte cae por debajo de la pantalla y el foco vuelve al instante. No sale del botón. Mientras está abierta, la página no hace scroll.
16
+ - **Sheet** (`variant="sheet"`): la hoja sube desde abajo (`y 100% → 0`, 0.72 s `cubic-bezier(.16,1,.3,1)`) y lo marcado con `[data-mk-underlay]` se hunde a escala .98 y opacidad .4 con un resorte 300/30. Al cerrar, una copia inerte cae por debajo de la pantalla y el foco vuelve al instante. No sale del botón. Mientras está abierta, la página no hace scroll.
19
17
 
20
18
  ## HTML / Astro
21
19
 
@@ -60,7 +58,7 @@ Las opciones van en `Dialog.Root` (`variant`, `placement`, `float`…). Controla
60
58
 
61
59
  | Opción | Atributo | Por defecto | Qué hace |
62
60
  |---|---|---|---|
63
- | `variant` | `variant` | `'morph'` | `'liquid'` para la versión líquida; `'sheet'` para la hoja que sube desde abajo (Dousan) |
61
+ | `variant` | `variant` | `'morph'` | `'liquid'` para la versión líquida; `'sheet'` para la hoja que sube desde abajo |
64
62
  | `placement` | `placement` | `'center'` | `'anchor'` junto al botón, `'side'` panel lateral |
65
63
  | `backdrop` | `no-backdrop` | `true` | Velo tintado |
66
64
  | `closeOnBackdrop` | `persistent` | `true` | Clic fuera cierra |
@@ -89,8 +87,6 @@ Las opciones van en `Dialog.Root` (`variant`, `placement`, `float`…). Controla
89
87
 
90
88
  Ancho hasta `--mk-sheet-max-width` (720px), alto hasta `100dvh − 24px`, radio solo arriba; en el móvil ocupa todo el ancho. Se cierra con Escape, con el fondo, arrastrando hacia abajo o con `[data-mk-close]`. Con movimiento reducido aparece y desaparece sin animar y la página de detrás no cambia. Funciones sueltas: `sheetOpen`, `sheetClose`, `pushUnderlay`, `releaseUnderlay`.
91
89
 
92
- Origen: dousanmiao.com, panel de proyecto a pantalla completa (`modal-project-fullscreen`).
93
-
94
90
  ## Controles y eventos
95
91
 
96
92
  `open(origen?)`, `close()`, `toggle(origen?)`, `float()`, `dock()`, `isOpen`, `isFloating`, `destroy()`.
@@ -1,4 +1,4 @@
1
- /* dialog — Stepbro's modal surface: 400 px, radius 40, tonal surface; full screen under 680 px. */
1
+ /* dialog — modal surface: 400 px, radius 40, tonal surface; full screen under 680 px. */
2
2
  .mk-dialog {
3
3
  --mk-dialog-bg: var(--mk-surface-2);
4
4
  --mk-dialog-radius: var(--mk-radius-dialog);
@@ -218,7 +218,7 @@ mk-dialog:not(:defined) {
218
218
  display: none;
219
219
  }
220
220
 
221
- /* Phones: dialogs take the whole screen with square corners (Stepbro's responsive rules). */
221
+ /* Phones: dialogs take the whole screen with square corners. */
222
222
  @media (max-width: 679px) {
223
223
  .mk-dialog:not([data-mk-placement='anchor']) {
224
224
  width: 100vw;
@@ -238,7 +238,7 @@ mk-dialog:not(:defined) {
238
238
  }
239
239
  }
240
240
 
241
- /* Sheet (Dousan): pinned to the bottom, up to 720 px wide, rises from below. Also on phones. */
241
+ /* Sheet: pinned to the bottom, up to 720 px wide, rises from below. Also on phones. */
242
242
  .mk-dialog[data-mk-variant='sheet'] {
243
243
  --mk-dialog-width: var(--mk-sheet-max-width, 720px);
244
244
  /* A modal <dialog> is inset: 0 — anchored only to the bottom, it takes its content's height. */
@@ -20,7 +20,7 @@ import { pushUnderlay, releaseUnderlay, sheetClose, sheetOpen } from '../../moti
20
20
  import { closeModal, fadeBackdrop, openModal } from '../../motion/surface';
21
21
 
22
22
  /*
23
- * Stepbro's ModalHost on a native <dialog> (focus trap, Escape, top layer): the
23
+ * Dialog on a native <dialog> (focus trap, Escape, top layer): the
24
24
  * control that opened it turns into it and it folds back into that control
25
25
  * (morph or liquid) over a backdrop tinted on the signature curve. It eases to
26
26
  * new heights (auto-resize), can be dragged away (> 100 px) and, on desktop,
@@ -38,7 +38,7 @@ export interface DialogLabels {
38
38
  }
39
39
 
40
40
  export interface DialogOptions {
41
- /** morph (Stepbro, default), liquid (Stepbro), or sheet: rises from the bottom and pushes [data-mk-underlay] back (Dousan). */
41
+ /** morph (default), liquid, or sheet: rises from the bottom and pushes [data-mk-underlay] back. */
42
42
  variant?: DialogVariant;
43
43
  placement?: DialogPlacement;
44
44
  /** Tinted backdrop; false leaves it transparent (menus). */
@@ -173,7 +173,7 @@ function Handle({ children, ...props }: ButtonHTMLAttributes<HTMLButtonElement>)
173
173
  }
174
174
 
175
175
  /**
176
- * Stepbro's dialog for React:
176
+ * dialog for React:
177
177
  * <Dialog.Root><Dialog.Trigger>Crear</Dialog.Trigger><Dialog.Content>
178
178
  * <Dialog.Header><Dialog.Title>…</Dialog.Title><Dialog.Close /></Dialog.Header>
179
179
  * <Dialog.Body>…</Dialog.Body><Dialog.Footer>…</Dialog.Footer>
@@ -0,0 +1,16 @@
1
+ # hover-card
2
+
3
+ Vista previa no modal, accesible al señalar, enfocar o tocar. Los enlaces/botones del panel pueden recibir foco.
4
+
5
+ ```html
6
+ <mk-hover-card class="mk-hover-card" placement="bottom">
7
+ <button type="button" data-mk-hover-trigger>Ver estudio</button>
8
+ <div data-mk-hover-panel hidden role="region" aria-label="Vista previa">Contenido</div>
9
+ </mk-hover-card>
10
+ ```
11
+
12
+ `defineElements()`, `createHoverCard(root, options)`, o `<HoverCard trigger="Ver estudio" placement="bottom">Contenido</HoverCard>`.
13
+
14
+ Opciones: `openDelay=120` ms, `closeDelay=140` ms, `placement='top'|'bottom'`, `onOpenChange(boolean)`. API `open()`, `close()`, `isOpen`, `destroy()`. Evento `mk:open-change` con `{open}`.
15
+
16
+ Escape cierra y retorna al disparador; salir con foco y puntero fuera o tocar fuera cierra. El panel se posiciona respecto al root: mantener un ancestro que permita overflow; no crea portal. No atrapa el foco. Entrada .28 s, instantánea bajo movimiento reducido. Limpia timers y restaura todos los atributos.
@@ -0,0 +1,8 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createHoverCard, type HoverCardInstance } from './engine';
3
+ /** Light DOM: supply the same child markup shown in the README. */
4
+ export class MkHoverCardElement extends MkElement<HoverCardInstance> {
5
+ static options: Record<string, OptionType> = { openDelay: 'number', closeDelay: 'number', placement: 'string' };
6
+ protected create(options: Record<string, unknown>) { return createHoverCard(this, options); }
7
+ }
8
+ export const defineHoverCard = () => defineElement('mk-hover-card', MkHoverCardElement);
@@ -0,0 +1,69 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ export interface HoverCardOptions {
5
+ openDelay?: number;
6
+ closeDelay?: number;
7
+ placement?: 'top' | 'bottom';
8
+ onOpenChange?: (open: boolean) => void;
9
+ }
10
+ export interface HoverCardInstance extends Instance {
11
+ open(): void;
12
+ close(): void;
13
+ readonly isOpen: boolean;
14
+ }
15
+ let sequence = 0;
16
+
17
+ /** Nonmodal preview: pointer, keyboard focus and tap all reveal the same panel. */
18
+ export function createHoverCard(root: HTMLElement | null, options: HoverCardOptions = {}): HoverCardInstance {
19
+ const trigger = root?.querySelector<HTMLElement>('[data-mk-hover-trigger]');
20
+ const panel = root?.querySelector<HTMLElement>('[data-mk-hover-panel]');
21
+ if (!root || !trigger || !panel) return inert({ open() {}, close() {}, isOpen: false });
22
+ const scope = createScope();
23
+ const openDelay = Number.isFinite(options.openDelay) ? Math.max(0, options.openDelay!) : 120;
24
+ const closeDelay = Number.isFinite(options.closeDelay) ? Math.max(0, options.closeDelay!) : 140;
25
+ let opened = false;
26
+ let timer: ReturnType<typeof setTimeout> | undefined;
27
+ let pointerInside = false;
28
+ let id = panel.id;
29
+ if (!id) { do { id = `mk-hover-${++sequence}`; } while (document.getElementById(id)); }
30
+ scope.attr(panel, 'id', id);
31
+ scope.attr(trigger, 'aria-controls', id);
32
+ scope.attr(root, 'data-mk-placement', options.placement ?? 'top');
33
+ scope.attr(root, 'data-mk-ready', '');
34
+ const cancel = () => { clearTimeout(timer); timer = undefined; };
35
+ const set = (value: boolean) => {
36
+ cancel();
37
+ if (scope.disposed) return;
38
+ const changed = value !== opened;
39
+ opened = value;
40
+ scope.attr(trigger, 'aria-expanded', String(value));
41
+ scope.attr(panel, 'hidden', value ? null : '');
42
+ scope.attr(root, 'data-mk-open', value ? '' : null);
43
+ if (changed) {
44
+ options.onOpenChange?.(value);
45
+ root.dispatchEvent(new CustomEvent('mk:open-change', { bubbles: true, detail: { open: value } }));
46
+ }
47
+ };
48
+ const schedule = (value: boolean, delay: number) => { cancel(); timer = setTimeout(() => set(value), delay); };
49
+ scope.on(root, 'pointerenter', () => { pointerInside = true; schedule(true, openDelay); });
50
+ scope.on(root, 'pointerleave', () => {
51
+ pointerInside = false;
52
+ if (!root.contains(document.activeElement)) schedule(false, closeDelay);
53
+ });
54
+ scope.on(root, 'focusin', () => set(true));
55
+ scope.on(root, 'focusout', (event) => {
56
+ if (!pointerInside && !root.contains((event as FocusEvent).relatedTarget as Node | null)) schedule(false, closeDelay);
57
+ });
58
+ scope.on(trigger, 'click', () => set(true));
59
+ scope.on(document, 'pointerdown', (event) => { if (!root.contains(event.target as Node)) set(false); });
60
+ scope.on(root, 'keydown', (event) => {
61
+ if (event.key !== 'Escape' || !opened) return;
62
+ event.preventDefault();
63
+ trigger.focus();
64
+ set(false);
65
+ });
66
+ scope.add(cancel);
67
+ set(false);
68
+ return { open: () => set(true), close: () => set(false), get isOpen() { return opened; }, destroy: () => scope.dispose() };
69
+ }
@@ -0,0 +1,13 @@
1
+ .mk-hover-card, mk-hover-card { position: relative; display: inline-flex; }
2
+ .mk-hover-card [data-mk-hover-panel], mk-hover-card [data-mk-hover-panel] {
3
+ position: absolute; z-index: 10; bottom: calc(100% + .75rem); left: 0;
4
+ width: min(19rem, calc(100vw - 3rem)); padding: 1.25rem; border-radius: var(--mk-radius-lg);
5
+ color: var(--mk-text); background: var(--mk-surface-3); border: 1px solid var(--mk-outline-soft);
6
+ box-shadow: 0 12px 40px color-mix(in srgb, var(--mk-shadow-color) 20%, transparent);
7
+ animation: mk-hover-card-in .28s cubic-bezier(.2,.8,.2,1) both;
8
+ }
9
+ .mk-hover-card [data-mk-hover-panel]::after, mk-hover-card [data-mk-hover-panel]::after { content: ''; position: absolute; inset: -1rem 0; z-index: -1; }
10
+ .mk-hover-card[data-mk-placement=bottom] [data-mk-hover-panel], mk-hover-card[data-mk-placement=bottom] [data-mk-hover-panel] { top: calc(100% + .75rem); bottom: auto; }
11
+ .mk-hover-card [hidden], mk-hover-card [hidden] { display: none !important; }
12
+ @keyframes mk-hover-card-in { from { opacity: 0; transform: translateY(7px) scale(.97); } to { opacity: 1; transform: none; } }
13
+ @media (prefers-reduced-motion: reduce) { .mk-hover-card [data-mk-hover-panel], mk-hover-card [data-mk-hover-panel] { animation: none; } }
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes, type ReactNode } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createHoverCard, type HoverCardOptions } from './engine';
5
+ export interface HoverCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>, HoverCardOptions {
6
+ trigger: ReactNode; children: ReactNode; triggerLabel?: string; panelLabel?: string;
7
+ }
8
+ export function HoverCard({ trigger, children, triggerLabel, panelLabel = 'Vista previa', openDelay, closeDelay, placement, onOpenChange, className, ...props }: HoverCardProps) {
9
+ const ref = useRef<HTMLDivElement>(null);
10
+ const change = useLatest(onOpenChange);
11
+ useIsomorphicLayoutEffect(() => {
12
+ const instance = createHoverCard(ref.current, { openDelay, closeDelay, placement, onOpenChange: (n) => change.current?.(n) });
13
+ return () => instance.destroy();
14
+ }, [openDelay, closeDelay, placement]);
15
+ return <div {...props} ref={ref} className={['mk-hover-card', className].filter(Boolean).join(' ')}>
16
+ <button type="button" className="mk-button mk-button--tonal" data-mk-hover-trigger aria-label={triggerLabel} aria-expanded="false">{trigger}</button>
17
+ <div data-mk-hover-panel role="region" aria-label={panelLabel} hidden>{children}</div>
18
+ </div>;
19
+ }
@@ -0,0 +1,18 @@
1
+ # input-otp
2
+
3
+ OTP numérico con un único input nativo y slots decorativos. Conserva edición, selección, pegado, ceros iniciales y autocomplete=one-time-code.
4
+
5
+ ```html
6
+ <mk-input-otp class="mk-input-otp" length="6">
7
+ <span data-mk-otp-slots aria-hidden="true"></span>
8
+ <input data-mk-otp-input type="text" name="code" aria-label="Código de verificación" />
9
+ </mk-input-otp>
10
+ ```
11
+
12
+ `defineElements()`, `createInputOtp(root, options)`, o `<InputOtp length={6} onChange={setCode} onComplete={verify} />`.
13
+
14
+ Opciones: `length=6` (1…16), `value`, `mask=false`, `onChange(value)`, `onComplete(value)`. API `setValue(text, notify=true)`, `focus()`, `value`, `destroy()`. Limpia caracteres no ASCII numéricos y limita longitud. Eventos `mk:change {value,complete}`, `mk:complete {value}`; escribir el mismo valor no repite callbacks. No envía ni valida códigos contra un servidor.
15
+
16
+ React acepta `defaultValue`, `label`, `name`, `disabled`, `required`, `invalid`. Variables `--mk-otp-size`, `--mk-otp-gap`; foco y error usan tokens. Sin movimiento con reduced-motion. Al destruir restaura input y nodos previos.
17
+
18
+ El reset nativo del formulario sincroniza el control sin emitir callbacks de edición. Los atributos de custom elements conservan el estado editado si value no está presente; value explícito tiene precedencia.
@@ -0,0 +1,14 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createInputOtp, type InputOtpInstance } from './engine';
3
+ /** Light DOM: supply the same child markup shown in the README. */
4
+ export class MkInputOtpElement extends MkElement<InputOtpInstance> {
5
+ static options: Record<string, OptionType> = { length: 'number', value: 'string', mask: 'boolean' };
6
+ protected create(options: Record<string, unknown>) { return createInputOtp(this, options); }
7
+ attributeChangedCallback(name: string, previous: string | null, next: string | null) {
8
+ if (previous === next || !this.isConnected || !this.instance) return;
9
+ const value = this.instance.value;
10
+ this.instance.destroy();
11
+ this.instance = this.create({ ...(!this.hasAttribute('value') ? { value } : {}), ...this.options });
12
+ }
13
+ }
14
+ export const defineInputOtp = () => defineElement('mk-input-otp', MkInputOtpElement);
@@ -0,0 +1,106 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ export interface InputOtpOptions {
5
+ length?: number;
6
+ value?: string;
7
+ mask?: boolean;
8
+ onChange?: (value: string) => void;
9
+ onComplete?: (value: string) => void;
10
+ }
11
+ export interface InputOtpInstance extends Instance {
12
+ setValue(value: string, notify?: boolean): void;
13
+ focus(): void;
14
+ readonly value: string;
15
+ }
16
+
17
+ /** One real input preserves native editing, mobile autofill and password managers. */
18
+ export function createInputOtp(root: HTMLElement | null, options: InputOtpOptions = {}): InputOtpInstance {
19
+ const input = root?.querySelector<HTMLInputElement>('[data-mk-otp-input]');
20
+ const slots = root?.querySelector<HTMLElement>('[data-mk-otp-slots]');
21
+ if (!root || !input || !slots) return inert({ setValue() {}, focus() {}, value: '' });
22
+ const scope = createScope();
23
+ const length = Number.isFinite(options.length) ? Math.min(16, Math.max(1, Math.round(options.length!))) : 6;
24
+ const clean = (text: string) => text.replace(/[^0-9]/g, '').slice(0, length);
25
+ const original = input.value;
26
+ const originalNodes = [...slots.childNodes];
27
+ let value = clean(options.value ?? input.value);
28
+ let pendingReset: Event | null = null;
29
+ const cells = Array.from({ length }, () => {
30
+ const cell = document.createElement('span');
31
+ cell.className = 'mk-input-otp__slot';
32
+ return cell;
33
+ });
34
+ slots.replaceChildren(...cells);
35
+ scope.attr(slots, 'aria-hidden', 'true');
36
+ scope.attr(input, 'type', 'text');
37
+ scope.attr(input, 'inputmode', 'numeric');
38
+ scope.attr(input, 'autocomplete', 'one-time-code');
39
+ scope.attr(input, 'maxlength', String(length));
40
+ scope.attr(input, 'pattern', `[0-9]{${length}}`);
41
+ scope.attr(root, 'data-mk-ready', '');
42
+ const render = () => {
43
+ if (pendingReset) {
44
+ if (pendingReset.eventPhase !== 0) return;
45
+ if (!pendingReset.defaultPrevented) value = clean(input.value);
46
+ pendingReset = null;
47
+ }
48
+ input.value = value;
49
+ const active = document.activeElement === input ? Math.min(length - 1, input.selectionStart ?? value.length) : -1;
50
+ cells.forEach((cell, i) => {
51
+ const text = value[i] ? options.mask ? '•' : value[i]! : '';
52
+ if (cell.textContent !== text) cell.textContent = text;
53
+ cell.toggleAttribute('data-mk-active', i === active);
54
+ cell.toggleAttribute('data-mk-filled', i < value.length);
55
+ });
56
+ scope.attr(root, 'data-mk-complete', value.length === length ? '' : null);
57
+ };
58
+ const setValue = (next: string, notify = true) => {
59
+ if (scope.disposed) return;
60
+ if (pendingReset?.eventPhase === 0) pendingReset = null;
61
+ const previous = value;
62
+ value = clean(String(next));
63
+ render();
64
+ if (notify && value !== previous) {
65
+ options.onChange?.(value);
66
+ root.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { value, complete: value.length === length } }));
67
+ if (value.length === length) {
68
+ options.onComplete?.(value);
69
+ root.dispatchEvent(new CustomEvent('mk:complete', { bubbles: true, detail: { value } }));
70
+ }
71
+ }
72
+ };
73
+ scope.on(input, 'input', (event) => {
74
+ if ((event as InputEvent).isComposing) return;
75
+ const caret = clean(input.value.slice(0, input.selectionStart ?? input.value.length)).length;
76
+ setValue(input.value);
77
+ input.setSelectionRange(caret, caret);
78
+ render();
79
+ });
80
+ scope.on(input, 'compositionend', () => setValue(input.value));
81
+ scope.on(input, 'paste', (event) => {
82
+ const text = (event as ClipboardEvent).clipboardData?.getData('text');
83
+ if (text === undefined) return;
84
+ event.preventDefault();
85
+ const start = input.selectionStart ?? value.length;
86
+ const end = input.selectionEnd ?? start;
87
+ const inserted = clean(text);
88
+ setValue(value.slice(0, start) + inserted + value.slice(end));
89
+ const caret = Math.min(length, start + inserted.length);
90
+ input.setSelectionRange(caret, caret);
91
+ render();
92
+ });
93
+ scope.on(input, 'focus', render);
94
+ scope.on(input, 'blur', render);
95
+ scope.on(input, 'keyup', render);
96
+ scope.on(input, 'click', render);
97
+ scope.on(document, 'selectionchange', () => { if (document.activeElement === input) render(); });
98
+ if (input.form) scope.on(input.form, 'reset', (event) => {
99
+ render();
100
+ pendingReset = event;
101
+ scope.timeout(() => { if (pendingReset === event) render(); }, 0);
102
+ });
103
+ scope.add(() => { slots.replaceChildren(...originalNodes); input.value = original; });
104
+ render();
105
+ return { setValue, focus: () => { if (!scope.disposed) input.focus(); }, get value() { return value; }, destroy: () => scope.dispose() };
106
+ }
@@ -0,0 +1,9 @@
1
+ .mk-input-otp, mk-input-otp { --mk-otp-size: 2.75rem; --mk-otp-gap: .4rem; display: inline-block; position: relative; max-width: 100%; }
2
+ .mk-input-otp [data-mk-otp-slots], mk-input-otp [data-mk-otp-slots] { display: flex; flex-wrap: wrap; gap: var(--mk-otp-gap); }
3
+ .mk-input-otp__slot { box-sizing: border-box; width: var(--mk-otp-size); height: 3.5rem; display: grid; place-items: center; border: 1px solid var(--mk-outline-soft); border-radius: var(--mk-radius-sm); color: var(--mk-text); background: var(--mk-surface-1); font: 1.5rem var(--mk-font-mono, monospace); transition: border-color .2s, background .2s, transform .2s; }
4
+ .mk-input-otp__slot[data-mk-active] { outline: 2px solid var(--mk-accent); outline-offset: 2px; background: var(--mk-accent-soft); color: var(--mk-on-accent-soft); transform: translateY(-2px); }
5
+ .mk-input-otp [data-mk-otp-input], mk-input-otp [data-mk-otp-input] { position: absolute; inset: 0; box-sizing: border-box; width: 100%; height: 100%; border: 0; background: transparent; color: transparent; caret-color: transparent; outline: 0; font: 1.5rem var(--mk-font-mono, monospace); letter-spacing: calc(var(--mk-otp-size) + var(--mk-otp-gap) - 1ch); padding: 0 0 0 calc((var(--mk-otp-size) - 1ch) / 2); }
6
+ .mk-input-otp:has([aria-invalid=true]) .mk-input-otp__slot, mk-input-otp:has([aria-invalid=true]) .mk-input-otp__slot { border-color: var(--mk-error); }
7
+ .mk-input-otp:has(:disabled), mk-input-otp:has(:disabled) { opacity: .45; }
8
+ @media (max-width: 420px) { .mk-input-otp, mk-input-otp { --mk-otp-size: 2.25rem; --mk-otp-gap: .3rem; } }
9
+ @media (prefers-reduced-motion: reduce) { .mk-input-otp__slot { transition: none; transform: none !important; } }
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { useRef, type HTMLAttributes } from 'react';
3
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
4
+ import { createInputOtp, type InputOtpOptions } from './engine';
5
+ export interface InputOtpProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onComplete' | 'defaultValue'>, InputOtpOptions {
6
+ label?: string; name?: string; disabled?: boolean; required?: boolean; invalid?: boolean; defaultValue?: string;
7
+ }
8
+ export function InputOtp({ length, value, defaultValue, mask, onChange, onComplete, label = 'Código de verificación', name, disabled, required, invalid, className, ...props }: InputOtpProps) {
9
+ const ref = useRef<HTMLDivElement>(null);
10
+ const edited = useRef<string | undefined>(undefined);
11
+ const change = useLatest(onChange);
12
+ const complete = useLatest(onComplete);
13
+ useIsomorphicLayoutEffect(() => {
14
+ const instance = createInputOtp(ref.current, { length, value: value ?? edited.current, mask, onChange: (n) => change.current?.(n), onComplete: (n) => complete.current?.(n) });
15
+ return () => { edited.current = instance.value; instance.destroy(); };
16
+ }, [length, value, mask]);
17
+ return <div {...props} ref={ref} className={['mk-input-otp', className].filter(Boolean).join(' ')}>
18
+ <span data-mk-otp-slots aria-hidden="true" />
19
+ <input data-mk-otp-input type="text" aria-label={label} name={name} defaultValue={value ?? defaultValue ?? ''} disabled={disabled} required={required} aria-invalid={invalid || undefined} />
20
+ </div>;
21
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Una isla de cristal esmerilado fija arriba y centrada que crece hasta ser un panel: la fila del contenido pasa de `0fr` a `1fr` (0.6 s `cubic-bezier(.625,.05,0,1)`, 0.125 s después), el radio pasa de 3em a 2em, la página de detrás se oscurece a .4 (0.7 s, 0.1 s después) y las filas del panel suben 3em desde 0.15 s, 25 ms una tras otra. Varios botones comparten la isla: el mismo cierra, otro cambia directamente a su panel. Escape, el fondo, un enlace del panel y hacer scroll después de una rueda o un toque la cierran. Al cargar cae desde −300 % en 1.8 s. En las tarjetas, pasar sobre una baja las demás al 20 % y su imagen crece a 1.04.
4
4
 
5
- Origen: iyo.ai, navbar de la home (`nav`/`d3`): cristal `blur(20px)` sobre `#38383899` con gradiente `141deg #a9c2f214 → #62718c24`, ancho 23.5rem (28rem desde 1920 px; a lo ancho con 1rem / 5vw de margen en móvil), botones de 2.5rem. iyo da solo la duración de la entrada, así que se usa la curva por defecto de GSAP (`power1.out`). El icono de nueve puntos es estático, como en iyo.
5
+ Superficie de cristal con blur de 20 px, ancho adaptable y botones de 2.5rem. La entrada usa `power1.out`; el icono de nueve puntos es estático.
6
6
 
7
7
  ## HTML / Astro
8
8
 
@@ -72,13 +72,13 @@ Con la clase `mk-island` sirve cualquier etiqueta (`<nav class="mk-island">` + `
72
72
 
73
73
  ## Opciones
74
74
 
75
- | Opción | Atributo | Por defecto | Qué hace |
75
+ | Opción | Atributo | Por defecto | Qué hace |
76
76
  | ----------------- | -------------------------- | ----------- | ---------------------------------------------------------- |
77
- | `entrance` | `entrance="false\|manual"` | `true` | Caer desde −300 % al cargar; `'manual'` espera a `ready()` |
78
- | `closeOnScroll` | `close-on-scroll` | `true` | Cerrar al moverse la página tras una rueda o un toque |
79
- | `closeOnNavigate` | `close-on-navigate` | `true` | Cerrar al seguir un enlace del panel |
80
- | `focusOnOpen` | `focus-on-open` | `true` | Enfocar el primer control del panel (150 ms) |
81
- | `onChange` | evento `mk:island` | — | Panel abierto o `null` |
77
+ | `entrance` | `entrance="false\|manual"` | `true` | Caer desde −300 % al cargar; `'manual'` espera a `ready()` |
78
+ | `closeOnScroll` | `close-on-scroll` | `true` | Cerrar al moverse la página tras una rueda o un toque |
79
+ | `closeOnNavigate` | `close-on-navigate` | `true` | Cerrar al seguir un enlace del panel |
80
+ | `focusOnOpen` | `focus-on-open` | `true` | Enfocar el primer control del panel (150 ms) |
81
+ | `onChange` | evento `mk:island` | — | Panel abierto o `null` |
82
82
 
83
83
  API: `createIsland(root, options)` → `{ open(name), close(), toggle(name), ready(), active, destroy }`.
84
84
 
@@ -5,7 +5,7 @@ import { TIMING } from '../../core/timing';
5
5
  import { inert, warnOnce, type Instance } from '../../core/types';
6
6
 
7
7
  /*
8
- * iyo.ai's floating navbar: a frosted glass pill centred at the top that grows
8
+ * floating navbar: a frosted glass pill centred at the top that grows
9
9
  * into a rounded panel (CSS grid rows 0fr → 1fr, 0.6 s cubic-bezier(.625,.05,0,1),
10
10
  * 0.125 s late; radius 3em → 2em) while the page behind darkens to .4 and the
11
11
  * panel's rows rise in 0.15 s + 0.025 s apart. Several triggers share the shell:
@@ -21,7 +21,7 @@ import { inert, warnOnce, type Instance } from '../../core/types';
21
21
  export interface IslandOptions {
22
22
  /** Drop in from above on load (true), wait for ready() ('manual') or not at all. */
23
23
  entrance?: boolean | 'manual';
24
- /** Close when the page scrolls after a wheel or touch (iyo). */
24
+ /** Close when the page scrolls after a wheel or touch. */
25
25
  closeOnScroll?: boolean;
26
26
  /** Close when a link inside a panel is followed. */
27
27
  closeOnNavigate?: boolean;
@@ -169,7 +169,7 @@ export function createIsland(root: HTMLElement | null, options: IslandOptions =
169
169
  }
170
170
  scope.add(() => clearTimeout(focusTimer));
171
171
 
172
- // iyo's entrance gives only a duration, so GSAP's default ease (power1.out) applies.
172
+ // The entrance uses GSAP's default ease (power1.out).
173
173
  const moving = entrance !== false && entrances();
174
174
  if (moving) gsap.set(shell, { yPercent: t.offset });
175
175
  const ready = () => {
@@ -1,5 +1,5 @@
1
1
  /*
2
- * iyo.ai's floating navbar: frosted glass pill at the top that grows into a
2
+ * floating navbar: frosted glass pill at the top that grows into a
3
3
  * panel. Fixed over the page by default; .mk-island--contained keeps it (and its
4
4
  * backdrop) inside a positioned parent, for previews.
5
5
  */
@@ -10,7 +10,7 @@ export interface IslandPanel {
10
10
  label: string;
11
11
  /** Button content; the menu's nine dots by default. */
12
12
  icon?: ReactNode;
13
- /** Light button (iyo's cart). */
13
+ /** Light button. */
14
14
  light?: boolean;
15
15
  content: ReactNode;
16
16
  }
@@ -27,7 +27,7 @@ export interface IslandProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange
27
27
  instanceRef?: { current: IslandInstance | null };
28
28
  }
29
29
 
30
- /** The nine static dots of iyo's menu button. */
30
+ /** The nine static dots of the island menu button. */
31
31
  export function IslandDots() {
32
32
  return (
33
33
  <span className="mk-island__dots" aria-hidden="true">
@@ -38,7 +38,7 @@ export function IslandDots() {
38
38
  );
39
39
  }
40
40
 
41
- /** iyo.ai's floating navbar. */
41
+ /** floating navbar. */
42
42
  export function Island({
43
43
  brand,
44
44
  cta,
@@ -1,8 +1,6 @@
1
1
  # menu
2
2
 
3
- El menú "Opciones" de Stepbro: un panel translúcido que crece desde su botón (el mismo motor del diálogo, anclado y sin velo) y cuyas opciones entran escalonadas desde 90 % con blur 4 px, de la última a la primera, 0.35 s, 40 ms entre opciones, tras 0.1 s. Un clic fuera o Escape lo cierra; flechas, Inicio y Fin mueven el foco.
4
-
5
- Origen: Stepbro NavMenu (abre con ModalHost `absolute`, sin backdrop, superficie translúcida; `nav-menu-enter` en el kit). Su código usa el mismo plegado de 0.5 s que los diálogos; el screencast en vivo de Stepbro midió menos (abre 175 → 364 ms, cierra 66 → 182 ms) que `pnpm timings` aquí (≈ 585 / 510 ms); lo más probable es que su comparación de frames no registrara la cascada pequeña de las opciones ni la cola del plegado.
3
+ El menú "Opciones": un panel translúcido que crece desde su botón (el mismo motor del diálogo, anclado y sin velo) y cuyas opciones entran escalonadas desde 90 % con blur 4 px, de la última a la primera, 0.35 s, 40 ms entre opciones, tras 0.1 s. Un clic fuera o Escape lo cierra; flechas, Inicio y Fin mueven el foco.
6
4
 
7
5
  ## HTML / Astro
8
6
 
@@ -63,5 +61,5 @@ Sobre tu propio `<dialog class="mk-dialog mk-menu">`: `createMenu(dialog, opcion
63
61
 
64
62
  ## Presentación boleto (v0.8)
65
63
 
66
- `createMenu(dialog, { ticket: true })`, `createTicketMenu(dialog)`, `<mk-menu ticket>` o `<Menu.Root ticket>` aplican la presentación de Siena. El motor conserva foco, teclado, Escape y selección. Apertura recortada .9 s; contenido desde 150%, 1.1 s con stagger .02. El kit adapta los paneles globales con grid rows a un diálogo local. La receta `ticket-menu` tiene su propia página en el playground.
64
+ `createMenu(dialog, { ticket: true })`, `createTicketMenu(dialog)`, `<mk-menu ticket>` o `<Menu.Root ticket>` aplican la presentación. El motor conserva foco, teclado, Escape y selección. Apertura recortada .9 s; contenido desde 150%, 1.1 s con stagger .02. La receta `ticket-menu` tiene su propia página en el playground.
67
65
 
@@ -7,7 +7,7 @@ import { flowInMenu } from '../../motion/entrances';
7
7
  import { createDialog, type DialogInstance, type DialogOptions } from '../dialog/engine';
8
8
 
9
9
  /*
10
- * Stepbro's NavMenu "Opciones": the same ModalHost as a dialog, anchored to its
10
+ * Options menu: the same surface engine as a dialog, anchored to its
11
11
  * button, without a tinted backdrop, on a translucent surface that grows out of
12
12
  * the button; its options flow in from 90 % out of a 4 px blur, last first,
13
13
  * 40 ms apart, after 0.1 s. Arrow keys, Home and End move between options.
@@ -1,4 +1,4 @@
1
- /* menu — Stepbro's translucent options panel that grows out of its button (an anchored .mk-dialog). */
1
+ /* menu — translucent options panel that grows out of its button (an anchored .mk-dialog). */
2
2
  .mk-menu {
3
3
  --mk-menu-bg: color-mix(in srgb, var(--mk-surface-3) 78%, transparent);
4
4
  --mk-menu-radius: 28px;
@@ -78,5 +78,5 @@ function Separator() {
78
78
  return <hr data-mk-separator="" />;
79
79
  }
80
80
 
81
- /** Stepbro's options menu: <Menu.Root onSelect><Menu.Trigger/><Menu.Content><Menu.Item value/>…</Menu.Content></Menu.Root>. */
81
+ /** options menu: <Menu.Root onSelect><Menu.Trigger/><Menu.Content><Menu.Item value/>…</Menu.Content></Menu.Root>. */
82
82
  export const Menu = { Root, Trigger, Content, Item, Separator };