@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,22 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ export interface JoinedCtaOptions { duration?: number; }
5
+ export interface JoinedCtaInstance extends Instance { expand(): void; collapse(): void; readonly expanded: boolean; }
6
+
7
+ /** Silvia's three contact capsules joining into one. Motion is entirely CSS. */
8
+ export function createJoinedCta(root: HTMLElement | null, options: JoinedCtaOptions = {}): JoinedCtaInstance {
9
+ if (!root) return inert({ expand() {}, collapse() {}, expanded: false });
10
+ const scope = createScope();
11
+ let expanded = false;
12
+ let pointer = false;
13
+ scope.attr(root, 'style', root.getAttribute('style'));
14
+ scope.attr(root, 'data-mk-ready', '');
15
+ if (Number.isFinite(options.duration)) root.style.setProperty('--mk-joined-duration', `${Math.max(0, options.duration!)}s`);
16
+ const set = (value: boolean) => { if (!scope.disposed) { expanded = value; scope.attr(root, 'data-mk-expanded', value ? '' : null); } };
17
+ scope.on(root, 'pointerenter', () => { pointer = true; set(true); });
18
+ scope.on(root, 'pointerleave', () => { pointer = false; if (!root.contains(document.activeElement)) set(false); });
19
+ scope.on(root, 'focusin', () => set(true));
20
+ scope.on(root, 'focusout', (event) => { if (!pointer && !root.contains((event as FocusEvent).relatedTarget as Node | null)) set(false); });
21
+ return { expand: () => set(true), collapse: () => set(false), get expanded() { return expanded; }, destroy: () => scope.dispose() };
22
+ }
@@ -0,0 +1,17 @@
1
+ .mk-joined-cta { --mk-joined-duration: .5s; --mk-joined-bg: var(--mk-text); --mk-joined-color: var(--mk-bg); position: relative; display: inline-flex; align-items: center; justify-content: center; width: 17.7rem; height: 5.9rem; max-width: 100%; border: 0; padding: 0; background: transparent; text-decoration: none; color: var(--mk-joined-color); cursor: pointer; font: inherit; isolation: isolate; border-radius: var(--mk-radius-full); }
2
+ .mk-joined-cta::before { content: ''; position: absolute; left: 0; width: 7.3rem; height: 5.9rem; border-radius: 100px; background: var(--mk-joined-bg); z-index: -1; transition: width var(--mk-joined-duration) ease-in-out; }
3
+ .mk-joined-cta > span { position: relative; display: grid; place-items: center; flex-shrink: 0; background: var(--mk-joined-bg); border-radius: 100px; height: 5.9rem; transition: left var(--mk-joined-duration) ease-in-out, height var(--mk-joined-duration) ease-in-out, transform var(--mk-joined-duration) ease-in-out, background var(--mk-joined-duration); }
4
+ .mk-joined-cta > span:nth-child(1) { width: 7.3rem; left: 0; }
5
+ .mk-joined-cta > span:nth-child(2) { width: 4.5rem; left: 0; z-index: 2; }
6
+ .mk-joined-cta > span:nth-child(3) { width: 5.9rem; height: 3.05rem; left: -1.45rem; transform: rotate(-90deg); }
7
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded])::before { width: 17.7rem; }
8
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(1) { left: 3.2rem; }
9
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(2) { left: -.75rem; background: transparent; }
10
+ .mk-joined-cta:is(:hover,:focus-visible,[data-mk-expanded]) > span:nth-child(3) { left: -3.65rem; height: 5.9rem; transform: rotate(0); }
11
+ .mk-joined-cta:focus-visible { outline: 2px solid var(--mk-accent); outline-offset: 5px; }
12
+ @media (hover: none), (max-width: 420px) {
13
+ .mk-joined-cta { width: auto; height: auto; padding: 1.25rem 2rem; gap: .3em; background: var(--mk-joined-bg); }
14
+ .mk-joined-cta::before { display: none; }
15
+ .mk-joined-cta > span:nth-child(n) { width: auto; height: auto; left: 0; transform: none; background: transparent; }
16
+ }
17
+ @media (prefers-reduced-motion: reduce) { .mk-joined-cta::before, .mk-joined-cta > span { transition: none; } }
@@ -5,13 +5,11 @@ Letras que juegan:
5
5
  - **`scale`**: al pasar el ratón por la palabra cada letra crece (`scale 1.3`, sube 5 px) con un resorte con rebote (Motion 300/10), 30 ms después de la anterior; vuelve igual al salir.
6
6
  - **`blink`**: cada letra pasa a un color pastel y vuelve (0.6 s, `ease-in-out`) con retardos desordenados (.1, .6, .2, 1, .4, 1.2, .15, .8, .3 s), dos veces. Arranca 0.6 s después de crearse y al pasar el ratón.
7
7
 
8
- Origen: Dousan Miao, el titular de la portada («impactful»: `hero-letters-blink`, `hero-scale-letters`).
9
-
10
8
  ## HTML / Astro
11
9
 
12
10
  ```html
13
11
  <h1>I design <span data-mk="letters" data-mk-effect="blink">impactful</span> products</h1>
14
- <span data-mk="letters" data-mk-effect="scale">Dousan</span>
12
+ <span data-mk="letters" data-mk-effect="scale">letters</span>
15
13
  ```
16
14
 
17
15
  ## React
@@ -23,7 +21,7 @@ useLetters(ref, { effect: 'blink', colors: ['#FFD6FF', '#C8B6FF'] });
23
21
 
24
22
  ## API
25
23
 
26
- `createLetters(el, { effect, colors, autoplay })` → `{ play(), destroy }`. `colors` por defecto: los pastel de Dousan (`#FFD6FF #E7C6FF #C8B6FF #B8C0FF #BBD0FF`).
24
+ `createLetters(el, { effect, colors, autoplay })` → `{ play(), destroy }`. `colors` por defecto: los pastel (`#FFD6FF #E7C6FF #C8B6FF #B8C0FF #BBD0FF`).
27
25
 
28
26
  ## Accesibilidad
29
27
 
@@ -6,15 +6,15 @@ import { TIMING } from '../../core/timing';
6
6
  import { inert, warnOnce, type Instance } from '../../core/types';
7
7
 
8
8
  /*
9
- * Dousan's hero words. 'scale': on hover every letter springs up (scale 1.3,
9
+ * hero words. 'scale': on hover every letter springs up (scale 1.3,
10
10
  * y −5 px, Motion 300/10), 30 ms after the previous one, and back on leave.
11
11
  * 'blink': every letter flashes a pastel and returns (0.6 s, ease-in-out) with
12
- * Dousan's scattered delays, twice; it plays 0.6 s after start and on hover.
12
+ * scattered delays, twice; it plays 0.6 s after start and on hover.
13
13
  */
14
14
 
15
15
  export interface LettersOptions {
16
16
  effect?: 'scale' | 'blink';
17
- /** Blink colours (default Dousan's pastels). */
17
+ /** Blink colours. */
18
18
  colors?: string[];
19
19
  /** Blink once 0.6 s after start (default true). */
20
20
  autoplay?: boolean;
@@ -0,0 +1,31 @@
1
+ # local-clock
2
+
3
+ La hora de otra zona en vivo, como el «20:48 local time» del contacto de DOVV Studio. Se actualiza justo al cambiar el minuto (o el segundo, con `seconds`). En un `<time>` también mantiene el atributo `datetime`.
4
+
5
+ ## Vanilla / Astro
6
+
7
+ ```html
8
+ <p><span class="mk-status__dot"></span> <time data-mk="local-clock" data-mk-time-zone="America/Mexico_City">--:--</time> hora local</p>
9
+ ```
10
+
11
+ Con `autoInit()` las opciones van como `data-mk-<opción>`. Sin `autoInit`: `createLocalClock(el, opciones)`, que devuelve `text` y `destroy()`. El contenido inicial (por ejemplo `--:--`) se ve sin JavaScript y vuelve con `destroy()`.
12
+
13
+ ## React
14
+
15
+ ```tsx
16
+ const ref = useRef<HTMLTimeElement>(null);
17
+ useLocalClock(ref, { timeZone: 'America/Argentina/Buenos_Aires' });
18
+ return <time ref={ref}>--:--</time>;
19
+ ```
20
+
21
+ ## Opciones
22
+
23
+ | Opción | Por defecto | Qué hace |
24
+ |---|---|---|
25
+ | `timeZone` | la del visitante | Zona IANA |
26
+ | `locale` | `'es'` | Idioma del formato |
27
+ | `hour12` | `false` | 12 h con a. m./p. m. |
28
+ | `seconds` | `false` | Mostrar y actualizar segundos |
29
+ | `format` | — | `Intl.DateTimeFormatOptions` completo |
30
+
31
+ Una zona o un idioma desconocidos usan los del visitante en lugar de fallar. No anuncia cada cambio a lectores de pantalla.
@@ -0,0 +1,61 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ /*
5
+ * Local clock (DOVV Studio contact: "20:48 local time"): the time in a given
6
+ * zone, refreshed on the minute (or the second). A <time> element also gets
7
+ * its datetime attribute. The text node is replaced; destroy() puts it back.
8
+ */
9
+
10
+ export interface LocalClockOptions {
11
+ /** IANA zone, e.g. "America/Mexico_City". The visitor's zone by default. */
12
+ timeZone?: string;
13
+ locale?: string;
14
+ hour12?: boolean;
15
+ seconds?: boolean;
16
+ /** Full control over the format (overrides hour12/seconds). */
17
+ format?: Intl.DateTimeFormatOptions;
18
+ }
19
+
20
+ export interface LocalClockInstance extends Instance {
21
+ /** Text currently shown. */
22
+ readonly text: string;
23
+ }
24
+
25
+ export function createLocalClock(root: HTMLElement | null, options: LocalClockOptions = {}): LocalClockInstance {
26
+ if (!root) return inert({ text: '' });
27
+ const scope = createScope();
28
+ const original = [...root.childNodes];
29
+ scope.add(() => root.replaceChildren(...original));
30
+ scope.attr(root, 'data-mk-ready', '');
31
+ let formatter: Intl.DateTimeFormat;
32
+ try {
33
+ formatter = new Intl.DateTimeFormat(options.locale ?? 'es', options.format ?? {
34
+ hour: '2-digit',
35
+ minute: '2-digit',
36
+ ...(options.seconds ? { second: '2-digit' } : {}),
37
+ hour12: options.hour12 ?? false,
38
+ timeZone: options.timeZone,
39
+ });
40
+ } catch {
41
+ // Unknown zone or locale: the visitor's own.
42
+ formatter = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit', hour12: options.hour12 ?? false });
43
+ }
44
+ let text = '';
45
+ const isTime = root instanceof HTMLTimeElement;
46
+ const render = () => {
47
+ const now = new Date();
48
+ text = formatter.format(now);
49
+ if (root.textContent !== text) root.textContent = text;
50
+ if (isTime) scope.attr(root, 'datetime', now.toISOString());
51
+ };
52
+ const step = options.seconds || options.format?.second ? 1000 : 60000;
53
+ const schedule = () => {
54
+ // Land just after the next boundary so the minute flips with the system clock.
55
+ const wait = step - (Date.now() % step) + 20;
56
+ scope.timeout(() => { if (!scope.disposed) { render(); schedule(); } }, wait);
57
+ };
58
+ render();
59
+ schedule();
60
+ return { get text() { return text; }, destroy: () => scope.dispose() };
61
+ }
@@ -2,8 +2,6 @@
2
2
 
3
3
  Una banda infinita: el primer hijo (el grupo) se copia hasta llenar la banda más una vez y cada copia se desplaza su propio tamaño en bucle lineal, sin cortes. Velocidad: 2 s por elemento del grupo (cuenta hasta 14).
4
4
 
5
- Origen: Siena Film Foundation, marquesina vertical de la página de película. Siena la usa en vertical; aquí el eje por defecto es horizontal (`axis: 'y'` para la de Siena).
6
-
7
5
  ## HTML / Astro
8
6
 
9
7
  ```html
@@ -4,14 +4,14 @@ import { TIMING } from '../../core/timing';
4
4
  import { inert, warnOnce, type Instance } from '../../core/types';
5
5
 
6
6
  /*
7
- * Siena's marquee: the first child (the group) is copied until the strip is
7
+ * marquee: the first child (the group) is copied until the strip is
8
8
  * full, plus one; every copy slides its own length in a linear loop, so the
9
9
  * strip never shows a seam. Speed: 2 s per item in the group (at most 14).
10
10
  * Copies are aria-hidden and inert: readers and keyboards meet the group once.
11
11
  */
12
12
 
13
13
  export interface MarqueeOptions {
14
- /** 'x' (default) or 'y' (Siena's vertical strip). */
14
+ /** 'x' (default) or 'y'. */
15
15
  axis?: 'x' | 'y';
16
16
  /** Seconds per item. */
17
17
  speed?: number;
@@ -47,7 +47,7 @@ export function createMarquee(root: HTMLElement | null, options: MarqueeOptions
47
47
  if (!entrances()) return;
48
48
  const size = axis === 'x' ? group.offsetWidth : group.offsetHeight;
49
49
  const room = axis === 'x' ? root.clientWidth : root.clientHeight;
50
- // Unmeasurable (hidden, not laid out yet): five copies, like Siena.
50
+ // Unmeasurable (hidden, not laid out yet): five copies.
51
51
  const count = size > 0 ? Math.max(1, Math.ceil(room / size)) + 1 : 5;
52
52
  for (let i = 0; i < count; i++) {
53
53
  const copy = group.cloneNode(true) as HTMLElement;
@@ -1,4 +1,4 @@
1
- /* marquee — Siena's strip: every copy of the group slides its own length in a linear loop. */
1
+ /* marquee — strip: every copy of the group slides its own length in a linear loop. */
2
2
  .mk-marquee {
3
3
  display: flex;
4
4
  gap: var(--mk-marquee-gap, 0px);
@@ -1,13 +1,11 @@
1
1
  # mask-text
2
2
 
3
- El texto sale de su máscara: cada línea (o palabra, o letra) vive en un recorte y sube desde abajo (`yPercent 100 → 0`, 1.2 s `expo.out`, retardo 0.1 s, 20 ms entre piezas) cuando el 80 % del bloque está en pantalla. Al salir de la pantalla vuelve a esconderse, así que se repite cada vez que regresa (salvo `once`). SplitText lo vuelve a partir al cambiar el tamaño o al cargar la fuente, algo que OFFFICE no hacía.
4
-
5
- Origen: OFFFICE (offficestud.io) `masked-type`; Siena usa el mismo gesto en los títulos de película partiendo de 120 %.
3
+ El texto sale de su máscara: cada línea (o palabra, o letra) vive en un recorte y sube desde abajo (`yPercent 100 → 0`, 1.2 s `expo.out`, retardo 0.1 s, 20 ms entre piezas) cuando el 80 % del bloque está en pantalla. Al salir de la pantalla vuelve a esconderse, así que se repite cada vez que regresa (salvo `once`). SplitText lo vuelve a partir al cambiar el tamaño o al cargar la fuente.
6
4
 
7
5
  ## HTML / Astro
8
6
 
9
7
  ```html
10
- <h2 data-mk="mask-text">Office for future furnishing</h2>
8
+ <h2 data-mk="mask-text">Texto que aparece desde su máscara</h2>
11
9
  <p data-mk="mask-text" data-mk-by="words" data-mk-once>Texto que entra palabra a palabra.</p>
12
10
  <h1 data-mk="mask-text" data-mk-y="120" data-mk-trigger="load">Savoy</h1>
13
11
  ```
@@ -32,7 +30,7 @@ const text = useMaskText(ref, { by: 'lines' });
32
30
  | `trigger` | `'scroll'` | `'load'`, `'manual'` |
33
31
  | `once` | `false` | no vuelve a esconderse al salir |
34
32
 
35
- `hide()` es la salida de OFFFICE: todas bajan a su recorte con 2 ms entre piezas.
33
+ `hide()` es la salida: todas bajan a su recorte con 2 ms entre piezas.
36
34
 
37
35
  ## Accesibilidad
38
36
 
@@ -5,7 +5,7 @@ import { shows, thresholds } from '../../core/visible';
5
5
  import { inert, warnOnce, type Instance } from '../../core/types';
6
6
 
7
7
  /*
8
- * OFFFICE's masked type (and Siena's case titles): the text is split into lines,
8
+ * masked type: the text is split into lines,
9
9
  * words or letters, each inside its own clip, and rises from below it
10
10
  * (yPercent 100 → 0, 1.2 s expo.out, 0.1 s delay, 20 ms apart) once 80 % of it
11
11
  * is on screen. It hides again when it leaves the screen (unless `once`), so it
@@ -14,7 +14,7 @@ import { inert, warnOnce, type Instance } from '../../core/types';
14
14
 
15
15
  export interface MaskTextOptions {
16
16
  by?: 'lines' | 'words' | 'chars';
17
- /** Start offset in % of each piece (Siena's case titles: 120). */
17
+ /** Start offset in % of each piece. */
18
18
  y?: number;
19
19
  duration?: number;
20
20
  ease?: string;
@@ -22,13 +22,13 @@ export interface MaskTextOptions {
22
22
  stagger?: number;
23
23
  /** 'scroll' (default) plays when 80 % is visible; 'load' now; 'manual' waits for play(). */
24
24
  trigger?: 'scroll' | 'load' | 'manual';
25
- /** Play once and stay (OFFFICE's data-a-mod). */
25
+ /** Play once and stay. */
26
26
  once?: boolean;
27
27
  }
28
28
 
29
29
  export interface MaskTextInstance extends Instance {
30
30
  play(): void;
31
- /** OFFFICE's exit: back down into the clips, 2 ms apart. */
31
+ /** exit: back down into the clips, 2 ms apart. */
32
32
  hide(): void;
33
33
  readonly split: SplitText | null;
34
34
  }
@@ -1,8 +1,6 @@
1
1
  # parallax
2
2
 
3
- La foto se mueve dentro de su marco mientras el marco cruza la pantalla: escalada a 1.2 (1.3 con `amount` distinto de 1, salvo en el móvil), viaja de −10 % a +10 % de su altura × `amount`. Sigue al scroll de la página sin retraso (Siena suavizaba la página entera con Lenis; con scroll nativo un suavizado propio deja la foto arrastrándose detrás del marco — `lerp: .1` si lo quieres). Una foto arriba del todo de la página cuenta desde `top top`, así que empieza en −10 %.
4
-
5
- Origen: Siena Film Foundation, fotos de las páginas de película y About (`translateY(-10·a + p·20·a %) scale(1.2)`).
3
+ La foto se mueve dentro de su marco mientras cruza la pantalla: escala 1.2 (1.3 con `amount` distinto de 1 en escritorio), recorrido de −10 % a +10 % de su altura × `amount`. Sigue al scroll sin retraso adicional; `lerp: .1` permite suavizado opcional. Arriba de la página comienza en `top top`.
6
4
 
7
5
  ## HTML / Astro
8
6
 
@@ -5,7 +5,7 @@ import { TIMING } from '../../core/timing';
5
5
  import { inert, warnOnce, type Instance } from '../../core/types';
6
6
 
7
7
  /*
8
- * Siena's photo parallax: the frame clips, the picture inside is scaled up
8
+ * photo parallax: the frame clips, the picture inside is scaled up
9
9
  * (1.2, or 1.3 for a stronger amount on wide screens) and travels from −10 % to
10
10
  * +10 % of its height × amount while the frame crosses the screen (a frame at
11
11
  * the top of the page counts from "top top", so it starts at −10 %). Target:
@@ -2,8 +2,6 @@
2
2
 
3
3
  Un proyecto como pila de 2–3 capas giradas que escalan con rebote al entrar en pantalla y reaccionan al hover.
4
4
 
5
- Origen: spencergabor.com — Overview / GridEntry.
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -6,7 +6,7 @@ import type { PileLayer } from './pile-math';
6
6
  export { pileLayers, type PileLayer } from './pile-math';
7
7
 
8
8
  /*
9
- * spencergabor.com project grid: a pile of two or three rotated layers that
9
+ * pile: a pile of two or three rotated layers that
10
10
  * scale in (CSS, elastic) once the pile scrolls into view. The rotations are
11
11
  * generated once with `pileLayers()` and written into the markup with
12
12
  * `pileStyle()`, so server and client render the same pile.
@@ -15,7 +15,7 @@ function mulberry32(seed: number) {
15
15
  };
16
16
  }
17
17
 
18
- /** Spencer's stack transforms, seeded so SSR output is stable. Layer 0 is the back. */
18
+ /** stack transforms, seeded so SSR output is stable. Layer 0 is the back. */
19
19
  export function pileLayers(count: number, seed: number): PileLayer[] {
20
20
  const rand = mulberry32(seed + 1);
21
21
  const range = (min: number, max: number) => rand() * (max - min) + min;
@@ -1,4 +1,4 @@
1
- /* pile — stack recipe from spencergabor.com (GridEntry) */
1
+ /* pile — stacked card geometry */
2
2
 
3
3
  .mk-pile {
4
4
  display: grid;
@@ -2,8 +2,6 @@
2
2
 
3
3
  Botón píldora con escala elástica al pasar (1.05 + sombra) y al pulsar (.95), y `.mk-stretch` para enlaces que se estiran al pasar.
4
4
 
5
- Origen: spencergabor.com — Button.vue.
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -1,4 +1,4 @@
1
- /* pill — spencergabor.com button: elastic scale and shadow */
1
+ /* pill — pill: elastic scale and shadow */
2
2
 
3
3
  .mk-pill {
4
4
  display: inline-flex;
@@ -34,7 +34,7 @@
34
34
  }
35
35
  }
36
36
 
37
- /* Stretch on hover (spencergabor.com footer links) */
37
+ /* Stretch on hover (stretch-footer links) */
38
38
  .mk-stretch {
39
39
  display: inline-block;
40
40
  transform-origin: 50% 100%;
@@ -2,8 +2,6 @@
2
2
 
3
3
  Un elemento entra escalando de .7 a 1 con rebote.
4
4
 
5
- Origen: spencergabor.com — imágenes de proyecto.
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -2,7 +2,7 @@ import { gsap, ELASTIC, ScrollTrigger, registerMotion } from '../../core/gsap';
2
2
  import { entrances } from '../../core/reduced';
3
3
  import { inert, warnOnce, type Instance } from '../../core/types';
4
4
 
5
- /* Elastic scale-in for a single element (spencergabor.com project images, scale .7 → 1). */
5
+ /* Elastic scale-in for a single element (scale .7 → 1). */
6
6
 
7
7
  export interface PopOptions {
8
8
  scale?: number;
@@ -2,8 +2,6 @@
2
2
 
3
3
  Un botón redondo cuyas opciones salen en arco con rebote; cerrado, las opciones quedan `inert`. Cierra con Escape y al pulsar fuera.
4
4
 
5
- Origen: spencergabor.com — selector de color.
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -2,7 +2,7 @@ import { createScope } from '../../core/scope';
2
2
  import { inert, warnOnce, type Instance } from '../../core/types';
3
3
 
4
4
  /*
5
- * spencergabor.com colour picker, generalised: options fan out in an arc from a
5
+ * radial-menu: options fan out in an arc from a
6
6
  * round toggle (CSS trig on --mk-i), elastic, 50 ms apart. Closed options are
7
7
  * `inert`, so they are skipped by Tab and screen readers.
8
8
  */
@@ -1,4 +1,4 @@
1
- /* radial-menu — spencergabor.com colour picker, generalised. Scoped to its root:
1
+ /* radial-menu — radial-menu. Scoped to its root:
2
2
  [data-mk-item] and [data-mk-open] are also used by other recipes and components. */
3
3
 
4
4
  [data-mk~="radial-menu"],
@@ -2,8 +2,6 @@
2
2
 
3
3
  Cuando una lista cambia (ordenar, filtrar, añadir), los elementos viajan a su nuevo lugar con un resorte (0.62 s, razón .52) y los nuevos crecen desde 94 %.
4
4
 
5
- Origen: Stepbro — task-layout (`layoutReflow`, `FA(.52)` en el kit).
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -15,9 +13,9 @@ Origen: Stepbro — task-layout (`layoutReflow`, `FA(.52)` en el kit).
15
13
 
16
14
  ```js
17
15
  const reflow = createReflow(lista);
18
- reflow.capture(); // antes de cambiar el DOM
19
- reordenar(lista); // tu cambio
20
- reflow.play(); // después
16
+ reflow.capture(); // antes de cambiar el DOM
17
+ reordenar(lista); // tu cambio
18
+ reflow.play(); // después
21
19
  ```
22
20
 
23
21
  `data-flip-id` estable empareja cada elemento con su posición anterior. No pongas los elementos en `position: absolute`.
@@ -4,7 +4,7 @@ import { TIMING } from '../../core/timing';
4
4
  import { captureLayout, reflowLayout, type CapturedLayout } from '../../motion/layout';
5
5
 
6
6
  /*
7
- * Stepbro's layout reflow on a list: call `capture()` before changing the DOM
7
+ * layout reflow on a list: call `capture()` before changing the DOM
8
8
  * and `play()` after it; moved items travel on a spring and new ones grow in.
9
9
  * Give items a stable `data-flip-id` so moves pair up.
10
10
  */
@@ -1,6 +1,6 @@
1
1
  # repel-bento
2
2
 
3
- Bento con entrada spring y repulsión de tarjetas vecinas (Dousan).
3
+ Bento con entrada spring y repulsión de tarjetas vecinas.
4
4
 
5
5
  ```html
6
6
  <div class="mk-repel-bento" data-mk="repel-bento"><article>Uno</article><article>Dos</article></div>
@@ -14,8 +14,8 @@ const instance = createRepelBento(root);
14
14
  instance.destroy();
15
15
  ```
16
16
 
17
- `distance` máximo 30 px; `breakpoint` 1024. Entrada con spring 60/15 y desfase .006 s; repulsión de vecinos min(5000/distancia,30) con spring 300/25. `play()` repite la entrada; `reset()` vuelve al reposo. Bajo el breakpoint, puntero grueso y movimiento reducido no hay repulsión. Origen: Dousan `desktop-card-enter` y `neighbor-card-repulsion`.
17
+ `distance` máximo 30 px; `breakpoint` 1024. Entrada con spring 60/15 y desfase .006 s; repulsión de vecinos min(5000/distancia,30) con spring 300/25. `play()` repite la entrada; `reset()` vuelve al reposo. Bajo el breakpoint, puntero grueso y movimiento reducido no hay repulsión.
18
18
 
19
19
  Vanilla declarativo: `autoInit(root)`. React: `useRepelBento(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.
20
20
 
21
- Desde 0.8.0. Parámetros investigados en `REA - Dousan OFFFICE Siena/`; implementación propia, sin código ni assets de esos sitios.
21
+ Desde 0.8.0.
@@ -2,8 +2,6 @@
2
2
 
3
3
  Un velo del color de la superficie la cubre y se abre como una onda desde un punto; luego se desvanece. El sobrepaso de 2 hace que la apertura pase la esquina más lejana a mitad de camino, y el final queda suave.
4
4
 
5
- Origen: Stepbro — reveal (`reveal-wipe` en el kit; usado en la navegación, el calendario y la hora). Velocidades `fast` 0.5/0.25 s, `normal` 0.7/0.35, `slow` 1.05/0.5, `slower` 1.6/0.75 (recorrido/fundido), `power2.out` y fundido `power1.out`.
6
-
7
5
  ## Vanilla / Astro
8
6
 
9
7
  ```html
@@ -3,7 +3,7 @@ import { inert, warnOnce, type Instance } from '../../core/types';
3
3
  import { revealFrom, type RevealHandle, type RevealSpeed } from '../../motion/reveal';
4
4
 
5
5
  /*
6
- * Stepbro's reveal on an element: a veil in its own colour opens like a wave
6
+ * reveal on an element: a veil in its own colour opens like a wave
7
7
  * from the origin (an element, else the last pointer position, else the centre).
8
8
  */
9
9
 
@@ -1,6 +1,6 @@
1
1
  # rotator
2
2
 
3
- Rotador de testimonios con autoplay, pausa al enfocar y controles manuales (Dousan).
3
+ Rotador de testimonios con autoplay, pausa al enfocar y controles manuales.
4
4
 
5
5
  ```html
6
6
  <div class="mk-rotator" data-mk="rotator"><blockquote>Uno</blockquote><blockquote>Dos</blockquote></div>
@@ -14,8 +14,8 @@ const instance = createRotator(root);
14
14
  instance.destroy();
15
15
  ```
16
16
 
17
- `interval` 8000 ms, `autoplay` true, `index` 0, `onChange(index)`. `select(index)`, `next()`, `previous()`, `index`. Pausa al señalar, enfocar o esconder la pestaña; los controles reinician el intervalo. Solo la pieza activa es visible y accesible. Entrada opacity/y 5 a 0, .25 s. Origen: testimonios de Dousan.
17
+ `interval` 8000 ms, `autoplay` true, `index` 0, `onChange(index)`. `select(index)`, `next()`, `previous()`, `index`. Pausa al señalar, enfocar o esconder la pestaña; los controles reinician el intervalo. Solo la pieza activa es visible y accesible. Entrada opacity/y 5 a 0, .25 s.
18
18
 
19
19
  Vanilla declarativo: `autoInit(root)`. React: `useRotator(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.
20
20
 
21
- Desde 0.8.0. Parámetros investigados en `REA - Dousan OFFFICE Siena/`; implementación propia, sin código ni assets de esos sitios.
21
+ Desde 0.8.0.
@@ -0,0 +1,46 @@
1
+ # scroll-dock
2
+
3
+ La navegación rápida de DOVV Studio: una cápsula invertida fija abajo y al centro. Aparece cuando el scroll pasa de 0.6 alturas de ventana y se esconde cuando el footer llega a 40 px del borde inferior. Sube desde `translateY(140%)` en 0.6 s `cubic-bezier(.32,1.25,.5,1)`, un resorte con un poco de rebote, y su opacidad cambia en 0.3 s.
4
+
5
+ ## Vanilla / Astro
6
+
7
+ ```html
8
+ <nav class="mk-scroll-dock" data-mk="scroll-dock" data-mk-hide-when="footer" aria-label="Navegación rápida">
9
+ <a href="#proyectos">Proyectos</a>
10
+ <a href="#proceso">Cómo trabajamos</a>
11
+ <a href="#precios">Precios</a>
12
+ <a href="#contacto">Contacto</a>
13
+ </nav>
14
+ ```
15
+
16
+ Con `autoInit()` las opciones van como `data-mk-<opción>` (`data-mk-threshold`, `data-mk-hide-when`, `data-mk-hide-offset`). Sin `autoInit`: `createScrollDock(el, opciones)`, que devuelve `visible`, `update()` y `destroy()`. Cada cambio emite `mk:change` con `{ visible }`.
17
+
18
+ ## React
19
+
20
+ ```tsx
21
+ const ref = useRef<HTMLElement>(null);
22
+ useScrollDock(ref, { hideWhen: 'footer' });
23
+ return <nav ref={ref} className="mk-scroll-dock" aria-label="Navegación rápida">…</nav>;
24
+ ```
25
+
26
+ ## Opciones
27
+
28
+ | Opción | Por defecto | Qué hace |
29
+ |---|---|---|
30
+ | `threshold` | `0.6` | Scroll mínimo, en alturas de ventana |
31
+ | `hideWhen` | — | Elemento o selector que la esconde al llegar abajo |
32
+ | `hideOffset` | `40` | px antes del borde inferior |
33
+ | `scroller` | `window` | Contenedor con scroll propio (elemento o selector, buscado desde la cápsula hacia arriba) |
34
+ | `onChange(visible)` | — | Al aparecer o esconderse |
35
+
36
+ Dentro de un contenedor con scroll propio, añade `.mk-scroll-dock--sticky` y pasa `scroller`. Así queda pegada al borde inferior del contenedor en vez de al de la ventana. Ponla como último hijo del contenido. No ocupa sitio: se superpone a la última sección, como la versión fija, así que no deja una franja vacía al final.
37
+
38
+ ## Variables CSS
39
+
40
+ `--mk-scroll-dock-bottom` (24 px) y `--mk-scroll-dock-height` (58 px, 64 px en móvil; sólo la usa `--sticky`; cámbiala si cambias el alto de los botones).
41
+
42
+ En pantallas de 680 px o menos los botones pasan a pestañas (icono sobre etiqueta, 11 px), así caben cuatro o cinco en un teléfono. Si aun así no caben, la cápsula se desplaza por dentro y nunca desborda la página.
43
+
44
+ ## Accesibilidad
45
+
46
+ Los enlaces siguen en el orden de tabulación. Si reciben foco con la cápsula escondida, `:focus-within` la muestra. Con movimiento reducido sólo cambia la opacidad.
@@ -0,0 +1,72 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+
4
+ /*
5
+ * Scroll dock (DOVV Studio quick navigation): a floating capsule that rises
6
+ * once the page is scrolled past `threshold` viewports and sinks again when
7
+ * `hideWhen` (the footer) comes within `hideOffset` px of the bottom edge.
8
+ * The rise is CSS: translateY 140 % → 0 in .6 s cubic-bezier(.32,1.25,.5,1),
9
+ * opacity .3 s. Keyboard focus inside shows it whatever the scroll.
10
+ */
11
+
12
+ export interface ScrollDockOptions {
13
+ /** Scroll distance before it shows, in viewport heights. */
14
+ threshold?: number;
15
+ /** Element (or selector) that sends it away when it reaches the viewport bottom. */
16
+ hideWhen?: Element | string | null;
17
+ /** px above the bottom edge at which `hideWhen` counts as reached. */
18
+ hideOffset?: number;
19
+ /** Scrolling element (or selector, looked up from the dock outwards); the window by default. */
20
+ scroller?: HTMLElement | string | null;
21
+ onChange?: (visible: boolean) => void;
22
+ }
23
+
24
+ export interface ScrollDockInstance extends Instance {
25
+ readonly visible: boolean;
26
+ /** Re-read the scroll position (after layout changes). */
27
+ update(): void;
28
+ }
29
+
30
+ export function createScrollDock(root: HTMLElement | null, options: ScrollDockOptions = {}): ScrollDockInstance {
31
+ if (!root) return inert({ visible: false, update() {} });
32
+ const scope = createScope();
33
+ const threshold = Number.isFinite(options.threshold) ? Math.max(0, options.threshold!) : 0.6;
34
+ const hideOffset = Number.isFinite(options.hideOffset) ? options.hideOffset! : 40;
35
+ const scroller = typeof options.scroller === 'string'
36
+ ? root.closest<HTMLElement>(options.scroller) ?? document.querySelector<HTMLElement>(options.scroller)
37
+ : options.scroller ?? null;
38
+ const hideTarget = () => (typeof options.hideWhen === 'string' ? document.querySelector(options.hideWhen) : options.hideWhen ?? null);
39
+ let visible = false;
40
+ let queued = false;
41
+ scope.attr(root, 'data-mk-ready', '');
42
+
43
+ const update = () => {
44
+ queued = false;
45
+ if (scope.disposed) return;
46
+ const offset = scroller ? scroller.scrollTop : window.scrollY;
47
+ const viewport = scroller ? scroller.clientHeight : window.innerHeight;
48
+ const bottom = scroller ? scroller.getBoundingClientRect().bottom : window.innerHeight;
49
+ const end = hideTarget();
50
+ const reachedEnd = end ? end.getBoundingClientRect().top < bottom - hideOffset : false;
51
+ const next = offset > viewport * threshold && !reachedEnd;
52
+ if (next === visible) return;
53
+ visible = next;
54
+ scope.attr(root, 'data-mk-visible', next ? '' : null);
55
+ options.onChange?.(next);
56
+ root.dispatchEvent(new CustomEvent('mk:change', { bubbles: true, detail: { visible: next } }));
57
+ };
58
+ const queue = () => {
59
+ if (queued) return;
60
+ queued = true;
61
+ requestAnimationFrame(update);
62
+ };
63
+ scope.on(scroller ?? window, 'scroll', queue, { passive: true });
64
+ scope.on(window, 'resize', queue, { passive: true });
65
+ update();
66
+
67
+ return {
68
+ get visible() { return visible; },
69
+ update,
70
+ destroy: () => scope.dispose(),
71
+ };
72
+ }