@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,35 @@
1
+ # editorial-reveal
2
+
3
+ Composición de títulos enmascarados, tarjetas, CTA con flecha duplicada, cursor decorativo y grupos de contacto escalonados. Referencia conductual: CoMinVi. Reutiliza `mask-text`, `cascade`, `parallax` y `contrast` sin copiar assets ni código de la referencia.
4
+
5
+ ```html
6
+ <section class="mk-editorial-reveal" data-mk="editorial-reveal">
7
+ <header data-mk-editorial-header>Estudio</header>
8
+ <section data-mk-tone="light">
9
+ <h1 data-mk-editorial-title>Un nuevo punto de vista.</h1>
10
+ <a href="/story" data-mk-editorial-card>
11
+ <img data-mk-editorial-card-image src="/your-image.webp" alt="Paisaje">
12
+ <h2>Explorar la historia</h2>
13
+ </a>
14
+ <a href="/story" data-mk-editorial-button><span data-mk-editorial-button-content>Leer</span></a>
15
+ </section>
16
+ <div data-mk-editorial-group>
17
+ <h2 data-mk-cascade="rise">Conversemos</h2>
18
+ <label data-mk-cascade="fade">Correo <input type="email"></label>
19
+ </div>
20
+ <span data-mk-editorial-cursor hidden>Leer ↗</span>
21
+ </section>
22
+ ```
23
+
24
+ ```ts
25
+ import { createEditorialReveal } from '@yewdiel21/motion-kit';
26
+ import { useEditorialReveal } from '@yewdiel21/motion-kit/react';
27
+ const motion = createEditorialReveal(root, { trigger: 'manual' });
28
+ motion.play(); motion.reset(); motion.destroy();
29
+ ```
30
+
31
+ Opciones: `trigger` (`scroll` por defecto, `load`, `manual`), `duration` 1.2 s, `stagger` .03 s, `y` 110 %, `ease` curva `(0.6,0,0,1)`, `hoverDuration` .5 s, `buttonShift` 2.25 em, `imageScale` 1.04 y `parallax` true. Los grupos mantienen el easing `expo.out` de `cascade`; la curva de referencia se aplica a títulos, CTA e imágenes. La entrada se reproduce una vez. `reset()` permite repetirla manualmente.
32
+
33
+ Marca un marco opcional con `[data-mk-editorial-image]` y su hijo con `[data-mk-parallax-target]` para un parallax de intensidad .25. El header observa únicamente las secciones de su propia instancia. El estado de tarjeta se activa tanto con puntero como con foco; el cursor es decorativo, se oculta en touch y con movimiento reducido, y nunca sustituye al cursor del sistema. Sin JavaScript el contenido sigue visible.
34
+
35
+ Con movimiento reducido el título queda entero, las entradas no ocultan campos y las interacciones conservan sus estados sin desplazamiento. `destroy()` es idempotente y restaura atributos/estilos propios y los helpers. El formulario, navegación y assets son responsabilidad de quien compone el contenido.
@@ -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; } }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Un texto cuyas letras empiezan al 20 % y se rellenan una tras otra ligadas al scroll: desde que el borde superior del texto llega al 90 % de la pantalla hasta que su centro llega al 40 % (cada letra 0.5 s de una línea de tiempo lineal, 0.1 s entre letras).
4
4
 
5
- Origen: iyo.ai, módulo `fill-text` (`GM`): SplitText `words, chars`, `autoAlpha .2 → 1`, `stagger .1`, `ease: linear`, ScrollTrigger con `scrub` de `top 90%` a `center 40%`. Aquí sigue al scroll con `trackScroll` y lee el texto completo a los lectores de pantalla (iyo usaba `aria: none`).
5
+ Divide el texto en palabras y caracteres: opacidad .2 → 1, stagger .1 y avance de scroll entre `top 90%` y `center 40%`. El texto completo sigue accesible a lectores de pantalla.
6
6
 
7
7
  ## HTML / Astro
8
8
 
@@ -5,7 +5,7 @@ import { TIMING } from '../../core/timing';
5
5
  import { inert, warnOnce, type Instance } from '../../core/types';
6
6
 
7
7
  /*
8
- * iyo.ai's fill-text: the letters start at 20 % and fill in one after another,
8
+ * fill-text: the letters start at 20 % and fill in one after another,
9
9
  * tied to the scroll, from when the text's top reaches 90 % of the screen until
10
10
  * its centre reaches 40 % (each letter 0.5 s of a linear timeline, 0.1 s apart).
11
11
  */
@@ -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; } }
@@ -2,8 +2,6 @@
2
2
 
3
3
  Ajusta el tamaño de una línea de texto para que ocupe exactamente el ancho de contenido de su padre; se reajusta al cambiar el ancho.
4
4
 
5
- Origen: portfolio — "Portfolio" y "Contacto" a ancho completo.
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -2,8 +2,6 @@
2
2
 
3
3
  Los hijos del contenedor caen desde 20 px arriba, desenfocados 16 px, en 0.5 s y con 50 ms entre ellos.
4
4
 
5
- Origen: Stepbro — flowIn (apps Tasks y Money).
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -3,7 +3,7 @@ import { flowIn, type FlowInOptions } from '../../motion/flow';
3
3
  import { entrances } from '../../core/reduced';
4
4
  import { inert, warnOnce, type Instance } from '../../core/types';
5
5
 
6
- /* Stepbro flowIn on a container: its children drop in from 20 px above, blurred 16 px, 0.5 s, 50 ms apart. */
6
+ /* flowIn on a container: its children drop in from 20 px above, blurred 16 px, 0.5 s, 50 ms apart. */
7
7
 
8
8
  export interface FlowOptions extends FlowInOptions {
9
9
  trigger?: 'scroll' | 'load' | 'manual';
@@ -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
+ }
@@ -1,8 +1,6 @@
1
1
  # frame-zoom
2
2
 
3
- Un marco que se cierra como una ventana mientras cruza la pantalla: entra completo por abajo, a media pantalla va al 90 % y al irse por arriba llega al 80 %, con la imagen de dentro creciendo al revés (110 % → 130 %; en el móvil 95–100 % y 105–110 %). Sigue al scroll de la página sin retraso. `open(true)` lo devuelve completo (1 s `expo.out`), como el vídeo de Siena al abrirse.
4
-
5
- Origen: Siena Film Foundation, máscara del vídeo de película (marco `e·20 + 80 %`, imagen `130 − e·20 %`, `e = 1 − progreso` del rastreador de Siena, que va de `top bottom` a `bottom top`).
3
+ Un marco que se cierra como una ventana mientras cruza la pantalla: entra completo por abajo, a media pantalla va al 90 % y al irse por arriba llega al 80 %, con la imagen de dentro creciendo al revés (110 % → 130 %; en el móvil 95–100 % y 105–110 %). Sigue al scroll de la página sin retraso. `open(true)` lo devuelve completo (1 s `expo.out`).
6
4
 
7
5
  ## HTML / Astro
8
6
 
@@ -5,11 +5,11 @@ import { TIMING } from '../../core/timing';
5
5
  import { inert, warnOnce, type Instance } from '../../core/types';
6
6
 
7
7
  /*
8
- * Siena's video mask: the frame and the picture inside scale in opposite
8
+ * video mask: the frame and the picture inside scale in opposite
9
9
  * directions. With e = (1 − progress)·(1 − open) + open, the frame is
10
10
  * (80 + 20e) % and the picture (130 − 20e) % (phones: 95 + 5e and 105 − 5e):
11
11
  * full as it enters at the bottom, it closes like a window while it crosses the
12
- * screen (Siena's tracker: top bottom → bottom top). open(true) brings it back
12
+ * screen. open(true) brings it back
13
13
  * to full (1 s expo.out).
14
14
  */
15
15
 
@@ -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; } }
@@ -1,6 +1,6 @@
1
1
  # infinite-reel
2
2
 
3
- Reel infinito horizontal o vertical con rueda, arrastre e inercia (Siena).
3
+ Reel infinito horizontal o vertical con rueda, arrastre e inercia.
4
4
 
5
5
  ```html
6
6
  <div class="mk-infinite-reel" data-mk="infinite-reel" aria-label="Películas">
@@ -16,8 +16,8 @@ const instance = createInfiniteReel(root);
16
16
  instance.destroy();
17
17
  ```
18
18
 
19
- Opciones: `axis: 'x' | 'y'` (x), `speed` px/frame (0), `friction` (.95). `moveBy(px)`, `pause()`, `resume()`, `position`. Rueda, arrastre, flechas y Espacio cuando tiene foco. Copias con aria-hidden/inert y sin IDs. Movimiento reducido: scroll nativo sin copias, ticker ni inercia. Origen: Siena `siena.reel.horizontal/vertical`; adaptación DOM sin snap ni shaders del reel original.
19
+ Opciones: `axis: 'x' | 'y'` (x), `speed` px/frame (0), `friction` (.95). `moveBy(px)`, `pause()`, `resume()`, `position`. Rueda, arrastre, flechas y Espacio cuando tiene foco. Copias con aria-hidden/inert y sin IDs. Movimiento reducido: scroll nativo sin copias, ticker ni inercia.
20
20
 
21
21
  Vanilla declarativo: `autoInit(root)`. React: `useInfiniteReel(ref, options, deps)` devuelve una ref a la instancia; se destruye al desmontar. Con movimiento reducido no se ejecuta movimiento decorativo. `destroy()` restaura atributos y estilos y quita listeners, observers y nodos generados.
22
22
 
23
- Desde 0.8.0. Parámetros investigados en `REA - Dousan OFFFICE Siena/`; implementación propia, sin código ni assets de esos sitios.
23
+ Desde 0.8.0.
@@ -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.