@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
@@ -0,0 +1,412 @@
1
+ import { gsap, registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { inert, type Instance } from '../../core/types';
5
+ import {
6
+ addDays, addMonths, capitalize, clampFirstDay, dayKey, monthWeeks, safeFormat, sameDay, sameMonth,
7
+ fromJson, startOfWeek, toDate, toDateTime, weekDays, weekdayNames, type DateInput,
8
+ } from '../../core/dates';
9
+ import { flowIn } from '../../motion/flow';
10
+
11
+ /*
12
+ * Calendar (Melon Mind, section-calendar-test): month and week grids with
13
+ * event chips. Motion from the reference: on first load the day cells flow in
14
+ * (search-result-item-in: y −20, blur 16, .5 s) 5 ms apart and the events
15
+ * 20 ms apart (CalendarComponent eventsSet → flowChilds). Changing month or
16
+ * week slides the grid like Melon Mind's time slots: out −30 px / in +30 px,
17
+ * .25 s ease-out (schedulePicker.css view transitions); the old grid leaves
18
+ * first (.12 s) so two dense months never overlap. Keyboard: an ARIA grid
19
+ * with a roving day button.
20
+ */
21
+
22
+ export type CalendarView = 'month' | 'week';
23
+ export type CalendarEventStatus = 'pending' | 'done' | 'cancelled';
24
+
25
+ export interface CalendarEvent {
26
+ id: string | number;
27
+ title: string;
28
+ start: DateInput;
29
+ end?: DateInput;
30
+ allDay?: boolean;
31
+ status?: CalendarEventStatus;
32
+ /** Any CSS colour for the chip. */
33
+ color?: string;
34
+ }
35
+
36
+ export interface CalendarRange { start: Date; end: Date; view: CalendarView }
37
+
38
+ export interface CalendarLabels {
39
+ more?: (count: number) => string;
40
+ events?: (count: number) => string;
41
+ status?: Partial<Record<CalendarEventStatus, string>>;
42
+ }
43
+
44
+ export interface CalendarOptions {
45
+ /** Date the calendar opens on. */
46
+ date?: DateInput;
47
+ view?: CalendarView;
48
+ /** 0 = Sunday … 6 = Saturday. */
49
+ firstDay?: number;
50
+ locale?: string;
51
+ events?: CalendarEvent[];
52
+ /** Chips per day in the month view before "+N". */
53
+ maxEvents?: number;
54
+ selected?: DateInput | null;
55
+ /** Override "today" (tests, demos in another zone). */
56
+ today?: DateInput;
57
+ labels?: CalendarLabels;
58
+ onSelect?: (date: Date) => void;
59
+ onEventClick?: (event: CalendarEvent) => void;
60
+ onNavigate?: (range: CalendarRange) => void;
61
+ }
62
+
63
+ export interface CalendarInstance extends Instance {
64
+ next(): void;
65
+ prev(): void;
66
+ goToday(): void;
67
+ setDate(date: DateInput): void;
68
+ setView(view: CalendarView): void;
69
+ setEvents(events: CalendarEvent[]): void;
70
+ select(date: DateInput | null, notify?: boolean): void;
71
+ readonly date: Date;
72
+ readonly view: CalendarView;
73
+ readonly selected: Date | null;
74
+ readonly range: CalendarRange;
75
+ }
76
+
77
+ const SLIDE = 30;
78
+ const SLIDE_OUT = 0.12;
79
+ const SLIDE_GAP = 0.1;
80
+ const SLIDE_DURATION = 0.25;
81
+ const CELL_STAGGER = 0.005;
82
+ const EVENT_STAGGER = 0.02;
83
+
84
+ export function createCalendar(root: HTMLElement | null, options: CalendarOptions = {}): CalendarInstance {
85
+ const grid = root?.querySelector<HTMLElement>('[data-mk-calendar-grid]');
86
+ if (!root || !grid) {
87
+ const now = toDate(null);
88
+ return inert({
89
+ next() {}, prev() {}, goToday() {}, setDate() {}, setView() {}, setEvents() {}, select() {},
90
+ date: now, view: 'month' as CalendarView, selected: null, range: { start: now, end: now, view: 'month' as CalendarView },
91
+ });
92
+ }
93
+ registerMotion();
94
+ const scope = createScope();
95
+ const ctx = gsap.context(() => {});
96
+ scope.add(() => ctx.revert());
97
+ const locale = options.locale ?? 'es';
98
+ const firstDay = clampFirstDay(options.firstDay);
99
+ const maxEvents = Number.isFinite(options.maxEvents) ? Math.max(0, Math.floor(options.maxEvents!)) : 3;
100
+ const labels = {
101
+ more: options.labels?.more ?? ((n: number) => `+${n} más`),
102
+ events: options.labels?.events ?? ((n: number) => (n === 1 ? '1 evento' : `${n} eventos`)),
103
+ status: { pending: 'pendiente', done: 'completado', cancelled: 'cancelado', ...options.labels?.status },
104
+ };
105
+ let date = toDate(options.date);
106
+ let view: CalendarView = options.view === 'week' ? 'week' : 'month';
107
+ let selected: Date | null = options.selected ? toDate(options.selected) : null;
108
+ let focusDay: Date = selected ?? date;
109
+ let events: CalendarEvent[] = [];
110
+ let byDay = new Map<string, CalendarEvent[]>();
111
+ let first = true;
112
+ const today = () => toDate(options.today ?? null);
113
+
114
+ const title = root.querySelector<HTMLElement>('[data-mk-calendar-title]');
115
+ const prevButton = root.querySelector<HTMLButtonElement>('[data-mk-calendar-prev]');
116
+ const nextButton = root.querySelector<HTMLButtonElement>('[data-mk-calendar-next]');
117
+ const todayButton = root.querySelector<HTMLButtonElement>('[data-mk-calendar-today]');
118
+ const viewButtons = [...root.querySelectorAll<HTMLButtonElement>('[data-mk-calendar-view]')];
119
+
120
+ const originalGrid = [...grid.childNodes];
121
+ const originalTitle = title ? [...title.childNodes] : [];
122
+ scope.add(() => {
123
+ grid.replaceChildren(...originalGrid);
124
+ title?.replaceChildren(...originalTitle);
125
+ });
126
+ scope.attr(root, 'data-mk-ready', '');
127
+ scope.attr(grid, 'role', 'grid');
128
+ if (title) {
129
+ if (!title.id) scope.attr(title, 'id', `mk-calendar-${Math.random().toString(36).slice(2, 8)}`);
130
+ scope.attr(grid, 'aria-labelledby', title.id);
131
+ }
132
+
133
+ const monthTitle = safeFormat(locale, { month: 'long', year: 'numeric' });
134
+ const rangeTitle = safeFormat(locale, { day: 'numeric', month: 'short', year: 'numeric' });
135
+ const fullDay = safeFormat(locale, { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
136
+ const timeFormat = safeFormat(locale, { hour: 'numeric', minute: '2-digit' });
137
+
138
+ const index = (list: CalendarEvent[]) => {
139
+ events = Array.isArray(list) ? list.filter((e) => e && toDateTime(e.start)) : [];
140
+ byDay = new Map();
141
+ const sorted = [...events].sort((a, b) => toDateTime(a.start)!.getTime() - toDateTime(b.start)!.getTime());
142
+ for (const event of sorted) {
143
+ const start = toDate(toDateTime(event.start)!);
144
+ const endTime = event.end ? toDateTime(event.end) : null;
145
+ const end = endTime && endTime > start ? toDate(endTime) : start;
146
+ // Multi-day events show on every day they cover (at most two months).
147
+ for (let day = start, n = 0; day <= end && n < 62; day = addDays(day, 1), n++) {
148
+ const key = dayKey(day);
149
+ byDay.set(key, [...(byDay.get(key) ?? []), event]);
150
+ }
151
+ }
152
+ };
153
+ index(fromJson<CalendarEvent[]>(options.events) ?? []);
154
+
155
+ const days = () => (view === 'week' ? [weekDays(date, firstDay)] : monthWeeks(date, firstDay));
156
+ const range = (): CalendarRange => {
157
+ const weeks = days();
158
+ return { start: weeks[0][0], end: weeks[weeks.length - 1][6], view };
159
+ };
160
+ const inRange = (day: Date) => {
161
+ const r = range();
162
+ return day >= r.start && day <= r.end;
163
+ };
164
+
165
+ const header = () => {
166
+ const row = document.createElement('div');
167
+ row.className = 'mk-calendar__head';
168
+ row.setAttribute('role', 'row');
169
+ const long = weekdayNames(locale, firstDay, 'short');
170
+ const narrow = weekdayNames(locale, firstDay, 'narrow');
171
+ long.forEach((name, i) => {
172
+ const cell = document.createElement('div');
173
+ cell.className = 'mk-calendar__weekday';
174
+ cell.setAttribute('role', 'columnheader');
175
+ cell.innerHTML = '<span class="mk-calendar__weekday-long"></span><span class="mk-calendar__weekday-narrow" aria-hidden="true"></span>';
176
+ cell.firstElementChild!.textContent = name;
177
+ cell.lastElementChild!.textContent = narrow[i];
178
+ row.append(cell);
179
+ });
180
+ return row;
181
+ };
182
+
183
+ const chip = (event: CalendarEvent) => {
184
+ const button = document.createElement('button');
185
+ button.type = 'button';
186
+ button.className = 'mk-calendar__event';
187
+ button.dataset.mkEvent = String(event.id);
188
+ const status = event.status ?? 'pending';
189
+ button.dataset.state = status;
190
+ if (event.color) button.style.setProperty('--mk-calendar-event-color', event.color);
191
+ const start = toDateTime(event.start)!;
192
+ const time = event.allDay ? '' : timeFormat.format(start);
193
+ button.innerHTML = '<span class="mk-calendar__status" aria-hidden="true"></span><span class="mk-calendar__event-title"></span><span class="mk-calendar__event-time"></span>';
194
+ button.children[1].textContent = event.title;
195
+ button.children[2].textContent = time;
196
+ button.setAttribute('aria-label', [event.title, time, labels.status[status]].filter(Boolean).join(', '));
197
+ return button;
198
+ };
199
+
200
+ const body = () => {
201
+ const wrap = document.createElement('div');
202
+ wrap.className = 'mk-calendar__body';
203
+ wrap.dataset.view = view;
204
+ wrap.setAttribute('role', 'rowgroup');
205
+ const now = today();
206
+ const focusKey = dayKey(focusDay);
207
+ for (const week of days()) {
208
+ const row = document.createElement('div');
209
+ row.className = 'mk-calendar__week';
210
+ row.setAttribute('role', 'row');
211
+ for (const day of week) {
212
+ const key = dayKey(day);
213
+ const cell = document.createElement('div');
214
+ cell.className = 'mk-calendar__day';
215
+ cell.setAttribute('role', 'gridcell');
216
+ cell.dataset.mkDate = key;
217
+ if (view === 'month' && !sameMonth(day, date)) cell.dataset.outside = '';
218
+ if (sameDay(day, now)) cell.dataset.today = '';
219
+ const isSelected = sameDay(day, selected);
220
+ cell.setAttribute('aria-selected', String(isSelected));
221
+ const list = byDay.get(key) ?? [];
222
+ const number = document.createElement('button');
223
+ number.type = 'button';
224
+ number.className = 'mk-calendar__number';
225
+ number.dataset.mkDay = key;
226
+ number.textContent = view === 'week' ? `${day.getDate()}` : String(day.getDate());
227
+ number.tabIndex = key === focusKey ? 0 : -1;
228
+ number.setAttribute('aria-label', `${capitalize(fullDay.format(day))}${list.length ? `, ${labels.events(list.length)}` : ''}`);
229
+ if (sameDay(day, now)) number.setAttribute('aria-current', 'date');
230
+ cell.append(number);
231
+ if (list.length) {
232
+ const box = document.createElement('div');
233
+ box.className = 'mk-calendar__events';
234
+ const shown = view === 'week' ? list : list.slice(0, maxEvents);
235
+ for (const event of shown) box.append(chip(event));
236
+ if (shown.length < list.length) {
237
+ const more = document.createElement('button');
238
+ more.type = 'button';
239
+ more.className = 'mk-calendar__more';
240
+ more.dataset.mkMore = key;
241
+ more.textContent = labels.more(list.length - shown.length);
242
+ box.append(more);
243
+ }
244
+ cell.append(box);
245
+ }
246
+ row.append(cell);
247
+ }
248
+ wrap.append(row);
249
+ }
250
+ return wrap;
251
+ };
252
+
253
+ let current: HTMLElement | null = null;
254
+ const leaving = new Set<HTMLElement>();
255
+ scope.add(() => leaving.forEach((el) => el.remove()));
256
+
257
+ const syncChrome = () => {
258
+ if (title) {
259
+ const r = range();
260
+ let text = capitalize(monthTitle.format(date));
261
+ if (view === 'week') {
262
+ const format = rangeTitle as Intl.DateTimeFormat & { formatRange?: (a: Date, b: Date) => string };
263
+ text = format.formatRange ? format.formatRange(r.start, r.end) : `${rangeTitle.format(r.start)} – ${rangeTitle.format(r.end)}`;
264
+ }
265
+ title.textContent = text;
266
+ }
267
+ for (const button of viewButtons) {
268
+ scope.attr(button, 'aria-pressed', String(button.dataset.mkCalendarView === view));
269
+ }
270
+ };
271
+
272
+ const render = (direction: number) => {
273
+ const next = body();
274
+ const motion = !reduced();
275
+ if (!current) {
276
+ grid.replaceChildren(header(), next);
277
+ } else if (motion && direction !== 0) {
278
+ const old = current;
279
+ old.style.top = `${old.offsetTop}px`;
280
+ old.classList.add('mk-calendar__body--leaving');
281
+ old.setAttribute('aria-hidden', 'true');
282
+ old.querySelectorAll('button').forEach((b) => (b.tabIndex = -1));
283
+ leaving.add(old);
284
+ grid.append(next);
285
+ ctx.add(() => {
286
+ // Out, then in: two dense month grids crossfading read as noise.
287
+ gsap.to(old, { x: -SLIDE * direction, opacity: 0, duration: SLIDE_OUT, ease: 'power1.in', onComplete: () => { old.remove(); leaving.delete(old); } });
288
+ gsap.fromTo(next, { x: SLIDE * direction, opacity: 0 }, { x: 0, opacity: 1, duration: SLIDE_DURATION, delay: SLIDE_GAP, ease: 'power2.out', clearProps: 'transform,opacity' });
289
+ });
290
+ } else {
291
+ current.replaceWith(next);
292
+ }
293
+ current = next;
294
+ syncChrome();
295
+ if (motion) {
296
+ ctx.add(() => {
297
+ if (first) flowIn(next, { target: '.mk-calendar__day', stagger: CELL_STAGGER });
298
+ flowIn(next, { target: '.mk-calendar__event, .mk-calendar__more', stagger: EVENT_STAGGER, delay: first ? 0.1 : direction ? SLIDE_GAP + SLIDE_DURATION * 0.5 : 0 });
299
+ });
300
+ }
301
+ first = false;
302
+ };
303
+
304
+ const focusButton = () => grid.querySelector<HTMLButtonElement>(`.mk-calendar__body:not(.mk-calendar__body--leaving) [data-mk-day="${dayKey(focusDay)}"]`);
305
+ const navigate = (to: Date, focus = false) => {
306
+ if (scope.disposed) return;
307
+ const from = date;
308
+ const moved = view === 'week' ? !sameDay(startOfWeek(to, firstDay), startOfWeek(from, firstDay)) : !sameMonth(to, from);
309
+ date = to;
310
+ focusDay = to;
311
+ if (moved) {
312
+ render(to > from ? 1 : -1);
313
+ const r = range();
314
+ options.onNavigate?.(r);
315
+ root.dispatchEvent(new CustomEvent('mk:navigate', { bubbles: true, detail: r }));
316
+ } else {
317
+ grid.querySelectorAll<HTMLButtonElement>('[data-mk-day]').forEach((b) => (b.tabIndex = b.dataset.mkDay === dayKey(to) ? 0 : -1));
318
+ }
319
+ if (focus) focusButton()?.focus();
320
+ };
321
+
322
+ const select = (value: DateInput | null, notify = true) => {
323
+ if (scope.disposed) return;
324
+ selected = value === null ? null : toDate(value);
325
+ grid.querySelectorAll<HTMLElement>('.mk-calendar__day').forEach((cell) => cell.setAttribute('aria-selected', String(!!selected && cell.dataset.mkDate === dayKey(selected))));
326
+ if (selected && notify) {
327
+ options.onSelect?.(selected);
328
+ root.dispatchEvent(new CustomEvent('mk:select', { bubbles: true, detail: { date: selected, key: dayKey(selected) } }));
329
+ }
330
+ };
331
+
332
+ scope.on(root, 'click', (e) => {
333
+ const target = (e.target as Element).closest<HTMLElement>('button');
334
+ if (!target || !root.contains(target) || (target as HTMLButtonElement).disabled) return;
335
+ if (target === prevButton) navigate(view === 'week' ? addDays(date, -7) : addMonths(date, -1));
336
+ else if (target === nextButton) navigate(view === 'week' ? addDays(date, 7) : addMonths(date, 1));
337
+ else if (target === todayButton) navigate(today(), true);
338
+ else if (target.dataset.mkCalendarView) setView(target.dataset.mkCalendarView as CalendarView);
339
+ else if (target.dataset.mkEvent !== undefined) {
340
+ const event = events.find((ev) => String(ev.id) === target.dataset.mkEvent);
341
+ if (event) {
342
+ options.onEventClick?.(event);
343
+ root.dispatchEvent(new CustomEvent('mk:event', { bubbles: true, detail: { event } }));
344
+ }
345
+ } else if (target.dataset.mkDay || target.dataset.mkMore) {
346
+ const day = toDate(target.dataset.mkDay ?? target.dataset.mkMore);
347
+ focusDay = day;
348
+ grid.querySelectorAll<HTMLButtonElement>('[data-mk-day]').forEach((b) => (b.tabIndex = b.dataset.mkDay === dayKey(day) ? 0 : -1));
349
+ select(day);
350
+ }
351
+ });
352
+
353
+ scope.on(grid, 'keydown', (e) => {
354
+ const event = e as KeyboardEvent;
355
+ const target = event.target as HTMLElement;
356
+ if (!target.dataset?.mkDay) return;
357
+ const day = toDate(target.dataset.mkDay);
358
+ const map: Record<string, () => Date> = {
359
+ ArrowLeft: () => addDays(day, -1),
360
+ ArrowRight: () => addDays(day, 1),
361
+ ArrowUp: () => addDays(day, -7),
362
+ ArrowDown: () => addDays(day, 7),
363
+ Home: () => startOfWeek(day, firstDay),
364
+ End: () => addDays(startOfWeek(day, firstDay), 6),
365
+ PageUp: () => addMonths(day, event.shiftKey ? -12 : -1),
366
+ PageDown: () => addMonths(day, event.shiftKey ? 12 : 1),
367
+ };
368
+ const to = map[event.key]?.();
369
+ if (!to) return;
370
+ event.preventDefault();
371
+ if (!event.key.startsWith('Page') && inRange(to)) {
372
+ focusDay = to;
373
+ grid.querySelectorAll<HTMLButtonElement>('[data-mk-day]').forEach((b) => (b.tabIndex = b.dataset.mkDay === dayKey(to) ? 0 : -1));
374
+ focusButton()?.focus();
375
+ } else navigate(to, true);
376
+ });
377
+
378
+ const setView = (next: CalendarView) => {
379
+ if (scope.disposed || (next !== 'month' && next !== 'week') || next === view) return;
380
+ view = next;
381
+ date = focusDay;
382
+ render(0);
383
+ const r = range();
384
+ options.onNavigate?.(r);
385
+ root.dispatchEvent(new CustomEvent('mk:navigate', { bubbles: true, detail: r }));
386
+ };
387
+
388
+ render(0);
389
+
390
+ return {
391
+ next: () => navigate(view === 'week' ? addDays(date, 7) : addMonths(date, 1)),
392
+ prev: () => navigate(view === 'week' ? addDays(date, -7) : addMonths(date, -1)),
393
+ goToday: () => navigate(today()),
394
+ setDate: (value) => navigate(toDate(value, date)),
395
+ setView,
396
+ setEvents(list) {
397
+ if (scope.disposed) return;
398
+ const hadFocus = grid.contains(document.activeElement);
399
+ index(fromJson<CalendarEvent[]>(list) ?? []);
400
+ const fresh = body();
401
+ current?.replaceWith(fresh);
402
+ current = fresh;
403
+ if (hadFocus) focusButton()?.focus();
404
+ },
405
+ select,
406
+ get date() { return date; },
407
+ get view() { return view; },
408
+ get selected() { return selected; },
409
+ get range() { return range(); },
410
+ destroy: () => scope.dispose(),
411
+ };
412
+ }
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+
3
+ import { useRef, type HTMLAttributes } from 'react';
4
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
5
+ import { createCalendar, type CalendarInstance, type CalendarOptions, type CalendarView } from './engine';
6
+ import type { DateInput } from '../../core/dates';
7
+
8
+ export interface CalendarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onSelect'>, CalendarOptions {
9
+ /** Hide the month/week switch. */
10
+ hideViews?: boolean;
11
+ labels?: CalendarOptions['labels'] & { previous?: string; next?: string; today?: string; month?: string; week?: string };
12
+ }
13
+
14
+ export function Calendar({
15
+ date, view, firstDay, locale, events, maxEvents, selected, today, labels, onSelect, onEventClick, onNavigate,
16
+ hideViews, className, ...props
17
+ }: CalendarProps) {
18
+ const ref = useRef<HTMLDivElement>(null);
19
+ const instance = useRef<CalendarInstance | null>(null);
20
+ const kept = useRef<{ date?: DateInput; view?: CalendarView; selected?: DateInput | null }>({});
21
+ const select = useLatest(onSelect);
22
+ const eventClick = useLatest(onEventClick);
23
+ const navigate = useLatest(onNavigate);
24
+ useIsomorphicLayoutEffect(() => {
25
+ const created = createCalendar(ref.current, {
26
+ date: date ?? kept.current.date,
27
+ view: view ?? kept.current.view,
28
+ selected: selected !== undefined ? selected : kept.current.selected,
29
+ firstDay, locale, events, maxEvents, today, labels,
30
+ onSelect: (d) => select.current?.(d),
31
+ onEventClick: (e) => eventClick.current?.(e),
32
+ onNavigate: (r) => navigate.current?.(r),
33
+ });
34
+ instance.current = created;
35
+ return () => {
36
+ kept.current = { date: created.date, view: created.view, selected: created.selected };
37
+ created.destroy();
38
+ instance.current = null;
39
+ };
40
+ // Events and selection update in place; the rest recreates the grid.
41
+ // eslint-disable-next-line react-hooks/exhaustive-deps
42
+ }, [date, view, firstDay, locale, maxEvents, today]);
43
+ // Re-index only when the events' content changes, not on every new array identity.
44
+ const eventsKey = JSON.stringify(events ?? []);
45
+ const firstEvents = useRef(true);
46
+ useIsomorphicLayoutEffect(() => {
47
+ if (firstEvents.current) { firstEvents.current = false; return; }
48
+ instance.current?.setEvents(events ?? []);
49
+ // eslint-disable-next-line react-hooks/exhaustive-deps
50
+ }, [eventsKey]);
51
+ useIsomorphicLayoutEffect(() => { if (selected !== undefined) instance.current?.select(selected, false); }, [selected]);
52
+ return (
53
+ <div {...props} ref={ref} className={['mk-calendar', className].filter(Boolean).join(' ')}>
54
+ <div className="mk-calendar__header">
55
+ <h3 className="mk-calendar__title" data-mk-calendar-title />
56
+ {!hideViews && (
57
+ <div className="mk-calendar__views" role="group" aria-label="Vista">
58
+ <button type="button" data-mk-calendar-view="month">{labels?.month ?? 'Mes'}</button>
59
+ <button type="button" data-mk-calendar-view="week">{labels?.week ?? 'Semana'}</button>
60
+ </div>
61
+ )}
62
+ <button type="button" className="mk-calendar__nav" data-mk-calendar-prev aria-label={labels?.previous ?? 'Anterior'}>←</button>
63
+ <button type="button" className="mk-calendar__nav" data-mk-calendar-today>{labels?.today ?? 'Hoy'}</button>
64
+ <button type="button" className="mk-calendar__nav" data-mk-calendar-next aria-label={labels?.next ?? 'Siguiente'}>→</button>
65
+ </div>
66
+ <div data-mk-calendar-grid />
67
+ </div>
68
+ );
69
+ }
@@ -1,6 +1,6 @@
1
1
  # chat
2
2
 
3
- El chat de Stepbro (PrettyChat): burbujas de cristal sobre el fondo vivo, desvanecido de 64 px abajo, composer de 64 px que crece con el texto.
3
+ El chat: burbujas de cristal sobre el fondo vivo, desvanecido de 64 px abajo, composer de 64 px que crece con el texto.
4
4
 
5
5
  - **Mensaje nuevo:** la burbuja crece desde el lado de quien habla (`scale .5 → 1`, blur 24 px, 0.7 s curva firma, 60 ms entre burbujas) y sus palabras suben (16 px, blur 6, 0.25 s, 50 ms, `back.out`). La conversación baja al último (0.35 s).
6
6
  - **Pensando:** `pending()` muestra "Pensando…" solo si la respuesta tarda más de 500 ms (con brillo que barre en 1.1 s). La respuesta se transforma desde ese hueco (Flip 0.7 s, sin escalar el texto) mientras sus palabras suben.
@@ -8,8 +8,6 @@ El chat de Stepbro (PrettyChat): burbujas de cristal sobre el fondo vivo, desvan
8
8
  - **Confirmación:** una barra entra con flow y sus palabras suben (100 ms entre palabras).
9
9
  - **Grabando:** el botón de micrófono pulsa (1.5 s).
10
10
 
11
- Origen: Stepbro `pretty-chat`, `chat-message-enter`, `chat-pending-to-reply`, `chat-composer-resize`, `chat-confirm` (kit Money).
12
-
13
11
  ## HTML / Astro
14
12
 
15
13
  ```html
@@ -1,4 +1,4 @@
1
- /* chat — Stepbro's PrettyChat: glass bubbles over the live background, a soft fade at the bottom, a 64 px composer. */
1
+ /* chat — Chat: glass bubbles over the live background, a soft fade at the bottom, a 64 px composer. */
2
2
  .mk-chat,
3
3
  mk-chat {
4
4
  --mk-chat-radius: var(--mk-radius-2xl);
@@ -9,7 +9,7 @@ import { flowIn } from '../../motion/flow';
9
9
  import { iconSvg } from '../icons';
10
10
 
11
11
  /*
12
- * Stepbro's PrettyChat: new bubbles grow from their speaker's side while their
12
+ * Chat: new bubbles grow from their speaker's side while their
13
13
  * words rise (enterMessages); the conversation scrolls to the newest one
14
14
  * (0.35 s). A "Pensando…" placeholder shows only if the answer takes more than
15
15
  * 500 ms, and then morphs (Flip, 0.7 s, no scaling of the text) into the reply.
@@ -183,7 +183,7 @@ function Confirm({ children, ...props }: HTMLAttributes<HTMLDivElement>) {
183
183
  }
184
184
 
185
185
  /**
186
- * Stepbro's chat: <Chat.Root onSend><ChatBackground /><Chat.Messages pending>{messages.map(m =>
186
+ * chat: <Chat.Root onSend><ChatBackground /><Chat.Messages pending>{messages.map(m =>
187
187
  * <Chat.Message role>)}</Chat.Messages><Chat.Composer /></Chat.Root>.
188
188
  */
189
189
  export const Chat = { Root, Messages, Message, Composer, Confirm };
@@ -1,11 +1,9 @@
1
1
  # chat-background
2
2
 
3
- El fondo vivo del chat de Stepbro: dos manchas suaves (primary-container y tertiary-container, degradados radiales de 800 px, saturación 150 %) que orbitan detrás del chat, con grano encima. La energía (0…1, por ejemplo la del micrófono) acelera las órbitas (`10 − 3.5·e` s y `15 − 4.5·e` s), sube su opacidad (`.5 + .15·e`) y las agranda (`(1 + .1·e)·scale`).
3
+ El fondo vivo del chat: dos manchas suaves (primary-container y tertiary-container, degradados radiales de 800 px, saturación 150 %) que orbitan detrás del chat, con grano encima. La energía (0…1, por ejemplo la del micrófono) acelera las órbitas (`10 − 3.5·e` s y `15 − 4.5·e` s), sube su opacidad (`.5 + .15·e`) y las agranda (`(1 + .1·e)·scale`).
4
4
 
5
5
  El estado "ocupado" del onboarding la lleva a energía 1.6 y escala 1.8 en 0.8 s (`power2.out`) y la devuelve en 0.6 s (`power2.inOut`).
6
6
 
7
- Origen: Stepbro PrettyBackground (`chat-background`, `background-audio-energy`, `onboarding-busy-background` en el kit). El grano es un SVG `feTurbulence` en lugar del PNG de Stepbro.
8
-
9
7
  ## HTML / Astro
10
8
 
11
9
  ```html
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Microphone energy for the chat background (Stepbro's analyser): FFT 256,
2
+ * Microphone energy for the chat background: FFT 256,
3
3
  * smoothing .3, −90…−10 dB; the lowest 40 % of the bins, ×3, clamped to 1, with
4
4
  * attack .8 and release .15. The caller passes a MediaStream it already has
5
5
  * permission for: this never asks for the microphone.
@@ -1,4 +1,4 @@
1
- /* chat-background — Stepbro's PrettyBackground: two orbiting blobs and a grain, driven by energy. */
1
+ /* chat-background — Chat background: two orbiting blobs and a grain, driven by energy. */
2
2
  .mk-chat-background,
3
3
  mk-chat-background {
4
4
  --mk-chat-orbit-1: 10s;
@@ -46,7 +46,7 @@ mk-chat-background::after {
46
46
  animation: mk-chat-blob-2 var(--mk-chat-orbit-2) linear infinite;
47
47
  }
48
48
 
49
- /* Grain on top (Stepbro ships a PNG; this is SVG turbulence at the same 30 %). */
49
+ /* Grain overlay using SVG turbulence at 30 %. */
50
50
  .mk-chat-background__noise {
51
51
  position: absolute;
52
52
  inset: 0;
@@ -5,7 +5,7 @@ import { TIMING } from '../../core/timing';
5
5
  import { inert, warnOnce, type Instance } from '../../core/types';
6
6
 
7
7
  /*
8
- * Stepbro's PrettyBackground: two soft blobs (primary-container and
8
+ * Chat background: two soft blobs (primary-container and
9
9
  * tertiary-container, 800 px radial gradients, saturate 150 %) orbit behind the
10
10
  * chat and a grain sits on top. Energy (0…1, e.g. the microphone's) speeds the
11
11
  * orbits up (10 − 3.5·e s and 15 − 4.5·e s), brightens the blobs (.5 + .15·e) and
@@ -12,7 +12,7 @@ export interface ChatBackgroundProps {
12
12
  style?: CSSProperties;
13
13
  }
14
14
 
15
- /** Stepbro's chat background. Put it first inside a positioned container. */
15
+ /** chat background. Put it first inside a positioned container. */
16
16
  export function ChatBackground({ energy = 0, scale = 1, className, style }: ChatBackgroundProps) {
17
17
  const ref = useRef<HTMLDivElement>(null);
18
18
  const instance = useRef<ChatBackgroundInstance | null>(null);
@@ -2,13 +2,11 @@
2
2
 
3
3
  Dos cursores:
4
4
 
5
- - **`chip`** (Dousan Miao, por defecto): el cursor del sistema se queda. Sobre un elemento con `data-mk-cursor="Texto"` aparece un chip de cristal (`.mk-glass`) con el texto que sigue al puntero con un resorte (Motion 800/35) y entra desde `scale .8` (500/30). `data-mk-cursor-shape="circle"` lo convierte en un círculo de 96 px. Se queda a 10 px de los bordes de la ventana. El texto es del color acento sobre fondos claros y blanco sobre oscuros (luminancia `(299R + 587G + 114B) / 1000 > 180`, como Dousan).
6
- - **`dot`** (Siena Film Foundation): dentro de su zona un círculo sustituye al cursor y lo sigue (20 % de la distancia por frame) con una palabra (`text` o el `data-mk-cursor` de lo que hay debajo). Al presionar encoge a .7, la palabra cae y entran dos flechas (0.2–0.5 s, `easeOutQuint`). `data-mk-cursor-hide` lo oculta sobre enlaces o botones.
5
+ - **`chip`**: el cursor del sistema se queda. Sobre un elemento con `data-mk-cursor="Texto"` aparece un chip de cristal (`.mk-glass`) con el texto que sigue al puntero con un resorte (Motion 800/35) y entra desde `scale .8` (500/30). `data-mk-cursor-shape="circle"` lo convierte en un círculo de 96 px. Se queda a 10 px de los bordes de la ventana. El texto es del color acento sobre fondos claros y blanco sobre oscuros (luminancia `(299R + 587G + 114B) / 1000 > 180`).
6
+ - **`dot`**: dentro de su zona un círculo sustituye al cursor y lo sigue (20 % de la distancia por frame) con una palabra (`text` o el `data-mk-cursor` de lo que hay debajo). Al presionar encoge a .7, la palabra cae y entran dos flechas (0.2–0.5 s, `easeOutQuint`). `data-mk-cursor-hide` lo oculta sobre enlaces o botones.
7
7
 
8
8
  Los dos se apagan en pantallas táctiles y con movimiento reducido.
9
9
 
10
- Origen: dousanmiao.com (cursor `kd`), siena.film (`[data-cursor]`, lerp .2).
11
-
12
10
  ## HTML / Astro
13
11
 
14
12
  ```html
@@ -29,7 +27,7 @@ Sin custom element: `createCursor(capa, { variant, zone, text, arrows })` sobre
29
27
  ## React
30
28
 
31
29
  ```tsx
32
- <Cursor /> // en el layout
30
+ <Cursor /> // en el layout
33
31
  <section><Cursor variant="dot" text="Drag" arrows="x" /></section>
34
32
  ```
35
33
 
@@ -1,4 +1,4 @@
1
- /* cursor — Dousan's glass chip and Siena's dot. The layer follows the pointer; the engine writes its transform. */
1
+ /* cursor — glass chip and dot. The layer follows the pointer; the engine writes its transform. */
2
2
  .mk-cursor {
3
3
  position: fixed;
4
4
  top: 0;
@@ -8,7 +8,7 @@
8
8
  will-change: transform;
9
9
  }
10
10
 
11
- /* chip (Dousan): a .mk-glass label; ink follows the background under it. */
11
+ /* chip: a .mk-glass label; ink follows the background under it. */
12
12
  .mk-cursor__chip {
13
13
  --mk-glass-fg: #fff;
14
14
  display: inline-flex;
@@ -46,7 +46,7 @@
46
46
  display: none;
47
47
  }
48
48
 
49
- /* dot (Siena): a disc with a word and two arrows that slide in when pressed. */
49
+ /* dot: a disc with a word and two arrows that slide in when pressed. */
50
50
  .mk-cursor--dot {
51
51
  --mk-cursor-size: 88px;
52
52
  transition: opacity 0.15s var(--mk-ease-quint);
@@ -2,8 +2,8 @@ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
2
  import { createCursor, type CursorInstance, type CursorOptions, type CursorVariant } from './engine';
3
3
 
4
4
  /**
5
- * <mk-cursor></mk-cursor> Dousan's glass chip (once per page)
6
- * <mk-cursor variant="dot" text="Scroll" arrows="y"></mk-cursor> Siena's dot, inside its parent
5
+ * <mk-cursor></mk-cursor> glass chip (once per page)
6
+ * <mk-cursor variant="dot" text="Scroll" arrows="y"></mk-cursor> dot, inside its parent
7
7
  * zone="#hero" picks another zone.
8
8
  */
9
9
  export class MkCursorElement extends MkElement<CursorInstance> {