@yewdiel21/motion-kit 0.9.1 → 0.14.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 (458) hide show
  1. package/README.md +72 -0
  2. package/dist/auto/index.d.ts +72 -3
  3. package/dist/auto/index.js +139 -3
  4. package/dist/auto/index.js.map +1 -1
  5. package/dist/{chunk-BFC27F2G.js → chunk-6P4P7NU6.js} +2 -218
  6. package/dist/chunk-6P4P7NU6.js.map +1 -0
  7. package/dist/chunk-CU6SXW3E.js +20334 -0
  8. package/dist/chunk-CU6SXW3E.js.map +1 -0
  9. package/dist/{chunk-XDWBVFHQ.js → chunk-EY4BJKC4.js} +1203 -29
  10. package/dist/chunk-EY4BJKC4.js.map +1 -0
  11. package/dist/chunk-QP4QJOEN.js +72 -0
  12. package/dist/chunk-QP4QJOEN.js.map +1 -0
  13. package/dist/css/components/activity-feed.css +240 -0
  14. package/dist/css/components/attachment.css +266 -0
  15. package/dist/css/components/breadcrumb.css +110 -0
  16. package/dist/css/components/calendar.css +159 -0
  17. package/dist/css/components/chart-tooltip.css +168 -0
  18. package/dist/css/components/chart.css +296 -0
  19. package/dist/css/components/code-block.css +264 -0
  20. package/dist/css/components/color-area.css +82 -0
  21. package/dist/css/components/color-field.css +67 -0
  22. package/dist/css/components/color-picker.css +131 -0
  23. package/dist/css/components/color-slider.css +113 -0
  24. package/dist/css/components/command-search.css +252 -0
  25. package/dist/css/components/context-menu.css +23 -0
  26. package/dist/css/components/credit-card.css +288 -0
  27. package/dist/css/components/data-table.css +591 -0
  28. package/dist/css/components/date-range-picker.css +168 -0
  29. package/dist/css/components/date-time-picker.css +178 -0
  30. package/dist/css/components/drawer.css +58 -0
  31. package/dist/css/components/editable-table.css +201 -0
  32. package/dist/css/components/empty-state.css +61 -0
  33. package/dist/css/components/file-explorer.css +633 -0
  34. package/dist/css/components/filter-builder.css +232 -0
  35. package/dist/css/components/hold-confirm.css +87 -0
  36. package/dist/css/components/hover-card.css +20 -0
  37. package/dist/css/components/image-preview.css +195 -0
  38. package/dist/css/components/input-otp.css +10 -0
  39. package/dist/css/components/json-viewer.css +272 -0
  40. package/dist/css/components/like-button.css +78 -0
  41. package/dist/css/components/link-preview.css +275 -0
  42. package/dist/css/components/loading-button.css +77 -0
  43. package/dist/css/components/multi-select.css +267 -0
  44. package/dist/css/components/notification-bell.css +127 -0
  45. package/dist/css/components/pagination.css +11 -0
  46. package/dist/css/components/password-strength.css +183 -0
  47. package/dist/css/components/popover.css +25 -0
  48. package/dist/css/components/progress.css +140 -0
  49. package/dist/css/components/quantity.css +16 -0
  50. package/dist/css/components/questionnaire.css +217 -0
  51. package/dist/css/components/range-calendar.css +186 -0
  52. package/dist/css/components/range-slider.css +192 -0
  53. package/dist/css/components/resizable-panel.css +243 -0
  54. package/dist/css/components/rich-text-editor.css +284 -0
  55. package/dist/css/components/scratch-card.css +75 -0
  56. package/dist/css/components/skeleton.css +108 -0
  57. package/dist/css/components/slot-picker.css +126 -0
  58. package/dist/css/components/split-view.css +19 -0
  59. package/dist/css/components/star-rating.css +80 -0
  60. package/dist/css/components/stat-card.css +164 -0
  61. package/dist/css/components/studio-dock.css +35 -0
  62. package/dist/css/components/swipe-actions.css +105 -0
  63. package/dist/css/components/tag-input.css +244 -0
  64. package/dist/css/components/time-field.css +88 -0
  65. package/dist/css/components/timeline.css +219 -0
  66. package/dist/css/components/tooltip.css +79 -0
  67. package/dist/css/components/trailer.css +37 -0
  68. package/dist/css/components/tree-view.css +210 -0
  69. package/dist/css/components/virtual-list.css +195 -0
  70. package/dist/css/controls.css +65 -0
  71. package/dist/css/tokens.css +185 -181
  72. package/dist/elements/index.d.ts +742 -5
  73. package/dist/elements/index.js +1527 -8
  74. package/dist/elements/index.js.map +1 -1
  75. package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
  76. package/dist/engine-QECQnGz1.d.ts +33 -0
  77. package/dist/index.css +10113 -181
  78. package/dist/index.d.ts +89 -5
  79. package/dist/index.js +285 -9
  80. package/dist/index.js.map +1 -1
  81. package/dist/react/index.d.ts +678 -23
  82. package/dist/react/index.js +2389 -7
  83. package/dist/react/index.js.map +1 -1
  84. package/dist/{reduced-B98f8LPs.d.ts → reduced-BBUusld6.d.ts} +206 -2
  85. package/dist/reveal-DEvp6U6T.d.ts +2381 -0
  86. package/dist/{store-pHiUvxP8.d.ts → store-DjkTBX7f.d.ts} +2 -2
  87. package/package.json +1 -1
  88. package/registry.json +583 -104
  89. package/skills/motion-kit/SKILL.md +209 -189
  90. package/src/auto/auto-init.ts +136 -0
  91. package/src/components/activity-feed/README.md +56 -0
  92. package/src/components/activity-feed/activity-feed.css +239 -0
  93. package/src/components/activity-feed/element.ts +36 -0
  94. package/src/components/activity-feed/engine.ts +351 -0
  95. package/src/components/activity-feed/react.tsx +32 -0
  96. package/src/components/attachment/README.md +27 -0
  97. package/src/components/attachment/attachment.css +265 -0
  98. package/src/components/attachment/element.ts +50 -0
  99. package/src/components/attachment/engine.ts +469 -0
  100. package/src/components/attachment/react.tsx +65 -0
  101. package/src/components/breadcrumb/README.md +38 -0
  102. package/src/components/breadcrumb/breadcrumb.css +109 -0
  103. package/src/components/breadcrumb/element.ts +27 -0
  104. package/src/components/breadcrumb/engine.ts +249 -0
  105. package/src/components/breadcrumb/react.tsx +30 -0
  106. package/src/components/calendar/README.md +82 -0
  107. package/src/components/calendar/calendar.css +158 -0
  108. package/src/components/calendar/element.ts +27 -0
  109. package/src/components/calendar/engine.ts +412 -0
  110. package/src/components/calendar/react.tsx +69 -0
  111. package/src/components/chart/README.md +24 -0
  112. package/src/components/chart/chart.css +295 -0
  113. package/src/components/chart/element.ts +41 -0
  114. package/src/components/chart/engine.ts +811 -0
  115. package/src/components/chart/react.tsx +50 -0
  116. package/src/components/chart-tooltip/README.md +53 -0
  117. package/src/components/chart-tooltip/chart-tooltip.css +167 -0
  118. package/src/components/chart-tooltip/element.ts +37 -0
  119. package/src/components/chart-tooltip/engine.ts +442 -0
  120. package/src/components/chart-tooltip/react.tsx +47 -0
  121. package/src/components/code-block/README.md +19 -0
  122. package/src/components/code-block/code-block.css +263 -0
  123. package/src/components/code-block/element.ts +34 -0
  124. package/src/components/code-block/engine.ts +254 -0
  125. package/src/components/code-block/highlight.ts +244 -0
  126. package/src/components/code-block/react.tsx +94 -0
  127. package/src/components/color-area/README.md +50 -0
  128. package/src/components/color-area/color-area.css +81 -0
  129. package/src/components/color-area/element.ts +26 -0
  130. package/src/components/color-area/engine.ts +246 -0
  131. package/src/components/color-area/react.tsx +42 -0
  132. package/src/components/color-field/README.md +43 -0
  133. package/src/components/color-field/color-field.css +66 -0
  134. package/src/components/color-field/element.ts +26 -0
  135. package/src/components/color-field/engine.ts +174 -0
  136. package/src/components/color-field/react.tsx +42 -0
  137. package/src/components/color-picker/README.md +45 -0
  138. package/src/components/color-picker/color-picker.css +130 -0
  139. package/src/components/color-picker/element.ts +45 -0
  140. package/src/components/color-picker/engine.ts +314 -0
  141. package/src/components/color-picker/react.tsx +47 -0
  142. package/src/components/color-slider/README.md +55 -0
  143. package/src/components/color-slider/color-slider.css +112 -0
  144. package/src/components/color-slider/element.ts +26 -0
  145. package/src/components/color-slider/engine.ts +234 -0
  146. package/src/components/color-slider/react.tsx +45 -0
  147. package/src/components/command-search/README.md +15 -0
  148. package/src/components/command-search/command-search.css +251 -0
  149. package/src/components/command-search/element.ts +36 -0
  150. package/src/components/command-search/engine.ts +356 -0
  151. package/src/components/command-search/match.ts +53 -0
  152. package/src/components/command-search/react.tsx +66 -0
  153. package/src/components/context-menu/README.md +31 -0
  154. package/src/components/context-menu/context-menu.css +22 -0
  155. package/src/components/context-menu/element.ts +40 -0
  156. package/src/components/context-menu/engine.ts +153 -0
  157. package/src/components/context-menu/react.tsx +57 -0
  158. package/src/components/credit-card/README.md +20 -0
  159. package/src/components/credit-card/card.ts +77 -0
  160. package/src/components/credit-card/credit-card.css +287 -0
  161. package/src/components/credit-card/element.ts +33 -0
  162. package/src/components/credit-card/engine.ts +410 -0
  163. package/src/components/credit-card/react.tsx +36 -0
  164. package/src/components/data-table/README.md +83 -0
  165. package/src/components/data-table/data-table.css +590 -0
  166. package/src/components/data-table/element.ts +54 -0
  167. package/src/components/data-table/engine.ts +1059 -0
  168. package/src/components/data-table/react.tsx +94 -0
  169. package/src/components/date-range-picker/README.md +51 -0
  170. package/src/components/date-range-picker/date-range-picker.css +167 -0
  171. package/src/components/date-range-picker/element.ts +52 -0
  172. package/src/components/date-range-picker/engine.ts +352 -0
  173. package/src/components/date-range-picker/react.tsx +57 -0
  174. package/src/components/date-time-picker/README.md +48 -0
  175. package/src/components/date-time-picker/date-time-picker.css +177 -0
  176. package/src/components/date-time-picker/element.ts +47 -0
  177. package/src/components/date-time-picker/engine.ts +350 -0
  178. package/src/components/date-time-picker/react.tsx +54 -0
  179. package/src/components/dialog/README.md +103 -102
  180. package/src/components/dialog/element.ts +123 -118
  181. package/src/components/dialog/engine.ts +449 -432
  182. package/src/components/drawer/README.md +28 -0
  183. package/src/components/drawer/drawer.css +57 -0
  184. package/src/components/drawer/element.ts +21 -0
  185. package/src/components/drawer/engine.ts +56 -0
  186. package/src/components/drawer/react.tsx +85 -0
  187. package/src/components/editable-table/README.md +67 -0
  188. package/src/components/editable-table/editable-table.css +200 -0
  189. package/src/components/editable-table/element.ts +45 -0
  190. package/src/components/editable-table/engine.ts +775 -0
  191. package/src/components/editable-table/react.tsx +79 -0
  192. package/src/components/empty-state/README.md +41 -0
  193. package/src/components/empty-state/element.ts +23 -0
  194. package/src/components/empty-state/empty-state.css +60 -0
  195. package/src/components/empty-state/engine.ts +232 -0
  196. package/src/components/empty-state/react.tsx +34 -0
  197. package/src/components/file-explorer/README.md +67 -0
  198. package/src/components/file-explorer/element.ts +39 -0
  199. package/src/components/file-explorer/engine.ts +1307 -0
  200. package/src/components/file-explorer/file-explorer.css +632 -0
  201. package/src/components/file-explorer/react.tsx +77 -0
  202. package/src/components/filter-builder/README.md +47 -0
  203. package/src/components/filter-builder/element.ts +34 -0
  204. package/src/components/filter-builder/engine.ts +365 -0
  205. package/src/components/filter-builder/filter-builder.css +231 -0
  206. package/src/components/filter-builder/react.tsx +36 -0
  207. package/src/components/hold-confirm/README.md +13 -0
  208. package/src/components/hold-confirm/element.ts +24 -0
  209. package/src/components/hold-confirm/engine.ts +216 -0
  210. package/src/components/hold-confirm/hold-confirm.css +86 -0
  211. package/src/components/hold-confirm/react.tsx +28 -0
  212. package/src/components/hover-card/README.md +20 -0
  213. package/src/components/hover-card/element.ts +8 -0
  214. package/src/components/hover-card/engine.ts +131 -0
  215. package/src/components/hover-card/hover-card.css +19 -0
  216. package/src/components/hover-card/react.tsx +19 -0
  217. package/src/components/image-preview/README.md +16 -0
  218. package/src/components/image-preview/element.ts +26 -0
  219. package/src/components/image-preview/engine.ts +325 -0
  220. package/src/components/image-preview/image-preview.css +194 -0
  221. package/src/components/image-preview/react.tsx +43 -0
  222. package/src/components/input-otp/README.md +18 -0
  223. package/src/components/input-otp/element.ts +14 -0
  224. package/src/components/input-otp/engine.ts +106 -0
  225. package/src/components/input-otp/input-otp.css +9 -0
  226. package/src/components/input-otp/react.tsx +21 -0
  227. package/src/components/json-viewer/README.md +56 -0
  228. package/src/components/json-viewer/element.ts +37 -0
  229. package/src/components/json-viewer/engine.ts +440 -0
  230. package/src/components/json-viewer/json-viewer.css +271 -0
  231. package/src/components/json-viewer/react.tsx +56 -0
  232. package/src/components/like-button/README.md +13 -0
  233. package/src/components/like-button/element.ts +47 -0
  234. package/src/components/like-button/engine.ts +145 -0
  235. package/src/components/like-button/like-button.css +77 -0
  236. package/src/components/like-button/react.tsx +39 -0
  237. package/src/components/link-preview/README.md +19 -0
  238. package/src/components/link-preview/element.ts +29 -0
  239. package/src/components/link-preview/engine.ts +192 -0
  240. package/src/components/link-preview/link-preview.css +274 -0
  241. package/src/components/link-preview/react.tsx +101 -0
  242. package/src/components/loading-button/README.md +45 -0
  243. package/src/components/loading-button/element.ts +34 -0
  244. package/src/components/loading-button/engine.ts +201 -0
  245. package/src/components/loading-button/loading-button.css +76 -0
  246. package/src/components/loading-button/react.tsx +43 -0
  247. package/src/components/multi-select/README.md +48 -0
  248. package/src/components/multi-select/element.ts +28 -0
  249. package/src/components/multi-select/engine.ts +294 -0
  250. package/src/components/multi-select/multi-select.css +266 -0
  251. package/src/components/multi-select/react.tsx +39 -0
  252. package/src/components/notification-bell/README.md +14 -0
  253. package/src/components/notification-bell/element.ts +48 -0
  254. package/src/components/notification-bell/engine.ts +202 -0
  255. package/src/components/notification-bell/notification-bell.css +126 -0
  256. package/src/components/notification-bell/react.tsx +57 -0
  257. package/src/components/pagination/README.md +17 -0
  258. package/src/components/pagination/element.ts +14 -0
  259. package/src/components/pagination/engine.ts +102 -0
  260. package/src/components/pagination/pagination.css +10 -0
  261. package/src/components/pagination/react.tsx +22 -0
  262. package/src/components/password-strength/README.md +17 -0
  263. package/src/components/password-strength/element.ts +22 -0
  264. package/src/components/password-strength/engine.ts +212 -0
  265. package/src/components/password-strength/password-strength.css +182 -0
  266. package/src/components/password-strength/react.tsx +36 -0
  267. package/src/components/password-strength/score.ts +62 -0
  268. package/src/components/popover/README.md +44 -0
  269. package/src/components/popover/element.ts +31 -0
  270. package/src/components/popover/engine.ts +114 -0
  271. package/src/components/popover/popover.css +24 -0
  272. package/src/components/popover/react.tsx +50 -0
  273. package/src/components/progress/README.md +17 -0
  274. package/src/components/progress/element.ts +30 -0
  275. package/src/components/progress/engine.ts +173 -0
  276. package/src/components/progress/progress.css +139 -0
  277. package/src/components/progress/react.tsx +35 -0
  278. package/src/components/quantity/README.md +20 -0
  279. package/src/components/quantity/element.ts +14 -0
  280. package/src/components/quantity/engine.ts +85 -0
  281. package/src/components/quantity/quantity.css +15 -0
  282. package/src/components/quantity/react.tsx +21 -0
  283. package/src/components/questionnaire/README.md +32 -0
  284. package/src/components/questionnaire/element.ts +118 -0
  285. package/src/components/questionnaire/engine.ts +255 -0
  286. package/src/components/questionnaire/questionnaire.css +216 -0
  287. package/src/components/questionnaire/react.tsx +140 -0
  288. package/src/components/range-calendar/README.md +58 -0
  289. package/src/components/range-calendar/element.ts +42 -0
  290. package/src/components/range-calendar/engine.ts +390 -0
  291. package/src/components/range-calendar/range-calendar.css +185 -0
  292. package/src/components/range-calendar/react.tsx +56 -0
  293. package/src/components/range-slider/README.md +23 -0
  294. package/src/components/range-slider/element.ts +53 -0
  295. package/src/components/range-slider/engine.ts +316 -0
  296. package/src/components/range-slider/range-slider.css +191 -0
  297. package/src/components/range-slider/react.tsx +66 -0
  298. package/src/components/resizable-panel/README.md +61 -0
  299. package/src/components/resizable-panel/element.ts +36 -0
  300. package/src/components/resizable-panel/engine.ts +551 -0
  301. package/src/components/resizable-panel/react.tsx +74 -0
  302. package/src/components/resizable-panel/resizable-panel.css +242 -0
  303. package/src/components/rich-text-editor/README.md +57 -0
  304. package/src/components/rich-text-editor/element.ts +34 -0
  305. package/src/components/rich-text-editor/engine.ts +673 -0
  306. package/src/components/rich-text-editor/react.tsx +60 -0
  307. package/src/components/rich-text-editor/rich-text-editor.css +283 -0
  308. package/src/components/scratch-card/README.md +15 -0
  309. package/src/components/scratch-card/element.ts +30 -0
  310. package/src/components/scratch-card/engine.ts +257 -0
  311. package/src/components/scratch-card/react.tsx +40 -0
  312. package/src/components/scratch-card/scratch-card.css +74 -0
  313. package/src/components/skeleton/README.md +15 -0
  314. package/src/components/skeleton/element.ts +32 -0
  315. package/src/components/skeleton/engine.ts +104 -0
  316. package/src/components/skeleton/react.tsx +41 -0
  317. package/src/components/skeleton/skeleton.css +107 -0
  318. package/src/components/slot-picker/README.md +68 -0
  319. package/src/components/slot-picker/element.ts +27 -0
  320. package/src/components/slot-picker/engine.ts +412 -0
  321. package/src/components/slot-picker/react.tsx +76 -0
  322. package/src/components/slot-picker/slot-picker.css +125 -0
  323. package/src/components/split-view/README.md +50 -0
  324. package/src/components/split-view/element.ts +34 -0
  325. package/src/components/split-view/engine.ts +114 -0
  326. package/src/components/split-view/react.tsx +63 -0
  327. package/src/components/split-view/split-view.css +18 -0
  328. package/src/components/star-rating/README.md +14 -0
  329. package/src/components/star-rating/element.ts +26 -0
  330. package/src/components/star-rating/engine.ts +217 -0
  331. package/src/components/star-rating/react.tsx +33 -0
  332. package/src/components/star-rating/star-rating.css +79 -0
  333. package/src/components/stat-card/README.md +42 -0
  334. package/src/components/stat-card/element.ts +40 -0
  335. package/src/components/stat-card/engine.ts +283 -0
  336. package/src/components/stat-card/react.tsx +36 -0
  337. package/src/components/stat-card/stat-card.css +163 -0
  338. package/src/components/studio-dock/README.md +44 -0
  339. package/src/components/studio-dock/element.ts +11 -0
  340. package/src/components/studio-dock/engine.ts +212 -0
  341. package/src/components/studio-dock/react.tsx +23 -0
  342. package/src/components/studio-dock/studio-dock.css +34 -0
  343. package/src/components/swipe-actions/README.md +19 -0
  344. package/src/components/swipe-actions/element.ts +26 -0
  345. package/src/components/swipe-actions/engine.ts +260 -0
  346. package/src/components/swipe-actions/react.tsx +80 -0
  347. package/src/components/swipe-actions/swipe-actions.css +104 -0
  348. package/src/components/tag-input/README.md +42 -0
  349. package/src/components/tag-input/element.ts +39 -0
  350. package/src/components/tag-input/engine.ts +313 -0
  351. package/src/components/tag-input/react.tsx +54 -0
  352. package/src/components/tag-input/tag-input.css +243 -0
  353. package/src/components/time-field/README.md +54 -0
  354. package/src/components/time-field/element.ts +38 -0
  355. package/src/components/time-field/engine.ts +110 -0
  356. package/src/components/time-field/react.tsx +45 -0
  357. package/src/components/time-field/time-field.css +87 -0
  358. package/src/components/timeline/README.md +57 -0
  359. package/src/components/timeline/element.ts +31 -0
  360. package/src/components/timeline/engine.ts +300 -0
  361. package/src/components/timeline/react.tsx +59 -0
  362. package/src/components/timeline/timeline.css +218 -0
  363. package/src/components/tooltip/README.md +46 -0
  364. package/src/components/tooltip/element.ts +22 -0
  365. package/src/components/tooltip/engine.ts +288 -0
  366. package/src/components/tooltip/react.tsx +30 -0
  367. package/src/components/tooltip/tooltip.css +78 -0
  368. package/src/components/trailer/README.md +28 -0
  369. package/src/components/trailer/element.ts +13 -0
  370. package/src/components/trailer/engine.ts +189 -0
  371. package/src/components/trailer/react.tsx +16 -0
  372. package/src/components/trailer/trailer.css +36 -0
  373. package/src/components/tree-view/README.md +61 -0
  374. package/src/components/tree-view/element.ts +32 -0
  375. package/src/components/tree-view/engine.ts +569 -0
  376. package/src/components/tree-view/react.tsx +78 -0
  377. package/src/components/tree-view/tree-view.css +209 -0
  378. package/src/components/virtual-list/README.md +81 -0
  379. package/src/components/virtual-list/element.ts +42 -0
  380. package/src/components/virtual-list/engine.ts +678 -0
  381. package/src/components/virtual-list/react.tsx +101 -0
  382. package/src/components/virtual-list/virtual-list.css +194 -0
  383. package/src/core/chips.ts +196 -0
  384. package/src/core/color.ts +304 -0
  385. package/src/core/date-range.ts +67 -0
  386. package/src/core/dates.ts +108 -0
  387. package/src/core/files.ts +107 -0
  388. package/src/core/filter.ts +229 -0
  389. package/src/core/gsap.ts +80 -65
  390. package/src/core/listbox.ts +332 -0
  391. package/src/core/mark.ts +29 -0
  392. package/src/core/position.ts +89 -0
  393. package/src/core/relative-time.ts +77 -0
  394. package/src/core/segment-field.ts +373 -0
  395. package/src/core/segments.ts +153 -0
  396. package/src/core/tree.ts +116 -0
  397. package/src/core/virtual.ts +79 -0
  398. package/src/css/controls/README.md +14 -0
  399. package/src/css/controls/status.css +63 -0
  400. package/src/css/tokens.css +186 -182
  401. package/src/elements/index.ts +171 -0
  402. package/src/index.ts +97 -0
  403. package/src/motion/micro.ts +248 -0
  404. package/src/motion/sheet.ts +88 -65
  405. package/src/motion/surface.ts +9 -0
  406. package/src/react/index.ts +78 -0
  407. package/src/react/use-action-button.ts +6 -0
  408. package/src/react/use-editorial-reveal.ts +7 -0
  409. package/src/react/use-film-hero.ts +6 -0
  410. package/src/react/use-font-cycle.ts +7 -0
  411. package/src/react/use-image-zoom.ts +5 -0
  412. package/src/react/use-joined-cta.ts +5 -0
  413. package/src/react/use-local-clock.ts +5 -0
  414. package/src/react/use-scroll-dock.ts +5 -0
  415. package/src/react/use-sticker.ts +5 -0
  416. package/src/react/use-studio-dock.ts +8 -0
  417. package/src/react/use-thumbnail-nav.ts +7 -0
  418. package/src/react/use-trailer.ts +6 -0
  419. package/src/react/use-weight-hover.ts +5 -0
  420. package/src/recipes/action-button/README.md +31 -0
  421. package/src/recipes/action-button/action-button.css +113 -0
  422. package/src/recipes/action-button/create-action-button.ts +56 -0
  423. package/src/recipes/editorial-reveal/README.md +35 -0
  424. package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
  425. package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
  426. package/src/recipes/film-hero/README.md +23 -0
  427. package/src/recipes/film-hero/create-film-hero.ts +65 -0
  428. package/src/recipes/film-hero/film-hero.css +29 -0
  429. package/src/recipes/font-cycle/README.md +11 -0
  430. package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
  431. package/src/recipes/font-cycle/font-cycle.css +36 -0
  432. package/src/recipes/image-zoom/README.md +16 -0
  433. package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
  434. package/src/recipes/image-zoom/image-zoom.css +7 -0
  435. package/src/recipes/joined-cta/README.md +13 -0
  436. package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
  437. package/src/recipes/joined-cta/joined-cta.css +17 -0
  438. package/src/recipes/local-clock/README.md +31 -0
  439. package/src/recipes/local-clock/create-local-clock.ts +61 -0
  440. package/src/recipes/scroll-dock/README.md +46 -0
  441. package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
  442. package/src/recipes/scroll-dock/scroll-dock.css +70 -0
  443. package/src/recipes/sticker/README.md +59 -0
  444. package/src/recipes/sticker/create-sticker.ts +490 -0
  445. package/src/recipes/sticker/sticker.css +112 -0
  446. package/src/recipes/thumbnail-nav/README.md +16 -0
  447. package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
  448. package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
  449. package/src/recipes/weight-hover/README.md +37 -0
  450. package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
  451. package/src/recipes/weight-hover/weight-hover.css +7 -0
  452. package/dist/chunk-BFC27F2G.js.map +0 -1
  453. package/dist/chunk-J6OCFNGU.js +0 -177
  454. package/dist/chunk-J6OCFNGU.js.map +0 -1
  455. package/dist/chunk-JQMJQSXD.js +0 -2374
  456. package/dist/chunk-JQMJQSXD.js.map +0 -1
  457. package/dist/chunk-XDWBVFHQ.js.map +0 -1
  458. package/dist/reveal-C0hCDcB_.d.ts +0 -415
@@ -0,0 +1,91 @@
1
+ import { gsap, CustomEase, 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 { createMaskText } from '../mask-text/create-mask-text';
6
+ import { createCascade } from '../cascade/create-cascade';
7
+ import { createParallax } from '../parallax/create-parallax';
8
+ import { createContrast } from '../contrast/create-contrast';
9
+
10
+ export interface EditorialRevealOptions {
11
+ trigger?: 'scroll' | 'load' | 'manual';
12
+ /** Title and staged groups, seconds. Reference default 1.2. */
13
+ duration?: number;
14
+ stagger?: number;
15
+ /** Title pieces' start offset, percent. Reference default 110. */
16
+ y?: number;
17
+ ease?: string;
18
+ hoverDuration?: number;
19
+ /** Duplicate label/arrow content moves by this many em. */
20
+ buttonShift?: number;
21
+ imageScale?: number;
22
+ parallax?: boolean;
23
+ }
24
+ export interface EditorialRevealInstance extends Instance { play(): void; reset(): void; }
25
+ let nextScope = 0;
26
+
27
+ /** Editorial composition: masked headings, staged contact groups, restrained image/card motion and keyboard-equivalent buttons. */
28
+ export function createEditorialReveal(root: HTMLElement | null, options: EditorialRevealOptions = {}): EditorialRevealInstance {
29
+ if (!root) return inert({ play() {}, reset() {} });
30
+ registerMotion();
31
+ const scope = createScope();
32
+ const context = gsap.context(() => {}, root);
33
+ const trigger = options.trigger ?? 'scroll';
34
+ const duration = options.duration ?? 1.2;
35
+ const stagger = options.stagger ?? .03;
36
+ const hoverDuration = options.hoverDuration ?? .5;
37
+ const ease = options.ease ?? 'mk-editorial-reference';
38
+ CustomEase.create('mk-editorial-reference', 'M0,0 C0.6,0 0,1 1,1');
39
+ // Helpers restore their own nodes; these snapshots also preserve caller inline styles/readiness.
40
+ const preserve = (el: HTMLElement) => ['style', 'data-mk-ready', 'aria-label'].forEach(name => scope.attr(el, name, el.getAttribute(name)));
41
+ preserve(root);
42
+ root.querySelectorAll<HTMLElement>('[data-mk-editorial-title],[data-mk-editorial-group],[data-mk-cascade],[data-mk-editorial-image],[data-mk-parallax-target],[data-mk-editorial-header],[data-mk-editorial-card-image],[data-mk-editorial-button-content]').forEach(preserve);
43
+ const titles = [...root.querySelectorAll<HTMLElement>('[data-mk-editorial-title]')].map(el => createMaskText(el, { by: 'lines', trigger, once: true, y: options.y ?? 110, duration, stagger, ease, delay: 0 }));
44
+ const groups = [...root.querySelectorAll<HTMLElement>('[data-mk-editorial-group]')].map(el => createCascade(el, { trigger, duration, step: stagger, once: true }));
45
+ const images = options.parallax === false ? [] : [...root.querySelectorAll<HTMLElement>('[data-mk-editorial-image]')].map(el => createParallax(el, { amount: .25 }));
46
+ const header = root.querySelector<HTMLElement>('[data-mk-editorial-header]');
47
+ const sections = [...root.querySelectorAll<HTMLElement>('[data-mk-tone]')];
48
+ const scopeId = String(++nextScope);
49
+ sections.forEach(el => scope.attr(el, 'data-mk-editorial-scope', scopeId));
50
+ const contrast = header ? createContrast(header, { sections: `[data-mk-editorial-scope="${scopeId}"]` }) : null;
51
+ scope.add(() => { titles.forEach(instance => instance.destroy()); groups.forEach(instance => instance.destroy()); images.forEach(instance => instance.destroy()); contrast?.destroy(); context.revert(); });
52
+ root.querySelectorAll<HTMLElement>('[data-mk-editorial-button]').forEach(button => {
53
+ const content = button.querySelector<HTMLElement>('[data-mk-editorial-button-content]');
54
+ if (!content) return;
55
+ let pointerInside = false;
56
+ const render = () => {
57
+ const active = pointerInside || button.contains(root.ownerDocument.activeElement);
58
+ context.add(() => gsap.to(content, { x: active && !reduced() ? `${options.buttonShift ?? 2.25}em` : '0em', duration: reduced() ? 0 : hoverDuration, ease, overwrite: true }));
59
+ };
60
+ scope.on(button, 'pointerenter', () => { pointerInside = true; render(); });
61
+ scope.on(button, 'pointerleave', () => { pointerInside = false; render(); });
62
+ scope.on(button, 'focusin', render);
63
+ scope.on(button, 'focusout', () => { scope.timeout(render, 0); });
64
+ });
65
+ const cursor = root.querySelector<HTMLElement>('[data-mk-editorial-cursor]');
66
+ if (cursor) { preserve(cursor); scope.attr(cursor, 'aria-hidden', 'true'); scope.attr(cursor, 'hidden', ''); }
67
+ root.querySelectorAll<HTMLElement>('[data-mk-editorial-card]').forEach(card => {
68
+ const image = card.querySelector<HTMLElement>('[data-mk-editorial-card-image]');
69
+ let pointerInside = false;
70
+ const render = () => {
71
+ const active = pointerInside || card.contains(root.ownerDocument.activeElement);
72
+ scope.attr(card, 'data-mk-editorial-active', active ? '' : null);
73
+ if (image) context.add(() => gsap.to(image, { scale: active && !reduced() ? options.imageScale ?? 1.04 : 1, duration: reduced() ? 0 : hoverDuration, ease, overwrite: true }));
74
+ };
75
+ scope.on(card, 'pointerenter', event => { pointerInside = true; render(); if (cursor && !reduced() && event.pointerType !== 'touch') scope.attr(cursor, 'hidden', null); });
76
+ scope.on(card, 'pointerleave', () => { pointerInside = false; render(); if (cursor) scope.attr(cursor, 'hidden', ''); });
77
+ scope.on(card, 'pointermove', event => {
78
+ if (!cursor || reduced() || event.pointerType === 'touch') return;
79
+ const rect = root.getBoundingClientRect();
80
+ cursor.style.transform = `translate(${event.clientX - rect.left + 16}px, ${event.clientY - rect.top + 16}px)`;
81
+ });
82
+ scope.on(card, 'focusin', render);
83
+ scope.on(card, 'focusout', () => { scope.timeout(render, 0); });
84
+ });
85
+ scope.attr(root, 'data-mk-ready', '');
86
+ return {
87
+ play() { if (!scope.disposed) { titles.forEach(instance => instance.play()); groups.forEach(instance => instance.play()); } },
88
+ reset() { if (!scope.disposed) { titles.forEach(instance => instance.hide()); groups.forEach(instance => instance.reset()); } },
89
+ destroy() { scope.dispose(); },
90
+ };
91
+ }
@@ -0,0 +1,17 @@
1
+ .mk-editorial-reveal { position: relative; isolation: isolate; color: var(--mk-text); }
2
+ .mk-editorial-reveal [data-mk-editorial-image], .mk-editorial-reveal [data-mk-editorial-card] { overflow: clip; border-radius: 12px; }
3
+ .mk-editorial-reveal [data-mk-editorial-image] > :is(img,video,picture) { display: block; width: 100%; height: 100%; object-fit: cover; }
4
+ .mk-editorial-reveal [data-mk-editorial-card] { display: grid; gap: 20px; padding: 24px; background: var(--mk-surface-2); color: inherit; text-decoration: none; transition: background-color .5s cubic-bezier(.6, 0, 0, 1), color .5s cubic-bezier(.6, 0, 0, 1); }
5
+ .mk-editorial-reveal [data-mk-editorial-card][data-mk-editorial-active] { background: var(--mk-accent); color: var(--mk-on-accent); }
6
+ .mk-editorial-reveal [data-mk-editorial-card-image] { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 8px; }
7
+ .mk-editorial-reveal [data-mk-editorial-button] { display: inline-flex; overflow: clip; align-items: center; padding: 12px 18px; border: 1px solid var(--mk-outline-soft); border-radius: 8px; background: var(--mk-text); color: var(--mk-surface); text-decoration: none; font: inherit; cursor: pointer; }
8
+ .mk-editorial-reveal [data-mk-editorial-button-content] { position: relative; display: flex; align-items: center; gap: 1em; padding-inline-end: 2.25em; }
9
+ .mk-editorial-reveal [data-mk-editorial-button-content]::before, .mk-editorial-reveal [data-mk-editorial-button-content]::after { content: '↗'; position: absolute; inset-block-start: 0; }
10
+ .mk-editorial-reveal [data-mk-editorial-button-content]::before { inset-inline-start: -2.25em; }
11
+ .mk-editorial-reveal [data-mk-editorial-button-content]::after { inset-inline-end: 0; }
12
+ .mk-editorial-reveal [data-mk-editorial-cursor] { pointer-events: none; position: absolute; z-index: 3; inset: 0 auto auto 0; width: max-content; height: max-content; padding: 10px 14px; border-radius: 30px; background: var(--mk-text); color: var(--mk-surface); font-size: .75rem; }
13
+ .mk-editorial-reveal [hidden] { display: none !important; }
14
+ .mk-editorial-reveal [data-mk-editorial-header][data-mk-on='dark'] { color: var(--mk-surface); }
15
+ .mk-editorial-reveal :is(a,button,input,textarea):focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 4px; }
16
+ .mk-editorial-reveal [data-mk-editorial-group] [data-mk-cascade='rise'] { position: relative; }
17
+ @media (prefers-reduced-motion: reduce) { .mk-editorial-reveal *, .mk-editorial-reveal *::after, .mk-editorial-reveal *::before { animation: none !important; transition: none !important; } }
@@ -0,0 +1,23 @@
1
+ # film-hero
2
+
3
+ Hero editorial de cine: líneas ascendentes, reglas que se trazan, imagen/video con escala y parallax dentro del contenedor de scroll. Componer con `action-button`, `details` y `trailer` para una ficha completa. La demo incluye créditos, premios, un selector de siguiente producción y medios locales originales.
4
+
5
+ ```html
6
+ <section class="mk-film-hero" data-mk="film-hero">
7
+ <h1 class="mk-film-hero__title">
8
+ <span class="mk-film-hero__mask"><span data-mk-film-line>Una historia</span></span>
9
+ </h1>
10
+ <span data-mk-film-rule></span>
11
+ <div class="mk-film-hero__visual">
12
+ <img class="mk-film-hero__media" src="/mi-imagen.jpg" alt="…" />
13
+ </div>
14
+ </section>
15
+ ```
16
+
17
+ `createFilmHero(root, { duration:.7, stagger:.08, parallax:.12, scale:1.08, edge:'organic', scroller })` → `replay()`, `refresh()`, `destroy()`. React: `useFilmHero(ref, options, deps?)`. Para demos/paneles con scroll, marcar el contenedor `data-mk-scroll-root` o pasar `scroller`; no requiere scroll global ni Lenis. `edge:'straight'` conserva el recorte rectangular.
18
+
19
+ Las líneas se marcan explícitamente; no cambia el HTML del título ni duplica su nombre accesible. Reduced-motion deja visibles texto/reglas y desactiva parallax. Sin JavaScript el contenido sigue visible. El observer, rAF pendiente, listeners, GSAP y estilos se revierten con `destroy()`.
20
+
21
+ El borde `organic` es un polígono CSS estático **aproximado**. El A24 original usa un algoritmo procedural con ruido, respuesta al cursor y disolución al scroll, además de un disco Three para la siguiente producción. Esta receta no incluye ese algoritmo ni afirma equivalencia. El disco CSS de la demo es otra aproximación decorativa. Las duraciones .7/.9s y stagger proceden de las fuentes inspeccionadas; `power3.out` reemplaza la curva custom del original. No incluye assets ni tipografías de A24.
22
+
23
+ Tokens: `--mk-film-ink`, `--mk-film-paper`, `--mk-film-font`; el motor escribe y restaura `--mk-film-shift`/`--mk-film-scale`.
@@ -0,0 +1,65 @@
1
+ import { gsap, registerMotion } from '../../core/gsap';
2
+ import { entrances, reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { inert, type Instance } from '../../core/types';
5
+
6
+ export interface FilmHeroOptions {
7
+ duration?: number;
8
+ stagger?: number;
9
+ /** Media travel as a fraction of the hero height. 0 disables parallax. */
10
+ parallax?: number;
11
+ scale?: number;
12
+ edge?: 'organic' | 'straight';
13
+ /** Scrollable demo panels and nested page shells can supply their own scroll root. */
14
+ scroller?: HTMLElement | Window;
15
+ }
16
+ export interface FilmHeroInstance extends Instance { replay(): void; refresh(): void; }
17
+
18
+ /** Editorial line reveals with scoped, scroll-driven media. Organic edge is an approximation. */
19
+ export function createFilmHero(root: HTMLElement | null, options: FilmHeroOptions = {}): FilmHeroInstance {
20
+ if (!root) return inert({ replay() {}, refresh() {} });
21
+ const scope = createScope();
22
+ scope.attr(root, 'data-mk-film-ready', '');
23
+ scope.attr(root, 'data-mk-film-edge', options.edge ?? 'organic');
24
+ scope.attr(root, 'style', root.getAttribute('style'));
25
+ const still = reduced();
26
+ const lines = [...root.querySelectorAll<HTMLElement>('[data-mk-film-line]')];
27
+ const rules = [...root.querySelectorAll<HTMLElement>('[data-mk-film-rule]')];
28
+ [...lines, ...rules].forEach(element => scope.attr(element, 'style', element.getAttribute('style')));
29
+ let timeline: gsap.core.Timeline | null = null;
30
+ const context = gsap.context(() => {}, root);
31
+ const replay = () => {
32
+ if (scope.disposed || !entrances()) return;
33
+ registerMotion();
34
+ context.add(() => {
35
+ timeline?.kill();
36
+ timeline = gsap.timeline();
37
+ if (lines.length) timeline.fromTo(lines, { yPercent: 120 }, { yPercent: 0, duration: options.duration ?? .7, stagger: options.stagger ?? .08, ease: 'power3.out' }, 0);
38
+ if (rules.length) timeline.fromTo(rules, { scaleX: 0, transformOrigin: 'left center' }, { scaleX: 1, duration: .9, stagger: .06, ease: 'power3.out' }, .08);
39
+ });
40
+ };
41
+ replay();
42
+ const scroller = options.scroller ?? root.closest<HTMLElement>('[data-mk-scroll-root]') ?? window;
43
+ let frame = 0;
44
+ const refresh = () => {
45
+ if (scope.disposed || still) return;
46
+ const rect = root.getBoundingClientRect();
47
+ const view = scroller instanceof HTMLElement ? scroller.getBoundingClientRect() : { top: 0, height: window.innerHeight };
48
+ const progress = Math.max(0, Math.min(1, (view.top - rect.top) / Math.max(1, rect.height)));
49
+ root.style.setProperty('--mk-film-shift', `${(progress * rect.height * (options.parallax ?? .12)).toFixed(2)}px`);
50
+ root.style.setProperty('--mk-film-scale', String((options.scale ?? 1.08) - progress * Math.max(0, (options.scale ?? 1.08) - 1)));
51
+ };
52
+ if (!still) {
53
+ const schedule = () => {
54
+ if (frame) return;
55
+ frame = requestAnimationFrame(() => { frame = 0; refresh(); });
56
+ };
57
+ scope.on(scroller, 'scroll', schedule, { passive: true });
58
+ scope.on(window, 'resize', schedule, { passive: true });
59
+ if (typeof ResizeObserver === 'function') scope.observe(new ResizeObserver(schedule)).observe(root);
60
+ scope.add(() => cancelAnimationFrame(frame));
61
+ refresh();
62
+ }
63
+ scope.add(() => { timeline?.kill(); context.revert(); });
64
+ return { replay, refresh, destroy: () => scope.dispose() };
65
+ }
@@ -0,0 +1,29 @@
1
+ .mk-film-hero {
2
+ --mk-film-ink: var(--mk-text, #191b18);
3
+ --mk-film-paper: var(--mk-surface, #f4f1e8);
4
+ --mk-film-font: Georgia, 'Times New Roman', serif;
5
+ position: relative;
6
+ isolation: isolate;
7
+ color: var(--mk-film-ink);
8
+ background: var(--mk-film-paper);
9
+ overflow: clip;
10
+ }
11
+ .mk-film-hero__credits { padding: clamp(20px, 5cqw, 64px); text-align: center; }
12
+ .mk-film-hero__eyebrow { font: 500 .65rem/1.5 var(--mk-font-mono, monospace); text-transform: uppercase; letter-spacing: .18em; }
13
+ .mk-film-hero__title { margin: .2em 0 .35em; font: 400 clamp(3.5rem, 10cqw, 8rem)/.82 var(--mk-film-font); letter-spacing: -.065em; text-transform: uppercase; }
14
+ .mk-film-hero__mask { display: block; overflow: clip; padding-bottom: .08em; margin-bottom: -.08em; }
15
+ [data-mk-film-line] { display: block; }
16
+ [data-mk-film-rule] { display: block; height: 1px; border: 0; background: currentColor; margin: 16px 0; }
17
+ .mk-film-hero__metadata { display: grid; gap: 0; margin: 24px 0 0; font: .8rem/1.5 var(--mk-font, sans-serif); }
18
+ .mk-film-hero__metadata > div { display: flex; align-items: baseline; justify-content: space-between; gap: 20px; padding: 8px 0; border-top: 1px solid currentColor; }
19
+ .mk-film-hero__metadata dt { font: .6rem/1.5 var(--mk-font-mono, monospace); text-transform: uppercase; letter-spacing: .14em; }
20
+ .mk-film-hero__metadata dd { margin: 0; text-align: right; }
21
+ .mk-film-hero__visual { position: relative; min-height: clamp(260px, 45cqw, 540px); display: grid; place-items: center; overflow: clip; color: #fffdf1; background: #243c36; }
22
+ .mk-film-hero__media { position: absolute; inset: -8% 0; width: 100%; height: 116%; object-fit: cover; transform: translateY(var(--mk-film-shift, 0px)) scale(var(--mk-film-scale, 1)); transform-origin: center; }
23
+ .mk-film-hero__visual::after { content: ''; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(transparent, #00120c66); }
24
+ .mk-film-hero__caption { position: relative; z-index: 1; padding: 48px 24px; text-align: center; }
25
+ .mk-film-hero__caption blockquote { font: 400 clamp(2.25rem, 6cqw, 5.5rem)/.95 var(--mk-film-font); letter-spacing: -.04em; margin: 0 0 24px; text-transform: uppercase; }
26
+ .mk-film-hero[data-mk-film-edge="organic"] .mk-film-hero__visual {
27
+ clip-path: polygon(0 7%, 9% 8%, 20% 5%, 32% 2%, 43% 0, 54% 2%, 68% 6%, 82% 8%, 93% 6%, 100% 4%, 100% 100%, 0 100%);
28
+ }
29
+ @media (prefers-reduced-motion: reduce) { .mk-film-hero__media { transform: none; } }
@@ -0,0 +1,11 @@
1
+ # font-cycle
2
+
3
+ Caras tipográficas alternadas por carácter, con copia enmascarada que entra al hover y al foco. Adaptación de la tipografía de Mat Voyce; el original también superpone GIFs. No incluye fuentes ni ilustraciones del sitio.
4
+
5
+ ```html
6
+ <button class="mk-font-cycle" data-mk="font-cycle" data-mk-font-alternate="Georgia, serif">Explore</button>
7
+ ```
8
+
9
+ `createFontCycle(root, { faces, text, duration: .6, stagger: .035, overlaySrc })` devuelve `play()`, `reset()`, `active`, `destroy()`. Duraciones en segundos, ease `power3.out`. `faces` es una tupla de `{ family?, axes?: { wght: 500, wdth: 75 }, weight?, style? }`; cada carácter alterna las dos caras. Los ejes que no admita la fuente no tienen efecto. `overlaySrc` (o `data-mk-font-overlay`) es una imagen decorativa propia, opcional.
10
+
11
+ React: `useFontCycle(ref, options, deps)`. La receta administra los hijos del elemento mientras está montada; no cambies sus hijos durante ese intervalo. Una copia accesible conserva el texto sin duplicar letras. El foco activa el mismo efecto que hover; con movimiento reducido el cambio es inmediato y no se muestra la imagen animada. `destroy()` restaura los nodos y atributos originales.
@@ -0,0 +1,126 @@
1
+ import { gsap, registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { createScope } from '../../core/scope';
4
+ import { splitGraphemes } from '../../core/split';
5
+ import { inert, type Instance } from '../../core/types';
6
+
7
+ export interface FontCycleFace {
8
+ /** A font supplied by the application. No font files are downloaded by this recipe. */
9
+ family?: string;
10
+ axes?: Readonly<Record<string, number>>;
11
+ weight?: number;
12
+ style?: 'normal' | 'italic' | 'oblique';
13
+ }
14
+
15
+ export interface FontCycleOptions {
16
+ text?: string;
17
+ /** Alternated per character; the second face rolls in on hover or focus. */
18
+ faces?: readonly [FontCycleFace, FontCycleFace];
19
+ /** Seconds per character. */
20
+ duration?: number;
21
+ /** Seconds between characters. */
22
+ stagger?: number;
23
+ /** Optional decorative artwork owned by the consumer. Hidden with reduced motion. */
24
+ overlaySrc?: string;
25
+ }
26
+
27
+ export interface FontCycleInstance extends Instance {
28
+ play(): void;
29
+ reset(): void;
30
+ readonly active: boolean;
31
+ }
32
+
33
+ function applyFace(element: HTMLElement, face: FontCycleFace) {
34
+ if (face.family) element.style.fontFamily = face.family;
35
+ if (face.weight !== undefined) element.style.fontWeight = String(face.weight);
36
+ if (face.style) element.style.fontStyle = face.style;
37
+ const axes = Object.entries(face.axes ?? {}).filter(([tag, value]) => /^[\w ]{4}$/.test(tag) && Number.isFinite(value));
38
+ if (axes.length) element.style.fontVariationSettings = axes.map(([tag, value]) => `"${tag}" ${value}`).join(', ');
39
+ }
40
+
41
+ /** Alternating variable-font faces in a masked, reversible character roll. */
42
+ export function createFontCycle(root: HTMLElement | null, options: FontCycleOptions = {}): FontCycleInstance {
43
+ if (!root || typeof document === 'undefined') return inert({ play() {}, reset() {}, active: false });
44
+ registerMotion();
45
+ const scope = createScope();
46
+ const original = [...root.childNodes];
47
+ const text = options.text ?? root.textContent ?? '';
48
+ const faces = options.faces ?? [
49
+ { weight: 500, axes: { wght: 500, wdth: 75 } },
50
+ { family: root.dataset.mkFontAlternate, weight: 900, axes: { wght: 900, wdth: 125 } },
51
+ ];
52
+ const duration = Math.max(0, options.duration ?? 0.6);
53
+ const stagger = Math.max(0, options.stagger ?? 0.035);
54
+ const tracks: HTMLElement[] = [];
55
+ let animation: gsap.core.Tween | undefined;
56
+ let active = false;
57
+ let hovered = false;
58
+ let focused = false;
59
+
60
+ const copy = document.createElement('span');
61
+ copy.className = 'mk-font-cycle__accessible';
62
+ copy.textContent = text;
63
+ const visual = document.createElement('span');
64
+ visual.className = 'mk-font-cycle__visual';
65
+ visual.setAttribute('aria-hidden', 'true');
66
+ for (const [index, letter] of splitGraphemes(text, Number.MAX_SAFE_INTEGER).entries()) {
67
+ const character = document.createElement('span');
68
+ character.className = 'mk-font-cycle__char';
69
+ const track = document.createElement('span');
70
+ track.className = 'mk-font-cycle__track';
71
+ for (let side = 0; side < 2; side++) {
72
+ const face = document.createElement('span');
73
+ face.className = 'mk-font-cycle__face';
74
+ face.textContent = letter === ' ' ? '\u00a0' : letter;
75
+ applyFace(face, faces[(index + side) % 2]!);
76
+ const sizer = face.cloneNode(true) as HTMLElement;
77
+ sizer.className = 'mk-font-cycle__sizer';
78
+ character.append(sizer);
79
+ track.append(face);
80
+ }
81
+ character.append(track);
82
+ visual.append(character);
83
+ tracks.push(track);
84
+ }
85
+ root.replaceChildren(copy, visual);
86
+ const overlaySrc = options.overlaySrc ?? root.dataset.mkFontOverlay;
87
+ if (overlaySrc) {
88
+ const overlay = document.createElement('img');
89
+ overlay.className = 'mk-font-cycle__overlay';
90
+ overlay.alt = '';
91
+ overlay.setAttribute('aria-hidden', 'true');
92
+ scope.attr(root, 'data-mk-font-overlay-ready', null);
93
+ const loaded = () => {
94
+ if (!scope.disposed) scope.attr(root, 'data-mk-font-overlay-ready', overlay.naturalWidth > 0 ? '' : null);
95
+ };
96
+ scope.on(overlay, 'load', loaded);
97
+ scope.on(overlay, 'error', () => {
98
+ if (!scope.disposed) scope.attr(root, 'data-mk-font-overlay-ready', null);
99
+ });
100
+ overlay.src = overlaySrc;
101
+ root.append(overlay);
102
+ if (overlay.complete) loaded();
103
+ }
104
+ scope.add(() => root.replaceChildren(...original));
105
+ scope.attr(root, 'class', `${root.className} mk-font-cycle`.trim());
106
+ scope.attr(root, 'data-mk-ready', '');
107
+
108
+ const setActive = (next: boolean) => {
109
+ if (scope.disposed || next === active) return;
110
+ active = next;
111
+ scope.attr(root, 'data-mk-active', next ? '' : null);
112
+ animation?.kill();
113
+ if (reduced() || duration === 0) gsap.set(tracks, { yPercent: next ? -50 : 0 });
114
+ else animation = gsap.to(tracks, { yPercent: next ? -50 : 0, duration, stagger, ease: 'power3.out', overwrite: true });
115
+ };
116
+ scope.on(root, 'mouseenter', () => { hovered = true; setActive(true); });
117
+ scope.on(root, 'mouseleave', () => { hovered = false; setActive(focused); });
118
+ scope.on(root, 'focusin', () => { focused = true; setActive(true); });
119
+ scope.on(root, 'focusout', (event) => {
120
+ if (event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;
121
+ focused = false;
122
+ setActive(hovered);
123
+ });
124
+ scope.add(() => animation?.kill());
125
+ return { play: () => setActive(true), reset: () => setActive(false), get active() { return active; }, destroy: () => scope.dispose() };
126
+ }
@@ -0,0 +1,36 @@
1
+ .mk-font-cycle {
2
+ position: relative;
3
+ display: inline-block;
4
+ color: inherit;
5
+ font-family: var(--mk-font, sans-serif);
6
+ isolation: isolate;
7
+ }
8
+ .mk-font-cycle__accessible {
9
+ position: absolute;
10
+ width: 1px;
11
+ height: 1px;
12
+ padding: 0;
13
+ overflow: hidden;
14
+ clip-path: inset(50%);
15
+ white-space: nowrap;
16
+ }
17
+ .mk-font-cycle__visual { display: inline-flex; white-space: pre; }
18
+ .mk-font-cycle__char {
19
+ position: relative;
20
+ display: inline-grid;
21
+ overflow: hidden;
22
+ line-height: 1.12;
23
+ vertical-align: bottom;
24
+ }
25
+ .mk-font-cycle__sizer { grid-area: 1 / 1; visibility: hidden; padding-inline: .035em; }
26
+ .mk-font-cycle__track { position: absolute; inset: 0 0 auto; display: flex; flex-direction: column; will-change: transform; }
27
+ .mk-font-cycle__face { display: block; flex: none; text-align: center; padding-inline: .035em; }
28
+ .mk-font-cycle__overlay { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; pointer-events: none; opacity: 0; }
29
+ .mk-font-cycle[data-mk-active][data-mk-font-overlay-ready] .mk-font-cycle__overlay { opacity: 1; }
30
+ .mk-font-cycle[data-mk-active][data-mk-font-overlay-ready] .mk-font-cycle__visual { opacity: 0; }
31
+ .mk-font-cycle:focus-visible { outline: 2px solid var(--mk-accent, currentColor); outline-offset: .15em; }
32
+ @media (prefers-reduced-motion: reduce) {
33
+ .mk-font-cycle__track { will-change: auto; }
34
+ .mk-font-cycle[data-mk-active][data-mk-font-overlay-ready] .mk-font-cycle__visual { opacity: 1; }
35
+ .mk-font-cycle[data-mk-active][data-mk-font-overlay-ready] .mk-font-cycle__overlay { display: none; }
36
+ }
@@ -0,0 +1,16 @@
1
+ # image-zoom
2
+
3
+ Contrazoom inspirado en las categorías de [Bernice](https://bernicebakery.com/collections/cakes). Marco 1→1.1 e imagen 1.2→1 en 1 s con cubic-bezier(.28,.71,0,.98).
4
+
5
+ ```html
6
+ <a class="mk-image-zoom" data-mk="image-zoom" href="/collection">
7
+ <span data-mk-zoom-frame style="border-radius:50%;width:10rem;height:10rem">
8
+ <img src="/image.jpg" width="400" height="400" alt="Descripción" />
9
+ </span>
10
+ <strong>Colección</strong>
11
+ </a>
12
+ ```
13
+
14
+ `createImageZoom(root,{duration:1,frameScale:1.1,imageScale:1.2})`, `autoInit()`, `useImageZoom(ref,options,deps)`. API `open(boolean)`, `active`, `destroy()`.
15
+
16
+ Hover/focus revelan el mismo efecto. Root puede contener imagen, video o picture dentro de frame. Para marcado distinto usar `data-mk-zoom-image` en el elemento que escala (evitar marcar también su padre). Variables: `--mk-zoom-duration`, `--mk-zoom-frame-scale`, `--mk-zoom-image-scale`, `--mk-zoom-ease`. Sin transformaciones bajo movimiento reducido; las imágenes siguen accesibles.
@@ -0,0 +1,27 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ export interface ImageZoomOptions { duration?: number; frameScale?: number; imageScale?: number; }
5
+ export interface ImageZoomInstance extends Instance { open(active: boolean): void; readonly active: boolean; }
6
+
7
+ /** Counter-zoom: the clipped frame grows as the picture inside settles to 1×. */
8
+ export function createImageZoom(root: HTMLElement | null, options: ImageZoomOptions = {}): ImageZoomInstance {
9
+ if (!root) return inert({ open() {}, active: false });
10
+ const scope = createScope();
11
+ let active = false;
12
+ let pointer = false;
13
+ scope.attr(root, 'style', root.getAttribute('style'));
14
+ scope.attr(root, 'data-mk-ready', '');
15
+ const setVariable = (name: string, value: number | undefined, suffix = '') => {
16
+ if (Number.isFinite(value)) root.style.setProperty(name, `${Math.max(0, value!)}${suffix}`);
17
+ };
18
+ setVariable('--mk-zoom-duration', options.duration, 's');
19
+ setVariable('--mk-zoom-frame-scale', options.frameScale);
20
+ setVariable('--mk-zoom-image-scale', options.imageScale);
21
+ const set = (on: boolean) => { if (!scope.disposed) { active = on; scope.attr(root, 'data-mk-active', on ? '' : null); } };
22
+ scope.on(root, 'pointerenter', () => { pointer = true; set(true); });
23
+ scope.on(root, 'pointerleave', () => { pointer = false; if (!root.contains(document.activeElement)) set(false); });
24
+ scope.on(root, 'focusin', () => set(true));
25
+ scope.on(root, 'focusout', (event) => { if (!pointer && !root.contains((event as FocusEvent).relatedTarget as Node | null)) set(false); });
26
+ return { open: set, get active() { return active; }, destroy: () => scope.dispose() };
27
+ }
@@ -0,0 +1,7 @@
1
+ .mk-image-zoom { --mk-zoom-duration: 1s; --mk-zoom-frame-scale: 1.1; --mk-zoom-image-scale: 1.2; --mk-zoom-ease: cubic-bezier(.28,.71,0,.98); color: inherit; text-decoration: none; }
2
+ .mk-image-zoom [data-mk-zoom-frame] { display: block; overflow: hidden; border-radius: inherit; transition: transform var(--mk-zoom-duration) var(--mk-zoom-ease); }
3
+ .mk-image-zoom [data-mk-zoom-frame] > :is(img,video,picture), .mk-image-zoom [data-mk-zoom-image] { display: block; width: 100%; height: 100%; object-fit: cover; transform: scale(var(--mk-zoom-image-scale)); transition: transform var(--mk-zoom-duration) var(--mk-zoom-ease); }
4
+ .mk-image-zoom:is(:hover,:focus-within,[data-mk-active]) [data-mk-zoom-frame] { transform: scale(var(--mk-zoom-frame-scale)); }
5
+ .mk-image-zoom:is(:hover,:focus-within,[data-mk-active]) [data-mk-zoom-frame] > :is(img,video,picture), .mk-image-zoom:is(:hover,:focus-within,[data-mk-active]) [data-mk-zoom-image] { transform: scale(1); }
6
+ .mk-image-zoom:focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 5px; }
7
+ @media (prefers-reduced-motion: reduce) { .mk-image-zoom [data-mk-zoom-frame], .mk-image-zoom [data-mk-zoom-frame] > :is(img,video,picture), .mk-image-zoom [data-mk-zoom-image] { transform: none !important; transition: none; } }
@@ -0,0 +1,13 @@
1
+ # joined-cta
2
+
3
+ CTA inspirado en [Silvia Sguotti](https://silviasguotti.design/): tres cápsulas que se desplazan, giran y se unen. El CSS público declara .5 s ease-in-out.
4
+
5
+ ```html
6
+ <button type="button" class="mk-joined-cta" data-mk="joined-cta" aria-label="Keep in touch">
7
+ <span>KEEP</span><span>IN</span><span>TOUCH</span>
8
+ </button>
9
+ ```
10
+
11
+ `createJoinedCta(root,{duration:.5})`, `autoInit()`, o `useJoinedCta(ref,options,deps)`. Se puede usar un enlace real en lugar del botón. API `expand()`, `collapse()`, `expanded`, `destroy()`.
12
+
13
+ Hover y foco equivalentes. En touch o pantallas <=420px se presenta una cápsula legible sin giro. Movimiento reducido conserva el cambio de estado instantáneo. Variables: `--mk-joined-duration`, `--mk-joined-bg`, `--mk-joined-color`. El contenido debe tener exactamente tres spans. No incluye navegación ni envío de correo.
@@ -0,0 +1,22 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ export interface JoinedCtaOptions { duration?: number; }
5
+ export interface JoinedCtaInstance extends Instance { expand(): void; collapse(): void; readonly expanded: boolean; }
6
+
7
+ /** Silvia's three contact capsules joining into one. Motion is entirely CSS. */
8
+ export function createJoinedCta(root: HTMLElement | null, options: JoinedCtaOptions = {}): JoinedCtaInstance {
9
+ if (!root) return inert({ expand() {}, collapse() {}, expanded: false });
10
+ const scope = createScope();
11
+ let expanded = false;
12
+ let pointer = false;
13
+ scope.attr(root, 'style', root.getAttribute('style'));
14
+ scope.attr(root, 'data-mk-ready', '');
15
+ if (Number.isFinite(options.duration)) root.style.setProperty('--mk-joined-duration', `${Math.max(0, options.duration!)}s`);
16
+ const set = (value: boolean) => { if (!scope.disposed) { expanded = value; scope.attr(root, 'data-mk-expanded', value ? '' : null); } };
17
+ scope.on(root, 'pointerenter', () => { pointer = true; set(true); });
18
+ scope.on(root, 'pointerleave', () => { pointer = false; if (!root.contains(document.activeElement)) set(false); });
19
+ scope.on(root, 'focusin', () => set(true));
20
+ scope.on(root, 'focusout', (event) => { if (!pointer && !root.contains((event as FocusEvent).relatedTarget as Node | null)) set(false); });
21
+ return { expand: () => set(true), collapse: () => set(false), get expanded() { return expanded; }, destroy: () => scope.dispose() };
22
+ }
@@ -0,0 +1,17 @@
1
+ .mk-joined-cta { --mk-joined-duration: .5s; --mk-joined-bg: var(--mk-text); --mk-joined-color: var(--mk-bg); position: relative; display: inline-flex; align-items: center; justify-content: center; width: 17.7rem; height: 5.9rem; max-width: 100%; border: 0; padding: 0; background: transparent; text-decoration: none; color: var(--mk-joined-color); cursor: pointer; font: inherit; isolation: isolate; border-radius: var(--mk-radius-full); }
2
+ .mk-joined-cta::before { content: ''; position: absolute; left: 0; width: 7.3rem; height: 5.9rem; border-radius: 100px; background: var(--mk-joined-bg); z-index: -1; transition: width var(--mk-joined-duration) ease-in-out; }
3
+ .mk-joined-cta > span { position: relative; display: grid; place-items: center; flex-shrink: 0; background: var(--mk-joined-bg); border-radius: 100px; height: 5.9rem; transition: left var(--mk-joined-duration) ease-in-out, height var(--mk-joined-duration) ease-in-out, transform var(--mk-joined-duration) ease-in-out, background var(--mk-joined-duration); }
4
+ .mk-joined-cta > span:nth-child(1) { width: 7.3rem; left: 0; }
5
+ .mk-joined-cta > span:nth-child(2) { width: 4.5rem; left: 0; z-index: 2; }
6
+ .mk-joined-cta > span:nth-child(3) { width: 5.9rem; height: 3.05rem; left: -1.45rem; transform: rotate(-90deg); }
7
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded])::before { width: 17.7rem; }
8
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(1) { left: 3.2rem; }
9
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(2) { left: -.75rem; background: transparent; }
10
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(3) { left: -3.65rem; height: 5.9rem; transform: rotate(0); }
11
+ .mk-joined-cta:focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 5px; }
12
+ @media (hover: none), (max-width: 420px) {
13
+ .mk-joined-cta { width: auto; height: auto; padding: 1.25rem 2rem; gap: .3em; background: var(--mk-joined-bg); }
14
+ .mk-joined-cta::before { display: none; }
15
+ .mk-joined-cta > span:nth-child(n) { width: auto; height: auto; left: 0; transform: none; background: transparent; }
16
+ }
17
+ @media (prefers-reduced-motion: reduce) { .mk-joined-cta::before, .mk-joined-cta > span { transition: none; } }
@@ -0,0 +1,31 @@
1
+ # local-clock
2
+
3
+ La hora de otra zona en vivo, como el «20:48 local time» del contacto de DOVV Studio. Se actualiza justo al cambiar el minuto (o el segundo, con `seconds`). En un `<time>` también mantiene el atributo `datetime`.
4
+
5
+ ## Vanilla / Astro
6
+
7
+ ```html
8
+ <p><span class="mk-status__dot"></span> <time data-mk="local-clock" data-mk-time-zone="America/Mexico_City">--:--</time> hora local</p>
9
+ ```
10
+
11
+ Con `autoInit()` las opciones van como `data-mk-<opción>`. Sin `autoInit`: `createLocalClock(el, opciones)`, que devuelve `text` y `destroy()`. El contenido inicial (por ejemplo `--:--`) se ve sin JavaScript y vuelve con `destroy()`.
12
+
13
+ ## React
14
+
15
+ ```tsx
16
+ const ref = useRef<HTMLTimeElement>(null);
17
+ useLocalClock(ref, { timeZone: 'America/Argentina/Buenos_Aires' });
18
+ return <time ref={ref}>--:--</time>;
19
+ ```
20
+
21
+ ## Opciones
22
+
23
+ | Opción | Por defecto | Qué hace |
24
+ |---|---|---|
25
+ | `timeZone` | la del visitante | Zona IANA |
26
+ | `locale` | `'es'` | Idioma del formato |
27
+ | `hour12` | `false` | 12 h con a. m./p. m. |
28
+ | `seconds` | `false` | Mostrar y actualizar segundos |
29
+ | `format` | — | `Intl.DateTimeFormatOptions` completo |
30
+
31
+ Una zona o un idioma desconocidos usan los del visitante en lugar de fallar. No anuncia cada cambio a lectores de pantalla.
@@ -0,0 +1,61 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ /*
5
+ * Local clock (DOVV Studio contact: "20:48 local time"): the time in a given
6
+ * zone, refreshed on the minute (or the second). A <time> element also gets
7
+ * its datetime attribute. The text node is replaced; destroy() puts it back.
8
+ */
9
+
10
+ export interface LocalClockOptions {
11
+ /** IANA zone, e.g. "America/Mexico_City". The visitor's zone by default. */
12
+ timeZone?: string;
13
+ locale?: string;
14
+ hour12?: boolean;
15
+ seconds?: boolean;
16
+ /** Full control over the format (overrides hour12/seconds). */
17
+ format?: Intl.DateTimeFormatOptions;
18
+ }
19
+
20
+ export interface LocalClockInstance extends Instance {
21
+ /** Text currently shown. */
22
+ readonly text: string;
23
+ }
24
+
25
+ export function createLocalClock(root: HTMLElement | null, options: LocalClockOptions = {}): LocalClockInstance {
26
+ if (!root) return inert({ text: '' });
27
+ const scope = createScope();
28
+ const original = [...root.childNodes];
29
+ scope.add(() => root.replaceChildren(...original));
30
+ scope.attr(root, 'data-mk-ready', '');
31
+ let formatter: Intl.DateTimeFormat;
32
+ try {
33
+ formatter = new Intl.DateTimeFormat(options.locale ?? 'es', options.format ?? {
34
+ hour: '2-digit',
35
+ minute: '2-digit',
36
+ ...(options.seconds ? { second: '2-digit' } : {}),
37
+ hour12: options.hour12 ?? false,
38
+ timeZone: options.timeZone,
39
+ });
40
+ } catch {
41
+ // Unknown zone or locale: the visitor's own.
42
+ formatter = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', hour12: options.hour12 ?? false });
43
+ }
44
+ let text = '';
45
+ const isTime = root instanceof HTMLTimeElement;
46
+ const render = () => {
47
+ const now = new Date();
48
+ text = formatter.format(now);
49
+ if (root.textContent !== text) root.textContent = text;
50
+ if (isTime) scope.attr(root, 'datetime', now.toISOString());
51
+ };
52
+ const step = options.seconds || options.format?.second ? 1000 : 60000;
53
+ const schedule = () => {
54
+ // Land just after the next boundary so the minute flips with the system clock.
55
+ const wait = step - (Date.now() % step) + 20;
56
+ scope.timeout(() => { if (!scope.disposed) { render(); schedule(); } }, wait);
57
+ };
58
+ render();
59
+ schedule();
60
+ return { get text() { return text; }, destroy: () => scope.dispose() };
61
+ }