@yewdiel21/motion-kit 0.0.0-stage → 0.9.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 (391) hide show
  1. package/NOTICE +38 -0
  2. package/README.md +80 -2
  3. package/bin/motion-kit.mjs +242 -0
  4. package/css.d.ts +2 -0
  5. package/dist/auto/index.d.ts +63 -0
  6. package/dist/auto/index.js +207 -0
  7. package/dist/auto/index.js.map +1 -0
  8. package/dist/chunk-2BQGFOA7.js +177 -0
  9. package/dist/chunk-2BQGFOA7.js.map +1 -0
  10. package/dist/chunk-34XYOXHG.js +2374 -0
  11. package/dist/chunk-34XYOXHG.js.map +1 -0
  12. package/dist/chunk-6OJAP6ZL.js +832 -0
  13. package/dist/chunk-6OJAP6ZL.js.map +1 -0
  14. package/dist/chunk-ABNRDDO7.js +28 -0
  15. package/dist/chunk-ABNRDDO7.js.map +1 -0
  16. package/dist/chunk-FS2HJVVY.js +3320 -0
  17. package/dist/chunk-FS2HJVVY.js.map +1 -0
  18. package/dist/chunk-J27KH2A2.js +157 -0
  19. package/dist/chunk-J27KH2A2.js.map +1 -0
  20. package/dist/chunk-KLY5BRRI.js +18 -0
  21. package/dist/chunk-KLY5BRRI.js.map +1 -0
  22. package/dist/chunk-Q4LVFNLN.js +1617 -0
  23. package/dist/chunk-Q4LVFNLN.js.map +1 -0
  24. package/dist/chunk-RFDIZYFX.js +304 -0
  25. package/dist/chunk-RFDIZYFX.js.map +1 -0
  26. package/dist/chunk-TGV27ZWM.js +17 -0
  27. package/dist/chunk-TGV27ZWM.js.map +1 -0
  28. package/dist/confetti-BSX1CLLV.d.ts +41 -0
  29. package/dist/css/components/chat-background.css +87 -0
  30. package/dist/css/components/chat.css +237 -0
  31. package/dist/css/components/cursor.css +178 -0
  32. package/dist/css/components/dialog.css +259 -0
  33. package/dist/css/components/island.css +301 -0
  34. package/dist/css/components/menu.css +105 -0
  35. package/dist/css/components/nav.css +1054 -0
  36. package/dist/css/components/steps.css +50 -0
  37. package/dist/css/components/theme-picker.css +115 -0
  38. package/dist/css/components/toast.css +115 -0
  39. package/dist/css/controls.css +950 -0
  40. package/dist/css/effects.css +587 -0
  41. package/dist/css/fonts.css +3 -0
  42. package/dist/css/themes/black-green.css +126 -0
  43. package/dist/css/themes/black.css +126 -0
  44. package/dist/css/themes/blue.css +126 -0
  45. package/dist/css/themes/brown.css +126 -0
  46. package/dist/css/themes/cold-blue.css +126 -0
  47. package/dist/css/themes/funny.css +126 -0
  48. package/dist/css/themes/gray.css +126 -0
  49. package/dist/css/themes/green.css +126 -0
  50. package/dist/css/themes/modern-1.css +126 -0
  51. package/dist/css/themes/neogreen.css +126 -0
  52. package/dist/css/themes/new.css +126 -0
  53. package/dist/css/themes/new2.css +126 -0
  54. package/dist/css/themes/oled.css +126 -0
  55. package/dist/css/themes/orange.css +126 -0
  56. package/dist/css/themes/pastel-pink.css +126 -0
  57. package/dist/css/themes/pink.css +126 -0
  58. package/dist/css/themes/purple.css +126 -0
  59. package/dist/css/themes/red.css +126 -0
  60. package/dist/css/themes/super-blue.css +126 -0
  61. package/dist/css/themes.css +2431 -0
  62. package/dist/css/tokens.css +452 -0
  63. package/dist/elements/index.d.ts +183 -0
  64. package/dist/elements/index.js +472 -0
  65. package/dist/elements/index.js.map +1 -0
  66. package/dist/engine-CUr9QijM.d.ts +46 -0
  67. package/dist/gl/index.d.ts +61 -0
  68. package/dist/gl/index.js +18 -0
  69. package/dist/gl/index.js.map +1 -0
  70. package/dist/index.css +5296 -0
  71. package/dist/index.d.ts +440 -0
  72. package/dist/index.js +390 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/react/index.d.ts +476 -0
  75. package/dist/react/index.js +969 -0
  76. package/dist/react/index.js.map +1 -0
  77. package/dist/reduced-BM4Ls0g5.d.ts +566 -0
  78. package/dist/reveal-WFUWDrei.d.ts +416 -0
  79. package/dist/store-Bqy_obnD.d.ts +195 -0
  80. package/dist/theme/generate.d.ts +18 -0
  81. package/dist/theme/generate.js +64 -0
  82. package/dist/theme/generate.js.map +1 -0
  83. package/dist/theme/index.d.ts +17 -0
  84. package/dist/theme/index.js +32 -0
  85. package/dist/theme/index.js.map +1 -0
  86. package/dist/theme-BFMoCzi6.d.ts +158 -0
  87. package/dist/types-BkS_raL8.d.ts +7 -0
  88. package/package.json +114 -3
  89. package/registry.json +469 -0
  90. package/skills/motion-kit/SKILL.md +189 -0
  91. package/skills/motion-kit/reference/components.md +86 -0
  92. package/skills/motion-kit/reference/design.md +92 -0
  93. package/skills/motion-kit/reference/recipes.md +172 -0
  94. package/src/auto/auto-init.ts +151 -0
  95. package/src/auto/index.ts +3 -0
  96. package/src/auto/parse-options.ts +68 -0
  97. package/src/components/chat/README.md +90 -0
  98. package/src/components/chat/chat.css +236 -0
  99. package/src/components/chat/element.ts +98 -0
  100. package/src/components/chat/engine.ts +295 -0
  101. package/src/components/chat/react.tsx +196 -0
  102. package/src/components/chat-background/README.md +50 -0
  103. package/src/components/chat-background/audio.ts +56 -0
  104. package/src/components/chat-background/chat-background.css +86 -0
  105. package/src/components/chat-background/element.ts +31 -0
  106. package/src/components/chat-background/engine.ts +108 -0
  107. package/src/components/chat-background/react.tsx +36 -0
  108. package/src/components/cursor/README.md +53 -0
  109. package/src/components/cursor/cursor.css +177 -0
  110. package/src/components/cursor/element.ts +23 -0
  111. package/src/components/cursor/engine.ts +254 -0
  112. package/src/components/cursor/react.tsx +26 -0
  113. package/src/components/dialog/README.md +106 -0
  114. package/src/components/dialog/dialog.css +258 -0
  115. package/src/components/dialog/element.ts +118 -0
  116. package/src/components/dialog/engine.ts +432 -0
  117. package/src/components/dialog/react.tsx +200 -0
  118. package/src/components/icon.tsx +10 -0
  119. package/src/components/icons.ts +24 -0
  120. package/src/components/island/README.md +91 -0
  121. package/src/components/island/element.ts +45 -0
  122. package/src/components/island/engine.ts +200 -0
  123. package/src/components/island/island.css +300 -0
  124. package/src/components/island/react.tsx +115 -0
  125. package/src/components/menu/README.md +67 -0
  126. package/src/components/menu/element.ts +55 -0
  127. package/src/components/menu/engine.ts +142 -0
  128. package/src/components/menu/menu.css +104 -0
  129. package/src/components/menu/react.tsx +82 -0
  130. package/src/components/nav/README.md +85 -0
  131. package/src/components/nav/element.ts +39 -0
  132. package/src/components/nav/engine.ts +164 -0
  133. package/src/components/nav/nav.css +1053 -0
  134. package/src/components/nav/react.tsx +90 -0
  135. package/src/components/steps/README.md +70 -0
  136. package/src/components/steps/element.ts +43 -0
  137. package/src/components/steps/engine.ts +185 -0
  138. package/src/components/steps/react.tsx +91 -0
  139. package/src/components/steps/steps.css +49 -0
  140. package/src/components/theme-picker/README.md +57 -0
  141. package/src/components/theme-picker/element.ts +20 -0
  142. package/src/components/theme-picker/engine.ts +91 -0
  143. package/src/components/theme-picker/react.tsx +31 -0
  144. package/src/components/theme-picker/theme-picker.css +114 -0
  145. package/src/components/toast/README.md +60 -0
  146. package/src/components/toast/element.ts +25 -0
  147. package/src/components/toast/engine.ts +483 -0
  148. package/src/components/toast/react.tsx +22 -0
  149. package/src/components/toast/store.ts +122 -0
  150. package/src/components/toast/toast.css +114 -0
  151. package/src/core/channels.ts +165 -0
  152. package/src/core/curves.ts +35 -0
  153. package/src/core/frame.ts +12 -0
  154. package/src/core/gsap.ts +65 -0
  155. package/src/core/loop.ts +44 -0
  156. package/src/core/math.ts +9 -0
  157. package/src/core/physics-spring.ts +59 -0
  158. package/src/core/pointer.ts +63 -0
  159. package/src/core/reduced.ts +21 -0
  160. package/src/core/scope.ts +70 -0
  161. package/src/core/scroll-track.ts +106 -0
  162. package/src/core/split.ts +68 -0
  163. package/src/core/spring.ts +22 -0
  164. package/src/core/styles.ts +31 -0
  165. package/src/core/timing.ts +204 -0
  166. package/src/core/types.ts +20 -0
  167. package/src/core/visible.ts +19 -0
  168. package/src/css/base.css +142 -0
  169. package/src/css/controls/README.md +122 -0
  170. package/src/css/controls/burger.css +67 -0
  171. package/src/css/controls/button.css +226 -0
  172. package/src/css/controls/checkbox.css +53 -0
  173. package/src/css/controls/chip.css +85 -0
  174. package/src/css/controls/field.css +124 -0
  175. package/src/css/controls/glass.css +220 -0
  176. package/src/css/controls/icon.css +26 -0
  177. package/src/css/controls/segmented.css +60 -0
  178. package/src/css/controls/switch.css +72 -0
  179. package/src/css/default-theme.css +123 -0
  180. package/src/css/effects/README.md +134 -0
  181. package/src/css/effects/anim.css +224 -0
  182. package/src/css/effects/fade.css +15 -0
  183. package/src/css/effects/float.css +64 -0
  184. package/src/css/effects/frost.css +62 -0
  185. package/src/css/effects/grain.css +58 -0
  186. package/src/css/effects/loading.css +60 -0
  187. package/src/css/effects/success.css +22 -0
  188. package/src/css/effects/text.css +67 -0
  189. package/src/css/fonts.css +3 -0
  190. package/src/css/states.css +51 -0
  191. package/src/css/themes/black-green.css +126 -0
  192. package/src/css/themes/black.css +126 -0
  193. package/src/css/themes/blue.css +126 -0
  194. package/src/css/themes/brown.css +126 -0
  195. package/src/css/themes/cold-blue.css +126 -0
  196. package/src/css/themes/funny.css +126 -0
  197. package/src/css/themes/gray.css +126 -0
  198. package/src/css/themes/green.css +126 -0
  199. package/src/css/themes/modern-1.css +126 -0
  200. package/src/css/themes/neogreen.css +126 -0
  201. package/src/css/themes/new.css +126 -0
  202. package/src/css/themes/new2.css +126 -0
  203. package/src/css/themes/oled.css +126 -0
  204. package/src/css/themes/orange.css +126 -0
  205. package/src/css/themes/pastel-pink.css +126 -0
  206. package/src/css/themes/pink.css +126 -0
  207. package/src/css/themes/purple.css +126 -0
  208. package/src/css/themes/red.css +126 -0
  209. package/src/css/themes/super-blue.css +126 -0
  210. package/src/css/tokens.css +182 -0
  211. package/src/elements/base.ts +76 -0
  212. package/src/elements/index.ts +48 -0
  213. package/src/gl/index.ts +15 -0
  214. package/src/gl/liquid-blend/README.md +23 -0
  215. package/src/gl/liquid-blend/create-liquid-blend.ts +7 -0
  216. package/src/gl/liquid-blend/shader.ts +9 -0
  217. package/src/gl/matcap/README.md +23 -0
  218. package/src/gl/matcap/create-matcap.ts +7 -0
  219. package/src/gl/matcap/shader.ts +16 -0
  220. package/src/gl/particle-hero/README.md +41 -0
  221. package/src/gl/particle-hero/create-particle-hero.ts +544 -0
  222. package/src/gl/particle-hero/particle-hero.css +19 -0
  223. package/src/gl/particle-hero/shaders.ts +84 -0
  224. package/src/gl/shader-blur/README.md +23 -0
  225. package/src/gl/shader-blur/create-shader-blur.ts +7 -0
  226. package/src/gl/shader-blur/shader.ts +11 -0
  227. package/src/gl/shader-grain/README.md +23 -0
  228. package/src/gl/shader-grain/create-shader-grain.ts +7 -0
  229. package/src/gl/shader-grain/shader.ts +6 -0
  230. package/src/gl/shared/create-shader-effect.ts +186 -0
  231. package/src/gl/shared/shader.css +3 -0
  232. package/src/gl/shared/shaders.ts +30 -0
  233. package/src/index.ts +177 -0
  234. package/src/motion/anchor.ts +45 -0
  235. package/src/motion/chat.ts +97 -0
  236. package/src/motion/confetti.ts +130 -0
  237. package/src/motion/count.ts +95 -0
  238. package/src/motion/details.ts +70 -0
  239. package/src/motion/drag-dismiss.ts +231 -0
  240. package/src/motion/effects.ts +81 -0
  241. package/src/motion/entrances.ts +27 -0
  242. package/src/motion/floating.ts +389 -0
  243. package/src/motion/flow.ts +115 -0
  244. package/src/motion/indicator.ts +44 -0
  245. package/src/motion/layout.ts +39 -0
  246. package/src/motion/liquid.ts +227 -0
  247. package/src/motion/morph.ts +540 -0
  248. package/src/motion/reveal.ts +112 -0
  249. package/src/motion/sheet.ts +65 -0
  250. package/src/motion/surface.ts +108 -0
  251. package/src/react/animated-number.tsx +30 -0
  252. package/src/react/flip-group.tsx +44 -0
  253. package/src/react/frost.tsx +33 -0
  254. package/src/react/index.ts +80 -0
  255. package/src/react/slot-text.tsx +44 -0
  256. package/src/react/theme-provider.tsx +42 -0
  257. package/src/react/use-card-dock.ts +9 -0
  258. package/src/react/use-cascade.ts +9 -0
  259. package/src/react/use-contrast.ts +9 -0
  260. package/src/react/use-counter.ts +8 -0
  261. package/src/react/use-curved-carousel.ts +10 -0
  262. package/src/react/use-depth-grid.ts +9 -0
  263. package/src/react/use-details.ts +7 -0
  264. package/src/react/use-dock-pill.ts +10 -0
  265. package/src/react/use-drag-ruler.ts +10 -0
  266. package/src/react/use-fill-text.ts +9 -0
  267. package/src/react/use-fit-text.ts +8 -0
  268. package/src/react/use-flow.ts +8 -0
  269. package/src/react/use-frame-zoom.ts +9 -0
  270. package/src/react/use-infinite-reel.ts +8 -0
  271. package/src/react/use-letters.ts +9 -0
  272. package/src/react/use-liquid-blend.ts +8 -0
  273. package/src/react/use-marquee.ts +9 -0
  274. package/src/react/use-mask-text.ts +9 -0
  275. package/src/react/use-matcap.ts +8 -0
  276. package/src/react/use-parallax.ts +9 -0
  277. package/src/react/use-particle-hero.ts +9 -0
  278. package/src/react/use-pile.ts +8 -0
  279. package/src/react/use-pop.ts +8 -0
  280. package/src/react/use-radial-menu.ts +10 -0
  281. package/src/react/use-recipe.ts +38 -0
  282. package/src/react/use-reflow.ts +9 -0
  283. package/src/react/use-repel-bento.ts +8 -0
  284. package/src/react/use-reveal.ts +8 -0
  285. package/src/react/use-rotator.ts +10 -0
  286. package/src/react/use-shader-blur.ts +8 -0
  287. package/src/react/use-shader-grain.ts +8 -0
  288. package/src/react/use-slot-text.ts +17 -0
  289. package/src/react/use-snap-strip.ts +9 -0
  290. package/src/react/use-sortable.ts +14 -0
  291. package/src/react/use-spring-cards.ts +8 -0
  292. package/src/react/use-stretch-footer.ts +20 -0
  293. package/src/react/use-tabs.ts +10 -0
  294. package/src/react/use-text-roll.ts +9 -0
  295. package/src/react/use-ticket-menu.ts +15 -0
  296. package/src/react/use-words.ts +8 -0
  297. package/src/recipes/card-dock/README.md +36 -0
  298. package/src/recipes/card-dock/card-dock.css +178 -0
  299. package/src/recipes/card-dock/create-card-dock.ts +97 -0
  300. package/src/recipes/cascade/README.md +33 -0
  301. package/src/recipes/cascade/create-cascade.ts +111 -0
  302. package/src/recipes/contrast/README.md +29 -0
  303. package/src/recipes/contrast/create-contrast.ts +68 -0
  304. package/src/recipes/counter/README.md +42 -0
  305. package/src/recipes/counter/create-counter.ts +84 -0
  306. package/src/recipes/curved-carousel/README.md +50 -0
  307. package/src/recipes/curved-carousel/carousel-math.ts +20 -0
  308. package/src/recipes/curved-carousel/create-curved-carousel.ts +257 -0
  309. package/src/recipes/curved-carousel/curved-carousel.css +50 -0
  310. package/src/recipes/depth-grid/README.md +30 -0
  311. package/src/recipes/depth-grid/create-depth-grid.ts +120 -0
  312. package/src/recipes/details/README.md +31 -0
  313. package/src/recipes/details/create-details.ts +27 -0
  314. package/src/recipes/dock-pill/README.md +23 -0
  315. package/src/recipes/dock-pill/create-dock-pill.ts +68 -0
  316. package/src/recipes/dock-pill/dock-pill.css +5 -0
  317. package/src/recipes/drag-ruler/README.md +21 -0
  318. package/src/recipes/drag-ruler/create-drag-ruler.ts +95 -0
  319. package/src/recipes/drag-ruler/drag-ruler.css +16 -0
  320. package/src/recipes/fill-text/README.md +27 -0
  321. package/src/recipes/fill-text/create-fill-text.ts +93 -0
  322. package/src/recipes/fit-text/README.md +37 -0
  323. package/src/recipes/fit-text/create-fit-text.ts +55 -0
  324. package/src/recipes/fit-text/fit-text.css +6 -0
  325. package/src/recipes/flow/README.md +46 -0
  326. package/src/recipes/flow/create-flow.ts +55 -0
  327. package/src/recipes/frame-zoom/README.md +29 -0
  328. package/src/recipes/frame-zoom/create-frame-zoom.ts +91 -0
  329. package/src/recipes/infinite-reel/README.md +23 -0
  330. package/src/recipes/infinite-reel/create-infinite-reel.ts +140 -0
  331. package/src/recipes/infinite-reel/infinite-reel.css +8 -0
  332. package/src/recipes/letters/README.md +30 -0
  333. package/src/recipes/letters/create-letters.ts +113 -0
  334. package/src/recipes/marquee/README.md +32 -0
  335. package/src/recipes/marquee/create-marquee.ts +74 -0
  336. package/src/recipes/marquee/marquee.css +41 -0
  337. package/src/recipes/mask-text/README.md +39 -0
  338. package/src/recipes/mask-text/create-mask-text.ts +134 -0
  339. package/src/recipes/parallax/README.md +31 -0
  340. package/src/recipes/parallax/create-parallax.ts +74 -0
  341. package/src/recipes/pile/README.md +48 -0
  342. package/src/recipes/pile/create-pile.ts +50 -0
  343. package/src/recipes/pile/pile-math.ts +27 -0
  344. package/src/recipes/pile/pile.css +63 -0
  345. package/src/recipes/pill/README.md +30 -0
  346. package/src/recipes/pill/pill.css +48 -0
  347. package/src/recipes/pop/README.md +36 -0
  348. package/src/recipes/pop/create-pop.ts +52 -0
  349. package/src/recipes/radial-menu/README.md +44 -0
  350. package/src/recipes/radial-menu/create-radial-menu.ts +96 -0
  351. package/src/recipes/radial-menu/radial-menu.css +45 -0
  352. package/src/recipes/reflow/README.md +47 -0
  353. package/src/recipes/reflow/create-reflow.ts +49 -0
  354. package/src/recipes/repel-bento/README.md +21 -0
  355. package/src/recipes/repel-bento/create-repel-bento.ts +65 -0
  356. package/src/recipes/repel-bento/repel-bento.css +3 -0
  357. package/src/recipes/reveal/README.md +36 -0
  358. package/src/recipes/reveal/create-reveal.ts +48 -0
  359. package/src/recipes/rotator/README.md +21 -0
  360. package/src/recipes/rotator/create-rotator.ts +66 -0
  361. package/src/recipes/rotator/rotator.css +3 -0
  362. package/src/recipes/slot-text/README.md +44 -0
  363. package/src/recipes/slot-text/create-slot-text.ts +244 -0
  364. package/src/recipes/slot-text/slot-text.css +42 -0
  365. package/src/recipes/snap-strip/README.md +31 -0
  366. package/src/recipes/snap-strip/create-snap-strip.ts +133 -0
  367. package/src/recipes/snap-strip/snap-strip.css +23 -0
  368. package/src/recipes/sortable/README.md +88 -0
  369. package/src/recipes/sortable/create-sortable.ts +863 -0
  370. package/src/recipes/sortable/sortable.css +40 -0
  371. package/src/recipes/spring-cards/README.md +48 -0
  372. package/src/recipes/spring-cards/create-spring-cards.ts +140 -0
  373. package/src/recipes/spring-cards/spring-cards.css +23 -0
  374. package/src/recipes/stretch-footer/README.md +44 -0
  375. package/src/recipes/stretch-footer/create-stretch-footer.ts +69 -0
  376. package/src/recipes/stretch-footer/stretch-footer.css +28 -0
  377. package/src/recipes/tabs/README.md +39 -0
  378. package/src/recipes/tabs/create-tabs.ts +91 -0
  379. package/src/recipes/tabs/tabs.css +19 -0
  380. package/src/recipes/text-roll/README.md +35 -0
  381. package/src/recipes/text-roll/create-text-roll.ts +137 -0
  382. package/src/recipes/text-roll/text-roll.css +21 -0
  383. package/src/recipes/ticket-menu/README.md +24 -0
  384. package/src/recipes/ticket-menu/create-ticket-menu.ts +6 -0
  385. package/src/recipes/words/README.md +43 -0
  386. package/src/recipes/words/create-words.ts +114 -0
  387. package/src/theme/css-format.ts +32 -0
  388. package/src/theme/generate.ts +72 -0
  389. package/src/theme/index.ts +15 -0
  390. package/src/theme/presets.ts +79 -0
  391. package/src/theme/theme.ts +119 -0
@@ -0,0 +1,111 @@
1
+ import { gsap, registerMotion } from '../../core/gsap';
2
+ import { entrances } from '../../core/reduced';
3
+ import { clearStyles } from '../../core/styles';
4
+ import { TIMING } from '../../core/timing';
5
+ import { inert, warnOnce, type Instance } from '../../core/types';
6
+ import { shows, thresholds } from '../../core/visible';
7
+
8
+ /*
9
+ * Siena's reveal group: when the block shows (30 %), its marked children enter
10
+ * one after another, 30 ms apart, 1.2 s expo.out — rules grow from alternating
11
+ * sides (scaleX 0 → 1), fades appear (OFFFICE's alpha), rises come up from 200 %
12
+ * inside their clip. When it leaves, every child goes back to its start state
13
+ * so the block enters again next time (unless once).
14
+ * Children: [data-mk-cascade="rule|fade|rise"].
15
+ */
16
+
17
+ export interface CascadeOptions {
18
+ /** Seconds before the first child (also data-mk-delay). */
19
+ delay?: number;
20
+ duration?: number;
21
+ /** Seconds between children. */
22
+ step?: number;
23
+ once?: boolean;
24
+ trigger?: 'scroll' | 'load' | 'manual';
25
+ }
26
+
27
+ export interface CascadeInstance extends Instance {
28
+ play(): void;
29
+ /** Every child back to its start state (what Siena does when the block leaves). */
30
+ reset(): void;
31
+ }
32
+
33
+ type Kind = 'rule' | 'fade' | 'rise';
34
+ const STYLES = ['transform', 'translate', 'rotate', 'scale', 'transform-origin', 'opacity', 'visibility'];
35
+
36
+ export function createCascade(root: HTMLElement | null, options: CascadeOptions = {}): CascadeInstance {
37
+ if (!root) {
38
+ warnOnce('cascade: missing element');
39
+ return inert({ play() {}, reset() {} });
40
+ }
41
+ const t = TIMING.cascade;
42
+ const { duration = t.duration, step = t.step, once = false, trigger = 'scroll' } = options;
43
+ const delay = options.delay ?? (Number(root.dataset.mkDelay) || 0);
44
+ const items = [...root.querySelectorAll<HTMLElement>('[data-mk-cascade]')].map((el, index) => ({
45
+ el,
46
+ index,
47
+ kind: (el.dataset.mkCascade as Kind) || 'fade',
48
+ }));
49
+ if (!entrances() || !items.length) {
50
+ root.dataset.mkReady = '';
51
+ return {
52
+ play() {},
53
+ reset() {},
54
+ destroy() {
55
+ delete root.dataset.mkReady;
56
+ },
57
+ };
58
+ }
59
+ registerMotion();
60
+ let observer: IntersectionObserver | null = null;
61
+
62
+ const setOut = ({ el, index, kind }: (typeof items)[number]) => {
63
+ gsap.killTweensOf(el);
64
+ if (kind === 'rule') gsap.set(el, { scaleX: 0, transformOrigin: index % 2 === 0 ? 'left center' : 'right center' });
65
+ else if (kind === 'rise') gsap.set(el, { yPercent: t.rise });
66
+ else gsap.set(el, { autoAlpha: 0 });
67
+ };
68
+ items.forEach(setOut);
69
+
70
+ let shown = false;
71
+ const play = () => {
72
+ shown = true;
73
+ for (const { el, index, kind } of items) {
74
+ const to = kind === 'rule' ? { scaleX: 1 } : kind === 'rise' ? { yPercent: 0 } : { autoAlpha: 1 };
75
+ gsap.to(el, { ...to, duration, ease: 'expo.out', delay: delay + index * step, overwrite: 'auto' });
76
+ }
77
+ };
78
+ const reset = () => {
79
+ shown = false;
80
+ items.forEach(setOut);
81
+ };
82
+
83
+ if (trigger === 'load') play();
84
+ else if (trigger === 'scroll' && typeof IntersectionObserver === 'function') {
85
+ observer = new IntersectionObserver(
86
+ (entries) => {
87
+ for (const entry of entries) {
88
+ if (shows(entry, t.threshold)) {
89
+ if (!shown) play();
90
+ if (once) observer?.disconnect();
91
+ } else if (!entry.isIntersecting && shown && !once) reset();
92
+ }
93
+ },
94
+ { threshold: thresholds(t.threshold) },
95
+ );
96
+ observer.observe(root);
97
+ }
98
+ root.dataset.mkReady = '';
99
+
100
+ return {
101
+ play,
102
+ reset,
103
+ destroy() {
104
+ observer?.disconnect();
105
+ const elements = items.map((item) => item.el);
106
+ gsap.killTweensOf(elements);
107
+ clearStyles(elements, STYLES);
108
+ delete root.dataset.mkReady;
109
+ },
110
+ };
111
+ }
@@ -0,0 +1,29 @@
1
+ # contrast
2
+
3
+ La cabecera cambia de tono según la sección que tiene debajo: las secciones dicen si son claras u oscuras (`data-mk-tone="light"` / `"dark"`) y la que cruza el 10 % superior de la pantalla escribe su tono en la cabecera (`data-mk-on`). Tu CSS decide los colores.
4
+
5
+ Origen: Siena Film Foundation, logo que pasa a negro sobre secciones claras (`rootMargin: 0% 0% -90% 0%`).
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <header data-mk="contrast" class="site-header">…</header>
11
+ <section data-mk-tone="dark">…</section>
12
+ <section data-mk-tone="light">…</section>
13
+
14
+ <style>
15
+ .site-header { color: white; transition: color 0.3s; }
16
+ .site-header[data-mk-on='light'] { color: black; }
17
+ </style>
18
+ ```
19
+
20
+ ## React
21
+
22
+ ```tsx
23
+ const ref = useRef<HTMLElement>(null);
24
+ useContrast(ref);
25
+ ```
26
+
27
+ ## API
28
+
29
+ `createContrast(target, { sections, attribute })` → `{ tone, destroy }`. `sections` por defecto `[data-mk-tone]`; `attribute` por defecto `data-mk-on`.
@@ -0,0 +1,68 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, warnOnce, type Instance } from '../../core/types';
3
+
4
+ /*
5
+ * Siena's header contrast: sections say what they are (data-mk-tone="light" or
6
+ * "dark"); the section crossing the top 10 % of the screen gives its tone to
7
+ * the header (data-mk-on), and the page's CSS turns the logo and links dark on
8
+ * light sections and light on dark ones.
9
+ */
10
+
11
+ export interface ContrastOptions {
12
+ /** Sections to watch (default [data-mk-tone]). */
13
+ sections?: string;
14
+ /** Attribute written on the target (default data-mk-on). */
15
+ attribute?: string;
16
+ }
17
+
18
+ export interface ContrastInstance extends Instance {
19
+ readonly tone: string | null;
20
+ }
21
+
22
+ export function createContrast(target: HTMLElement | null, options: ContrastOptions = {}): ContrastInstance {
23
+ if (!target) {
24
+ warnOnce('contrast: missing element');
25
+ return inert({ tone: null });
26
+ }
27
+ const { sections = '[data-mk-tone]', attribute = 'data-mk-on' } = options;
28
+ const scope = createScope();
29
+ const watched = [...document.querySelectorAll<HTMLElement>(sections)];
30
+ // Under the band, in the order they came in: the newest wins.
31
+ const under = new Set<HTMLElement>();
32
+ let tone: string | null = target.getAttribute(attribute);
33
+
34
+ const apply = () => {
35
+ const current = [...under].at(-1);
36
+ if (!current) return;
37
+ tone = current.dataset.mkTone ?? null;
38
+ scope.attr(target, attribute, tone);
39
+ };
40
+
41
+ if (typeof IntersectionObserver === 'function' && watched.length) {
42
+ const observer = scope.observe(
43
+ new IntersectionObserver(
44
+ (entries) => {
45
+ for (const entry of entries) {
46
+ const section = entry.target as HTMLElement;
47
+ under.delete(section);
48
+ if (entry.isIntersecting) under.add(section);
49
+ }
50
+ apply();
51
+ },
52
+ { rootMargin: '0% 0% -90% 0%', threshold: 0 },
53
+ ),
54
+ );
55
+ for (const section of watched) observer.observe(section);
56
+ }
57
+ target.dataset.mkReady = '';
58
+
59
+ return {
60
+ get tone() {
61
+ return tone;
62
+ },
63
+ destroy() {
64
+ scope.dispose();
65
+ delete target.dataset.mkReady;
66
+ },
67
+ };
68
+ }
@@ -0,0 +1,42 @@
1
+ # counter
2
+
3
+ Un número cuenta hasta su valor con la curva `numberSoft` (0.6 s), partiendo del valor que muestra.
4
+
5
+ Origen: Stepbro — animated number (`numeric-counter` en el kit: 0.6 s, 0.7 s en precios, `M0,0 C0.25,0.1 0.25,1 1,1`).
6
+
7
+ ## Vanilla / Astro
8
+
9
+ ```html
10
+ <span data-mk="counter" data-mk-to="1280" data-mk-prefix="$">0</span>
11
+ ```
12
+
13
+ Sin `autoInit`: `createCounter(el, opciones)`.
14
+
15
+ ## React
16
+
17
+ ```tsx
18
+ const ref = useRef<HTMLSpanElement>(null);
19
+ const counter = useCounter(ref, { to: total, trigger: 'load' });
20
+ // después: counter.current?.set(nuevoTotal)
21
+ ```
22
+
23
+ Para un número que cambia con el estado, `<AnimatedNumber value={total} />` es más cómodo.
24
+
25
+ ## Opciones
26
+
27
+ | Opción | Por defecto | Qué hace |
28
+ |---|---|---|
29
+ | `to` | número del texto | Valor final |
30
+ | `from` | `0` | Valor inicial |
31
+ | `duration` | `.6` | Segundos |
32
+ | `decimals` | `0` | Decimales mostrados |
33
+ | `locale` | del navegador | Agrupación y separador decimal (`Intl`) |
34
+ | `prefix`, `suffix` | `''` | Texto antes y después |
35
+ | `trigger` | `'scroll'` | `'scroll'`, `'load'` o `'manual'` |
36
+ | `start` | `'top 90%'` | Inicio del ScrollTrigger |
37
+
38
+ ## Controles
39
+
40
+ `play()`, `set(valor)`, `destroy()` (devuelve el texto original)
41
+
42
+ Con movimiento reducido muestra el valor final directamente.
@@ -0,0 +1,84 @@
1
+ import { gsap, ScrollTrigger, registerMotion } from '../../core/gsap';
2
+ import { entrances } from '../../core/reduced';
3
+ import { inert, warnOnce, type Instance } from '../../core/types';
4
+ import { TIMING } from '../../core/timing';
5
+ import { countTo } from '../../motion/count';
6
+
7
+ /* Stepbro's animated number: counts to its value with `numberSoft` (0.6 s), from the value shown. */
8
+
9
+ export interface CounterOptions {
10
+ /** Final value. Default: the number written in the element. */
11
+ to?: number;
12
+ from?: number;
13
+ duration?: number;
14
+ decimals?: number;
15
+ /** Intl locale for grouping and decimals. Default: the browser's. */
16
+ locale?: string;
17
+ prefix?: string;
18
+ suffix?: string;
19
+ trigger?: 'scroll' | 'load' | 'manual';
20
+ start?: string;
21
+ }
22
+
23
+ export interface CounterInstance extends Instance {
24
+ /** Count from the start value to `to`. */
25
+ play(): void;
26
+ /** Count from the value shown to `value`. */
27
+ set(value: number): void;
28
+ }
29
+
30
+ export function createCounter(el: HTMLElement | null, options: CounterOptions = {}): CounterInstance {
31
+ if (!el) {
32
+ warnOnce('counter: missing element');
33
+ return inert({ play() {}, set() {} });
34
+ }
35
+ registerMotion();
36
+ const original = el.textContent ?? '';
37
+ const parsed = Number(original.replace(/[^\d.-]/g, ''));
38
+ const {
39
+ to = Number.isFinite(parsed) ? parsed : 0,
40
+ from = 0,
41
+ duration = TIMING.number,
42
+ decimals = 0,
43
+ locale,
44
+ prefix = '',
45
+ suffix = '',
46
+ trigger = 'scroll',
47
+ start = 'top 90%',
48
+ } = options;
49
+ const format = (value: number) =>
50
+ prefix + value.toLocaleString(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals }) + suffix;
51
+ const ctx = gsap.context(() => {});
52
+ let played = false;
53
+
54
+ const play = () => {
55
+ if (played) return;
56
+ played = true;
57
+ ctx.add(() => countTo(el, to, { from, duration, format }));
58
+ };
59
+
60
+ if (entrances()) {
61
+ el.textContent = format(from);
62
+ ctx.add(() => {
63
+ if (trigger === 'load') play();
64
+ else if (trigger !== 'manual') ScrollTrigger.create({ trigger: el, start, end: 'max', once: true, onEnter: play });
65
+ });
66
+ } else {
67
+ played = true;
68
+ el.textContent = format(to);
69
+ }
70
+ el.dataset.mkReady = '';
71
+
72
+ return {
73
+ play,
74
+ set(value: number) {
75
+ played = true;
76
+ ctx.add(() => countTo(el, value, { duration, format }));
77
+ },
78
+ destroy() {
79
+ ctx.revert();
80
+ el.textContent = original;
81
+ delete el.dataset.mkReady;
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,50 @@
1
+ # curved-carousel
2
+
3
+ Carrusel en arco: se arrastra con inercia (fricción .85), se centra solo en la slide más cercana, da la vuelta infinita y responde a la rueda horizontal, las flechas y el foco.
4
+
5
+ Origen: spencergabor.com — Carousel.global.vue.
6
+
7
+ ## Vanilla / Astro
8
+
9
+ ```html
10
+ <div data-mk="curved-carousel" tabindex="0" aria-roledescription="carousel" aria-label="Proyectos">
11
+ <button data-mk-slide>
12
+ <span class="mk-carousel__media"><img src="a.jpg" alt=""></span>
13
+ <span class="mk-carousel__title">Proyecto A</span>
14
+ </button>
15
+ …
16
+ </div>
17
+ <script>el.addEventListener('mk:select', (e) => open(e.detail.index))</script>
18
+ ```
19
+
20
+ Con `autoInit()` (de `@yewdiel21/motion-kit/auto`) las opciones van como `data-mk-<opción>` en kebab-case. Sin `autoInit`: `createCurvedCarousel(el, opciones)`.
21
+
22
+ ## React
23
+
24
+ ```tsx
25
+ const ref = useRef<HTMLDivElement>(null);
26
+ const carousel = useCurvedCarousel(ref, { onSelect: (i) => setOpen(i) });
27
+ // carousel.current?.next()
28
+ ```
29
+
30
+ ## Opciones
31
+
32
+ | Opción | Por defecto | Qué hace |
33
+ |---|---|---|
34
+ | `slideSelector` | `'[data-mk-slide]'` | Slides dentro del root |
35
+ | `friction` | `.85` | Decaimiento por frame de la velocidad |
36
+ | `throwBoost` | `2` | Multiplicador de la velocidad al soltar |
37
+ | `wheel` | `.2` | Factor de la rueda horizontal |
38
+ | `tapDistance` | `6` | px por debajo de los cuales es un clic |
39
+ | `startIndex` | `0` | Slide centrada al inicio |
40
+ | `onSelect(index, slide)` | — | Clic/Enter en una slide (en `autoInit`: evento `mk:select`) |
41
+
42
+ ## Controles
43
+
44
+ `next()`, `prev()`, `goTo(i)`, `index`, `destroy()`
45
+
46
+ ## CSS
47
+
48
+ Mientras corre, el root lleva `data-mk-carousel` y las slides se posicionan en absoluto con `--mk-x` y giran según `--mk-t` (distancia al centro). Sin JS el root conserva tu estilo (por ejemplo, una fila con scroll). Ajusta `--mk-slide-w`, `--mk-gap` y `--mk-carousel-h` en el root.
49
+
50
+ Con `prefers-reduced-motion: reduce` la receta deja el contenido en su estado final, sin animar.
@@ -0,0 +1,20 @@
1
+ import { wrap } from '../../core/math';
2
+
3
+ export const FRICTION = 0.85;
4
+
5
+ /** X of slide k relative to the viewport's left edge, wrapped so the track loops. */
6
+ export function slideX(k: number, offset: number, step: number, length: number) {
7
+ return wrap(k * step - offset + step, length) - step;
8
+ }
9
+
10
+ /** Where the offset ends if velocity v (px/frame, applied as offset -= v) decays by f each frame. */
11
+ export const restingOffset = (offset: number, v: number, f = FRICTION) => offset - (v * f) / (1 - f);
12
+
13
+ /** Velocity that makes restingOffset(offset, v) === target. */
14
+ export const velocityFor = (target: number, offset: number, f = FRICTION) => -((target - offset) * (1 - f)) / f;
15
+
16
+ /** Offset that centres the slide nearest to the projected rest; `dir` forces ±1 slide from the current one. */
17
+ export function snapOffset(offset: number, v: number, step: number, center: number, f = FRICTION, dir = 0) {
18
+ const base = dir === 0 ? restingOffset(offset, v, f) : offset;
19
+ return (Math.round((base + center) / step) + dir) * step - center;
20
+ }
@@ -0,0 +1,257 @@
1
+ import { registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { whileVisible } from '../../core/loop';
4
+ import { createScope } from '../../core/scope';
5
+ import { inert, warnOnce, type Instance } from '../../core/types';
6
+ import { shows, thresholds } from '../../core/visible';
7
+ import { wrap } from '../../core/math';
8
+ import { FRICTION, slideX, snapOffset } from './carousel-math';
9
+
10
+ /*
11
+ * spencergabor.com carousel: offset with friction .85, drag follows the pointer,
12
+ * release multiplies the velocity and snaps to the slide nearest the projected
13
+ * rest. Slides loop by wrapping their x; CSS turns the distance from the centre
14
+ * (`--mk-t`) into the arc (see curved-carousel.css).
15
+ */
16
+
17
+ export interface CurvedCarouselOptions {
18
+ /** Slides inside the root. */
19
+ slideSelector?: string;
20
+ /** Per-frame velocity decay (0–1). */
21
+ friction?: number;
22
+ /** Multiplier applied to the release velocity before snapping. */
23
+ throwBoost?: number;
24
+ /** Horizontal wheel / trackpad factor. */
25
+ wheel?: number;
26
+ /** Movement below this many px counts as a tap (click), not a drag. */
27
+ tapDistance?: number;
28
+ startIndex?: number;
29
+ /** Tap/click or Enter on a slide. */
30
+ onSelect?: (index: number, slide: HTMLElement) => void;
31
+ }
32
+
33
+ export interface CurvedCarouselInstance extends Instance {
34
+ next(): void;
35
+ prev(): void;
36
+ goTo(index: number): void;
37
+ /** Index of the slide closest to the centre. */
38
+ readonly index: number;
39
+ }
40
+
41
+ export function createCurvedCarousel(root: HTMLElement | null, options: CurvedCarouselOptions = {}): CurvedCarouselInstance {
42
+ const o = {
43
+ slideSelector: '[data-mk-slide]',
44
+ friction: FRICTION,
45
+ throwBoost: 2,
46
+ wheel: 0.2,
47
+ tapDistance: 6,
48
+ startIndex: 0,
49
+ ...options,
50
+ };
51
+ o.friction = Math.min(0.99, Math.max(0.5, o.friction));
52
+ const slides = root ? [...root.querySelectorAll<HTMLElement>(o.slideSelector)] : [];
53
+ const count = slides.length;
54
+ if (!root || !count) {
55
+ warnOnce(`curved-carousel: no "${o.slideSelector}" inside the root`);
56
+ return inert({ next() {}, prev() {}, goTo() {}, index: 0 });
57
+ }
58
+
59
+ registerMotion();
60
+ const scope = createScope();
61
+ root.dataset.mkCarousel = '';
62
+ slides.forEach((slide, k) => slide.style.setProperty('--mk-k', String(k)));
63
+ scope.add(() => {
64
+ delete root.dataset.mkCarousel;
65
+ delete root.dataset.mkReady;
66
+ delete root.dataset.mkInview;
67
+ slides.forEach((slide) => ['--mk-k', '--mk-x', '--mk-t'].forEach((name) => slide.style.removeProperty(name)));
68
+ });
69
+
70
+ let rootW = 0;
71
+ let slideW = 0;
72
+ let step = 1;
73
+ let length = count;
74
+ let center = 0;
75
+ let offset = 0;
76
+ let v = 0;
77
+ let target: number | null = null;
78
+ let measured = false;
79
+
80
+ let down = false;
81
+ let dragDelta = 0;
82
+ let lastX = 0;
83
+ let travelled = 0;
84
+ let suppressClick = false;
85
+ let wheelTimer: ReturnType<typeof setTimeout> | undefined;
86
+ scope.add(() => clearTimeout(wheelTimer));
87
+
88
+ const render = () => {
89
+ for (let k = 0; k < count; k++) {
90
+ const x = slideX(k, offset, step, length);
91
+ const style = slides[k]!.style;
92
+ style.setProperty('--mk-x', x.toFixed(2));
93
+ style.setProperty('--mk-t', (x + slideW / 2 - rootW / 2).toFixed(2));
94
+ }
95
+ };
96
+
97
+ /** Offset that centres slide k, picking the loop copy nearest to the current offset. */
98
+ const centred = (k: number) => {
99
+ const base = k * step - center;
100
+ return base + Math.round((offset - base) / length) * length;
101
+ };
102
+
103
+ const settleTo = (next: number) => {
104
+ target = next;
105
+ if (!reduced()) return;
106
+ offset = next;
107
+ target = null;
108
+ v = 0;
109
+ render();
110
+ };
111
+
112
+ const measure = () => {
113
+ const previousCenter = center;
114
+ rootW = root.clientWidth;
115
+ slideW = slides[0]!.offsetWidth;
116
+ const gap = parseFloat(getComputedStyle(root).columnGap) || 16;
117
+ const index = measured ? (offset + previousCenter) / step : o.startIndex;
118
+ step = slideW + gap || 1;
119
+ length = count * step;
120
+ center = (rootW - slideW) / 2;
121
+ offset = index * step - center;
122
+ if (target !== null) target = Math.round((target + previousCenter) / step) * step - center;
123
+ measured = true;
124
+ render();
125
+ };
126
+
127
+ const tick = (dt: number) => {
128
+ if (down) {
129
+ v = dragDelta;
130
+ dragDelta = 0;
131
+ offset -= v;
132
+ } else if (target !== null) {
133
+ // Geometric approach to the snap point: the same path as Spencer's computed velocity.
134
+ const next = offset + (target - offset) * (1 - Math.pow(o.friction, dt));
135
+ v = offset - next;
136
+ offset = next;
137
+ if (Math.abs(target - offset) < 0.1) {
138
+ offset = target;
139
+ target = null;
140
+ v = 0;
141
+ }
142
+ } else if (Math.abs(v) > 0.01) {
143
+ v *= Math.pow(o.friction, dt);
144
+ offset -= v * dt;
145
+ } else {
146
+ return;
147
+ }
148
+ render();
149
+ };
150
+
151
+ // Drag (window listeners so the drag survives leaving the carousel)
152
+ scope.on(root, 'pointerdown', (e: PointerEvent) => {
153
+ if (e.button !== 0) return;
154
+ if (e.pointerType === 'mouse') e.preventDefault();
155
+ down = true;
156
+ target = null;
157
+ dragDelta = 0;
158
+ travelled = 0;
159
+ lastX = e.clientX;
160
+ });
161
+ scope.on(
162
+ window,
163
+ 'pointermove',
164
+ (e: PointerEvent) => {
165
+ if (!down) return;
166
+ const dx = e.clientX - lastX;
167
+ lastX = e.clientX;
168
+ dragDelta += dx;
169
+ travelled += Math.abs(dx);
170
+ },
171
+ { passive: true },
172
+ );
173
+ const release = (cancelled: boolean) => {
174
+ if (!down) return;
175
+ down = false;
176
+ suppressClick = travelled > o.tapDistance;
177
+ const throwV = cancelled || reduced() ? 0 : v * o.throwBoost;
178
+ settleTo(snapOffset(offset, throwV, step, center, o.friction));
179
+ };
180
+ scope.on(window, 'pointerup', () => release(false));
181
+ scope.on(window, 'pointercancel', () => release(true));
182
+
183
+ // A drag must not count as a click on the slide under the pointer.
184
+ scope.on(
185
+ root,
186
+ 'click',
187
+ (e: MouseEvent) => {
188
+ if (!suppressClick) return;
189
+ suppressClick = false;
190
+ e.preventDefault();
191
+ e.stopPropagation();
192
+ },
193
+ true,
194
+ );
195
+ scope.on(root, 'click', (e: MouseEvent) => {
196
+ const slide = (e.target as Element).closest<HTMLElement>(o.slideSelector);
197
+ if (slide && root.contains(slide)) o.onSelect?.(slides.indexOf(slide), slide);
198
+ });
199
+
200
+ // Horizontal wheel / trackpad
201
+ scope.on(
202
+ root,
203
+ 'wheel',
204
+ (e: WheelEvent) => {
205
+ if (Math.abs(e.deltaX) <= Math.abs(e.deltaY)) return;
206
+ e.preventDefault();
207
+ target = null;
208
+ if (reduced()) {
209
+ offset += e.deltaX;
210
+ render();
211
+ } else {
212
+ v -= e.deltaX * o.wheel;
213
+ }
214
+ clearTimeout(wheelTimer);
215
+ wheelTimer = setTimeout(() => settleTo(snapOffset(offset, v, step, center, o.friction)), 150);
216
+ },
217
+ { passive: false },
218
+ );
219
+
220
+ // Keyboard: arrows step one slide; focusing a slide centres it.
221
+ const stepBy = (dir: 1 | -1) => settleTo(snapOffset(target ?? offset, 0, step, center, o.friction, dir));
222
+ scope.on(root, 'keydown', (e: KeyboardEvent) => {
223
+ if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
224
+ e.preventDefault();
225
+ stepBy(e.key === 'ArrowLeft' ? -1 : 1);
226
+ });
227
+ scope.on(root, 'focusin', (e: FocusEvent) => {
228
+ const slide = (e.target as Element).closest<HTMLElement>(o.slideSelector);
229
+ if (!slide || down) return;
230
+ settleTo(centred(slides.indexOf(slide)));
231
+ });
232
+
233
+ scope.observe(new ResizeObserver(measure)).observe(root);
234
+ scope
235
+ .observe(
236
+ new IntersectionObserver(
237
+ ([entry]) => {
238
+ if (entry && shows(entry, 0.2)) root.dataset.mkInview = '';
239
+ },
240
+ { threshold: thresholds(0.2) },
241
+ ),
242
+ )
243
+ .observe(root);
244
+ scope.add(whileVisible(root, tick));
245
+ measure();
246
+ root.dataset.mkReady = '';
247
+
248
+ return {
249
+ destroy: () => scope.dispose(),
250
+ next: () => stepBy(1),
251
+ prev: () => stepBy(-1),
252
+ goTo: (index: number) => settleTo(centred(wrap(Math.round(index), count))),
253
+ get index() {
254
+ return wrap(Math.round(((target ?? offset) + center) / step), count);
255
+ },
256
+ };
257
+ }