@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,40 @@
1
+ /* sortable — Stepbro's drag reorder (Tasks): the item lifts into a floating copy that tilts; the others make room. */
2
+ [data-mk-sorting] {
3
+ -webkit-user-select: none;
4
+ user-select: none;
5
+ cursor: grabbing;
6
+ }
7
+
8
+ /* The slot the item left (its floating copy is on top). */
9
+ [data-mk-item][data-mk-dragging] {
10
+ visibility: hidden;
11
+ }
12
+
13
+ /* The floating copy: its shadow grows with the lift (0 → 1). */
14
+ [data-mk-sortable-clone] {
15
+ --mk-lift: 0;
16
+ box-shadow: var(
17
+ --mk-sortable-shadow,
18
+ 0 calc(12px * var(--mk-lift)) calc(32px * var(--mk-lift)) rgb(0 0 0 / calc(0.22 * var(--mk-lift))),
19
+ 0 0 0 1px var(--md-sys-color-outline-variant) inset
20
+ );
21
+ cursor: grabbing;
22
+ }
23
+
24
+ [data-mk-sort-handle] {
25
+ cursor: grab;
26
+ touch-action: none;
27
+ }
28
+
29
+ [data-mk-sorting] [data-mk-sort-handle] {
30
+ cursor: grabbing;
31
+ }
32
+
33
+ /* Lifted with the keyboard. */
34
+ [data-mk-item][data-mk-sort-grabbed] {
35
+ position: relative;
36
+ z-index: 1;
37
+ box-shadow:
38
+ 0 12px 32px rgb(0 0 0 / 0.22),
39
+ 0 0 0 2px var(--mk-accent);
40
+ }
@@ -0,0 +1,48 @@
1
+ # spring-cards
2
+
3
+ Tarjetas en abanico: entran escalando con rebote, cada una tiene un resorte que arranca desplazado al azar y vuelve a su sitio, y el cursor las empuja al pasar cerca.
4
+
5
+ Origen: spencergabor.com — portada (Hero.vue, `useSpring`).
6
+
7
+ ## Vanilla / Astro
8
+
9
+ ```html
10
+ <div data-mk="spring-cards" class="cards">
11
+ <div data-mk-card><div class="mk-card__shadow"></div><img src="a.jpg" alt=""></div>
12
+ <div data-mk-card><div class="mk-card__shadow"></div><img src="b.jpg" alt=""></div>
13
+ </div>
14
+ ```
15
+
16
+ Con `autoInit()` (de `@yewdiel21/motion-kit/auto`) las opciones van como `data-mk-<opción>` en kebab-case. Sin `autoInit`: `createSpringCards(el, opciones)`.
17
+
18
+ ## React
19
+
20
+ ```tsx
21
+ const ref = useRef<HTMLDivElement>(null);
22
+ useSpringCards(ref);
23
+ return <div ref={ref} data-mk="spring-cards">{/* data-mk-card … */}</div>;
24
+ ```
25
+
26
+ ## Opciones
27
+
28
+ | Opción | Por defecto | Qué hace |
29
+ |---|---|---|
30
+ | `selector` | `'[data-mk-card]'` | Tarjetas dentro del root |
31
+ | `initialOffset` | `100` | Desplazamiento inicial aleatorio, ±px |
32
+ | `releaseAfter` | `100` | ms hasta soltar los resortes |
33
+ | `push` | `.4` | Fuerza del empuje del cursor |
34
+ | `falloff` | `6` | Exponente de caída con la distancia: (1 − d)^falloff |
35
+ | `damping` | `.9` | Amortiguación por frame del empuje |
36
+ | `stiffness / friction` | `.05 / .8` | Resorte |
37
+ | `entrance` | `true` | Entrada escalando |
38
+ | `stagger / delay` | `.08 / .4` | Segundos |
39
+
40
+ ## Controles
41
+
42
+ `destroy()`
43
+
44
+ ## CSS
45
+
46
+ Cada hijo de `[data-mk-card]` se traslada con `--mk-x/--mk-y` y rota `--mk-tilt − --mk-velocity·.25deg`. Las inclinaciones por defecto (2°, −3°, 6°, −2°) y el desfase vertical (`--mk-ty`) se cambian con esas variables.
47
+
48
+ Con `prefers-reduced-motion: reduce` la receta deja el contenido en su estado final, sin animar.
@@ -0,0 +1,140 @@
1
+ import { gsap, ELASTIC, registerMotion } from '../../core/gsap';
2
+ import { entrances, reduced } from '../../core/reduced';
3
+ import { createSpring, type Spring } from '../../core/spring';
4
+ import { whileVisible } from '../../core/loop';
5
+ import { bindPointer, pointer, takeMovement } from '../../core/pointer';
6
+ import { clamp } from '../../core/math';
7
+ import { createScope } from '../../core/scope';
8
+ import { inert, warnOnce, type Instance } from '../../core/types';
9
+
10
+ /*
11
+ * spencergabor.com hero: each card owns a spring that starts at a random offset
12
+ * and is pulled home shortly after; pointer movement near a card adds velocity
13
+ * with a (1 − distance)^falloff · push weight. CSS turns the variables into
14
+ * translate/rotate (see spring-cards.css).
15
+ */
16
+
17
+ export interface SpringCardsOptions {
18
+ /** Cards inside the root. */
19
+ selector?: string;
20
+ /** Random start offset range, ±px. */
21
+ initialOffset?: number;
22
+ /** Delay before the springs are pulled to rest, ms. */
23
+ releaseAfter?: number;
24
+ /** Strength of the pointer push. */
25
+ push?: number;
26
+ /** Exponent of the distance falloff. */
27
+ falloff?: number;
28
+ /** Per-frame decay of the pushed velocity. */
29
+ damping?: number;
30
+ stiffness?: number;
31
+ friction?: number;
32
+ /** Scale-in entrance. */
33
+ entrance?: boolean;
34
+ stagger?: number;
35
+ delay?: number;
36
+ }
37
+
38
+ export type SpringCardsInstance = Instance;
39
+
40
+ const VARS = ['--mk-x', '--mk-y', '--mk-velocity'] as const;
41
+
42
+ interface Card {
43
+ el: HTMLElement;
44
+ spring: Spring;
45
+ push: { x: number; y: number };
46
+ cx: number;
47
+ cy: number;
48
+ }
49
+
50
+ const rand = (min: number, max: number) => Math.random() * (max - min) + min;
51
+
52
+ export function createSpringCards(root: HTMLElement | null, options: SpringCardsOptions = {}): SpringCardsInstance {
53
+ const o = {
54
+ selector: '[data-mk-card]',
55
+ initialOffset: 100,
56
+ releaseAfter: 100,
57
+ push: 0.4,
58
+ falloff: 6,
59
+ damping: 0.9,
60
+ stiffness: 0.05,
61
+ friction: 0.8,
62
+ entrance: true,
63
+ stagger: 0.08,
64
+ delay: 0.4,
65
+ ...options,
66
+ };
67
+ const elements = root ? [...root.querySelectorAll<HTMLElement>(o.selector)] : [];
68
+ if (!root || !elements.length) {
69
+ warnOnce(`spring-cards: no "${o.selector}" inside the root`);
70
+ return inert();
71
+ }
72
+
73
+ registerMotion();
74
+ const scope = createScope();
75
+ const ctx = gsap.context(() => {});
76
+ scope.add(() => ctx.revert());
77
+
78
+ if (o.entrance && entrances()) {
79
+ ctx.add(() => gsap.fromTo(elements, { scale: 0 }, { scale: 1, duration: 1, ease: ELASTIC, stagger: o.stagger, delay: o.delay }));
80
+ }
81
+ root.dataset.mkReady = '';
82
+ scope.add(() => {
83
+ delete root.dataset.mkReady;
84
+ elements.forEach((el) => VARS.forEach((name) => el.style.removeProperty(name)));
85
+ });
86
+
87
+ if (reduced()) return { destroy: () => scope.dispose() };
88
+
89
+ scope.add(bindPointer());
90
+ const cards: Card[] = elements.map((el) => {
91
+ const spring = createSpring(o.stiffness, o.friction);
92
+ spring.current.x = spring.target.x = rand(-o.initialOffset, o.initialOffset);
93
+ spring.current.y = spring.target.y = rand(-o.initialOffset, o.initialOffset);
94
+ return { el, spring, push: { x: 0, y: 0 }, cx: 0, cy: 0 };
95
+ });
96
+ scope.timeout(() => cards.forEach(({ spring }) => (spring.target.x = spring.target.y = 0)), o.releaseAfter);
97
+
98
+ // Centres in page coordinates; the card element is only scaled, never translated.
99
+ const measure = () =>
100
+ cards.forEach((card) => {
101
+ const rect = card.el.getBoundingClientRect();
102
+ card.cx = rect.left + scrollX + rect.width / 2;
103
+ card.cy = rect.top + scrollY + rect.height / 2;
104
+ });
105
+ measure();
106
+ scope.observe(new ResizeObserver(measure)).observe(root);
107
+
108
+ scope.add(
109
+ whileVisible(root, (dt) => {
110
+ const movement = takeMovement();
111
+ const mx = clamp(movement.mx, -80, 80);
112
+ const my = clamp(movement.my, -80, 80);
113
+ const decay = Math.pow(o.damping, dt);
114
+
115
+ for (const card of cards) {
116
+ const { spring, push } = card;
117
+ push.x *= decay;
118
+ push.y *= decay;
119
+ spring.current.x += push.x * dt;
120
+ spring.current.y += push.y * dt;
121
+ spring.update(dt);
122
+
123
+ if (pointer.active) {
124
+ const dx = ((pointer.x + scrollX - card.cx) / innerWidth) * 2;
125
+ const dy = ((pointer.y + scrollY - card.cy) / innerHeight) * 2;
126
+ const k = Math.pow(Math.max(0, 1 - Math.hypot(dx, dy)), o.falloff) * o.push;
127
+ push.x += mx * k;
128
+ push.y += my * k;
129
+ }
130
+
131
+ const style = card.el.style;
132
+ style.setProperty('--mk-x', spring.current.x.toFixed(2));
133
+ style.setProperty('--mk-y', spring.current.y.toFixed(2));
134
+ style.setProperty('--mk-velocity', (spring.velocity.x - spring.velocity.y).toFixed(3));
135
+ }
136
+ }),
137
+ );
138
+
139
+ return { destroy: () => scope.dispose() };
140
+ }
@@ -0,0 +1,23 @@
1
+ /* spring-cards — card geometry and tilt from spencergabor.com (Hero) */
2
+
3
+ [data-mk-card] {
4
+ display: grid;
5
+ border-radius: var(--mk-radius-card);
6
+ }
7
+
8
+ /* Every layer of the card (shadow, image) follows the spring */
9
+ [data-mk-card] > * {
10
+ grid-area: 1 / 1;
11
+ border-radius: inherit;
12
+ translate: calc(var(--mk-x, 0) * 1px) calc(var(--mk-y, 0) * 1px + var(--mk-ty, 0%));
13
+ rotate: calc(var(--mk-tilt, 0deg) - var(--mk-velocity, 0) * 0.25deg);
14
+ }
15
+
16
+ [data-mk-card]:nth-child(4n + 1) { --mk-tilt: 2deg; --mk-ty: 5%; }
17
+ [data-mk-card]:nth-child(4n + 2) { --mk-tilt: -3deg; --mk-ty: -6%; }
18
+ [data-mk-card]:nth-child(4n + 3) { --mk-tilt: 6deg; --mk-ty: 5%; }
19
+ [data-mk-card]:nth-child(4n + 4) { --mk-tilt: -2deg; --mk-ty: -4%; }
20
+
21
+ .mk-card__shadow {
22
+ box-shadow: var(--mk-shadow);
23
+ }
@@ -0,0 +1,44 @@
1
+ # stretch-footer
2
+
3
+ Footer fijo detrás de la página que aparece al final del scroll; su título gigante se estira de 0 a 1 en vertical siguiendo el scroll suavizado.
4
+
5
+ Origen: spencergabor.com — Footer.vue.
6
+
7
+ ## Vanilla / Astro
8
+
9
+ ```html
10
+ <main style="position:relative; z-index:1; background:#fff">…</main>
11
+ <div data-mk-phantom aria-hidden="true"></div>
12
+ <footer data-mk="stretch-footer">
13
+ <p data-mk-footer-title data-mk="fit-text">Contacto</p>
14
+ <ul>…enlaces…</ul>
15
+ </footer>
16
+ ```
17
+
18
+ Con `autoInit()` (de `@yewdiel21/motion-kit/auto`) las opciones van como `data-mk-<opción>` en kebab-case. Sin `autoInit`: `createStretchFooter(el, opciones)`.
19
+
20
+ ## React
21
+
22
+ ```tsx
23
+ const footer = useRef<HTMLElement>(null);
24
+ const phantom = useRef<HTMLDivElement>(null);
25
+ useStretchFooter(footer, { phantomRef: phantom });
26
+ ```
27
+
28
+ ## Opciones
29
+
30
+ | Opción | Por defecto | Qué hace |
31
+ |---|---|---|
32
+ | `phantom` | `'[data-mk-phantom]'` | Espaciador al final del flujo (100dvh) |
33
+ | `title` | `'[data-mk-footer-title]'` | Título que se estira |
34
+ | `lambda` | `.9` | Suavizado del scroll |
35
+
36
+ ## Controles
37
+
38
+ `destroy()`
39
+
40
+ ## CSS
41
+
42
+ El contenido de la página debe quedar encima: `main { position: relative; z-index: 1; background: … }`.
43
+
44
+ Con `prefers-reduced-motion: reduce` la receta deja el contenido en su estado final, sin animar.
@@ -0,0 +1,69 @@
1
+ import { registerMotion } from '../../core/gsap';
2
+ import { reduced } from '../../core/reduced';
3
+ import { whileVisible } from '../../core/loop';
4
+ import { damp, mapRange } from '../../core/math';
5
+ import { createScope } from '../../core/scope';
6
+ import { inert, warnOnce, type Instance } from '../../core/types';
7
+
8
+ /*
9
+ * spencergabor.com footer: fixed behind the page and revealed by a 100dvh
10
+ * "phantom" at the end of the flow. Smoothed scroll g = damp(g, scrollY, λ)
11
+ * drives the title's scaleY (0 → 1) and lifts the footer from "title on the
12
+ * bottom edge" to its resting place.
13
+ */
14
+
15
+ export interface StretchFooterOptions {
16
+ /** Spacer at the end of the flow; defaults to `[data-mk-phantom]` in the document. */
17
+ phantom?: HTMLElement | null;
18
+ /** Stretching title; defaults to `[data-mk-footer-title]` inside the footer. */
19
+ title?: HTMLElement | null;
20
+ /** Scroll smoothing (higher follows faster). */
21
+ lambda?: number;
22
+ }
23
+
24
+ export type StretchFooterInstance = Instance;
25
+
26
+ export function createStretchFooter(footer: HTMLElement | null, options: StretchFooterOptions = {}): StretchFooterInstance {
27
+ const phantom = options.phantom ?? document.querySelector<HTMLElement>('[data-mk-phantom]');
28
+ const title = options.title ?? footer?.querySelector<HTMLElement>('[data-mk-footer-title]') ?? null;
29
+ if (!footer || !phantom || !title) {
30
+ warnOnce('stretch-footer: needs the footer, a [data-mk-phantom] spacer and a [data-mk-footer-title]');
31
+ return inert();
32
+ }
33
+ registerMotion();
34
+ const scope = createScope();
35
+ footer.dataset.mkReady = '';
36
+ scope.add(() => {
37
+ delete footer.dataset.mkReady;
38
+ footer.style.removeProperty('--mk-footer-scale');
39
+ footer.style.removeProperty('--mk-footer-offset');
40
+ if (!footer.getAttribute('style')) footer.removeAttribute('style');
41
+ });
42
+ if (reduced()) return { destroy: () => scope.dispose() };
43
+
44
+ const lambda = options.lambda ?? 0.9;
45
+ let phantomTop = 0;
46
+ let titleBottom = 0;
47
+ let viewport = innerHeight;
48
+ let g = scrollY;
49
+
50
+ const measure = () => {
51
+ phantomTop = phantom.offsetTop;
52
+ titleBottom = title.offsetTop + title.offsetHeight;
53
+ viewport = innerHeight;
54
+ };
55
+ measure();
56
+ scope.observe(new ResizeObserver(measure)).observe(document.body);
57
+ scope.on(window, 'resize', measure);
58
+
59
+ const update = (dt: number) => {
60
+ g = damp(g, scrollY, lambda, dt);
61
+ const start = phantomTop - viewport;
62
+ footer.style.setProperty('--mk-footer-scale', mapRange(g, start, phantomTop, 0, 1, true).toFixed(4));
63
+ footer.style.setProperty('--mk-footer-offset', mapRange(g, start, phantomTop, viewport - titleBottom, 0, true).toFixed(2));
64
+ };
65
+ update(100); // settle on the current scroll position before the first frame
66
+ scope.add(whileVisible(phantom, update, '0px'));
67
+
68
+ return { destroy: () => scope.dispose() };
69
+ }
@@ -0,0 +1,28 @@
1
+ /*
2
+ * stretch-footer — spencergabor.com (Footer). The page content must sit above it:
3
+ * give <main> `position: relative; z-index: 1; background: <page colour>`.
4
+ */
5
+
6
+ [data-mk-phantom] {
7
+ height: 100dvh;
8
+ pointer-events: none;
9
+ }
10
+
11
+ [data-mk~="stretch-footer"],
12
+ .mk-stretch-footer {
13
+ position: fixed;
14
+ inset: auto 0 0;
15
+ z-index: 0;
16
+ height: 100dvh;
17
+ display: flex;
18
+ flex-direction: column;
19
+ justify-content: flex-end;
20
+ translate: 0 calc(var(--mk-footer-offset, 0) * 1px);
21
+ }
22
+
23
+ [data-mk-footer-title] {
24
+ align-self: flex-start;
25
+ line-height: 0.8;
26
+ transform: scaleY(var(--mk-footer-scale, 1));
27
+ transform-origin: 50% 100%;
28
+ }
@@ -0,0 +1,39 @@
1
+ # tabs
2
+
3
+ Un indicador se desliza hasta la pestaña elegida en 0.45 s con la curva signature. Si la lista hace scroll, centra la pestaña. Flechas (según orientación), Inicio y Fin cambian la selección.
4
+
5
+ Origen: Stepbro — PrettyTabs (`tabs`, `tabs-indicator` en el kit: `Flip.fit`, 0.45 s, `prettyTabsSlide`).
6
+
7
+ ## Vanilla / Astro
8
+
9
+ ```html
10
+ <div data-mk="tabs" class="mk-segmented">
11
+ <button data-mk-tab aria-selected="true">Día</button>
12
+ <button data-mk-tab>Semana</button>
13
+ <button data-mk-tab>Mes</button>
14
+ <span data-mk-indicator></span>
15
+ </div>
16
+ ```
17
+
18
+ Escucha el cambio con `mk:tab` (`event.detail.index`, `event.detail.tab`). Sin `autoInit`: `createTabs(el, opciones)`.
19
+
20
+ ## React
21
+
22
+ ```tsx
23
+ const ref = useRef<HTMLDivElement>(null);
24
+ useTabs(ref, { onSelect: (i) => setView(views[i]) });
25
+ ```
26
+
27
+ ## Opciones
28
+
29
+ | Opción | Por defecto | Qué hace |
30
+ |---|---|---|
31
+ | `orientation` | `'horizontal'` | `'vertical'` usa ↑ ↓ y pone `aria-orientation` |
32
+ | `duration` | `.45` | Segundos del deslizamiento |
33
+ | `onSelect` | — | `(index, tab) => void` |
34
+
35
+ ## Controles
36
+
37
+ `select(index)`, `index`, `destroy()`
38
+
39
+ La receta pone `role="tablist"`, `role="tab"`, `aria-selected` y `tabindex` y los restaura al destruirse. Con movimiento reducido el indicador salta sin deslizarse.
@@ -0,0 +1,91 @@
1
+ import { gsap, registerMotion } from '../../core/gsap';
2
+ import { createScope } from '../../core/scope';
3
+ import { inert, warnOnce, type Instance } from '../../core/types';
4
+ import { TIMING } from '../../core/timing';
5
+ import { slideIndicator } from '../../motion/indicator';
6
+
7
+ /*
8
+ * Stepbro's PrettyTabs: an indicator slides onto the selected tab in 0.45 s on
9
+ * the signature curve; a scrolling tab list centres the selection. Arrow keys
10
+ * (by orientation), Home and End move the selection.
11
+ */
12
+
13
+ export interface TabsOptions {
14
+ orientation?: 'horizontal' | 'vertical';
15
+ duration?: number;
16
+ onSelect?: (index: number, tab: HTMLElement) => void;
17
+ }
18
+
19
+ export interface TabsInstance extends Instance {
20
+ select(index: number): void;
21
+ readonly index: number;
22
+ }
23
+
24
+ export function createTabs(root: HTMLElement | null, options: TabsOptions = {}): TabsInstance {
25
+ const tabs = root ? [...root.querySelectorAll<HTMLElement>('[data-mk-tab]')] : [];
26
+ if (!root || !tabs.length) {
27
+ warnOnce('tabs: needs at least one [data-mk-tab]');
28
+ return inert({ select() {}, index: 0 });
29
+ }
30
+ registerMotion();
31
+ const { orientation = 'horizontal', duration = TIMING.indicator } = options;
32
+ const indicator = root.querySelector<HTMLElement>('[data-mk-indicator]');
33
+ const scope = createScope();
34
+ const ctx = gsap.context(() => {});
35
+ let index = Math.max(0, tabs.findIndex((tab) => tab.getAttribute('aria-selected') === 'true'));
36
+
37
+ scope.attr(root, 'role', 'tablist');
38
+ if (orientation === 'vertical') scope.attr(root, 'aria-orientation', 'vertical');
39
+
40
+ const render = (immediate: boolean) => {
41
+ tabs.forEach((tab, i) => {
42
+ scope.attr(tab, 'role', 'tab');
43
+ scope.attr(tab, 'aria-selected', String(i === index));
44
+ scope.attr(tab, 'tabindex', i === index ? '0' : '-1');
45
+ });
46
+ if (indicator) ctx.add(() => slideIndicator(indicator, tabs[index], { immediate, duration, scroller: root, orientation }));
47
+ };
48
+
49
+ const select = (next: number, focus = false) => {
50
+ const target = Math.min(tabs.length - 1, Math.max(0, next));
51
+ if (focus) tabs[target].focus();
52
+ if (target === index) return;
53
+ index = target;
54
+ render(false);
55
+ root.dispatchEvent(new CustomEvent('mk:tab', { detail: { index, tab: tabs[index] }, bubbles: true }));
56
+ options.onSelect?.(index, tabs[index]);
57
+ };
58
+
59
+ scope.on(root, 'click', (event) => {
60
+ const tab = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-tab]');
61
+ const at = tab ? tabs.indexOf(tab) : -1;
62
+ if (at >= 0) select(at);
63
+ });
64
+ scope.on(root, 'keydown', (event) => {
65
+ const key = (event as KeyboardEvent).key;
66
+ const back = orientation === 'vertical' ? 'ArrowUp' : 'ArrowLeft';
67
+ const forward = orientation === 'vertical' ? 'ArrowDown' : 'ArrowRight';
68
+ const moves: Record<string, number> = { [back]: index - 1, [forward]: index + 1, Home: 0, End: tabs.length - 1 };
69
+ if (!(key in moves)) return;
70
+ event.preventDefault();
71
+ select(moves[key], true);
72
+ });
73
+ if (typeof ResizeObserver === 'function') {
74
+ scope.observe(new ResizeObserver(() => render(true))).observe(root);
75
+ }
76
+
77
+ render(true);
78
+ root.dataset.mkReady = '';
79
+
80
+ return {
81
+ select: (next: number) => select(next),
82
+ get index() {
83
+ return index;
84
+ },
85
+ destroy() {
86
+ scope.dispose();
87
+ ctx.revert();
88
+ delete root.dataset.mkReady;
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,19 @@
1
+ /* tabs: the indicator sits under the tabs at 0,0 of the list; the recipe sizes and moves it. */
2
+ [data-mk~='tabs'] {
3
+ position: relative;
4
+ }
5
+
6
+ [data-mk~='tabs'] [data-mk-tab] {
7
+ position: relative;
8
+ z-index: 1;
9
+ }
10
+
11
+ [data-mk~='tabs'] [data-mk-indicator] {
12
+ position: absolute;
13
+ top: 0;
14
+ left: 0;
15
+ z-index: 0;
16
+ pointer-events: none;
17
+ border-radius: var(--mk-tabs-indicator-radius, var(--mk-radius-full));
18
+ background: var(--mk-tabs-indicator, var(--mk-accent-soft));
19
+ }
@@ -0,0 +1,35 @@
1
+ # text-roll
2
+
3
+ Un título que gira como una tragaperras: cada letra es un carrete (la letra y una copia debajo) que da entre 9 y 15 vueltas al azar; todas frenan con `expo.out` en 2 s, 40 ms entre letras en orden aleatorio, y se quedan en la misma letra. Puede repetirse cada 6 s mientras se ve y no hay scroll (espera 1 s de calma).
4
+
5
+ Origen: Siena Film Foundation, títulos de película de la portada (copia `cloneText2`, `L.utils.random(8, 14)` repeticiones).
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <h1 data-mk="text-roll">My Project X</h1>
11
+ <h2 data-mk="text-roll" data-mk-trigger="hover">Explorar</h2>
12
+ <h1 data-mk="text-roll" data-mk-interval="6000">Savoy</h1>
13
+ ```
14
+
15
+ ## React
16
+
17
+ ```tsx
18
+ const ref = useRef<HTMLHeadingElement>(null);
19
+ const roll = useTextRoll(ref, { trigger: 'manual' });
20
+ <button onClick={() => roll.current?.play()}>Girar</button>
21
+ ```
22
+
23
+ ## API
24
+
25
+ `createTextRoll(el, { duration, stagger, turns, trigger, delay, interval })` → `{ play(), timelines, destroy }`.
26
+
27
+ | Opción | Por defecto | |
28
+ |---|---|---|
29
+ | `trigger` | `'load'` | `'scroll'`, `'hover'`, `'manual'` |
30
+ | `interval` | `0` | ms entre repeticiones (Siena: 6000) |
31
+ | `turns` | `[8, 14]` | repeticiones de la vuelta de cada letra |
32
+
33
+ ## Accesibilidad
34
+
35
+ La raíz recibe `aria-label` y las letras partidas quedan ocultas a lectores. Con movimiento reducido el texto no se parte.