@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,189 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { inert, type Instance } from '../../core/types';
3
+ import { createDialog } from '../dialog/engine';
4
+
5
+ export interface TrailerOptions {
6
+ triggers?: boolean | string;
7
+ autoplay?: boolean;
8
+ closeOnBackdrop?: boolean;
9
+ onOpen?: () => void;
10
+ onClose?: () => void;
11
+ }
12
+ export interface TrailerInstance extends Instance {
13
+ open(origin?: HTMLElement | null): void;
14
+ close(): void;
15
+ toggle(origin?: HTMLElement | null): void;
16
+ play(): Promise<boolean>;
17
+ pause(): void;
18
+ seek(seconds: number): void;
19
+ setVolume(volume: number): void;
20
+ readonly isOpen: boolean;
21
+ }
22
+ const time = (value: number) => {
23
+ const seconds = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0;
24
+ return `${Math.floor(seconds / 60).toString().padStart(2, '0')}:${(seconds % 60).toString().padStart(2, '0')}`;
25
+ };
26
+
27
+ /** Native modal + consumer-owned video, with optional accessible custom transport controls. */
28
+ export function createTrailer(root: HTMLElement | null, options: TrailerOptions = {}): TrailerInstance {
29
+ const video = root?.querySelector<HTMLVideoElement>('video');
30
+ if (!root || !video || root.tagName !== 'DIALOG') return inert({ open() {}, close() {}, toggle() {}, play: async () => false, pause() {}, seek() {}, setVolume() {}, isOpen: false });
31
+ const scope = createScope();
32
+ const get = <T extends HTMLElement>(name: string) => root.querySelector<T>(`[data-mk-trailer-${name}]`);
33
+ const playButton = get<HTMLButtonElement>('play');
34
+ const muteButton = get<HTMLButtonElement>('mute');
35
+ const fullButton = get<HTMLButtonElement>('fullscreen');
36
+ // The Fullscreen API rejects HTMLDialogElement. A consumer can wrap media and
37
+ // transport in a stage; otherwise fullscreen just the native video element.
38
+ const fullTarget = get<HTMLElement>('stage') ?? video;
39
+ const captionsButton = get<HTMLButtonElement>('captions');
40
+ const seekInput = get<HTMLInputElement>('seek');
41
+ const volumeInput = get<HTMLInputElement>('volume');
42
+ const output = get<HTMLElement>('time');
43
+ const status = get<HTMLElement>('status');
44
+ const originalText = [output, status].filter((el): el is HTMLElement => !!el).map(el => [el, el.textContent] as const);
45
+ scope.add(() => originalText.forEach(([el, text]) => { el.textContent = text; }));
46
+ scope.attr(root, 'data-mk-trailer-ready', '');
47
+ scope.attr(root, 'data-mk-trailer-state', 'paused');
48
+ const controls = video.controls;
49
+ // Keep the browser's controls unless the consumer supplied a complete transport.
50
+ if (playButton && muteButton && seekInput && (!video.querySelector('track') || captionsButton)) video.controls = false;
51
+ scope.add(() => { video.controls = controls; });
52
+ for (const input of [seekInput, volumeInput]) {
53
+ if (!input) continue;
54
+ const initialValue = input.value;
55
+ scope.add(() => { input.value = initialValue; });
56
+ scope.attr(input, 'min', '0');
57
+ scope.attr(input, 'max', '1');
58
+ scope.attr(input, 'step', input === volumeInput ? '.01' : '.1');
59
+ scope.attr(input, 'aria-label', input.getAttribute('aria-label') ?? (input === volumeInput ? 'Volumen' : 'Posición del video'));
60
+ }
61
+ if (status) scope.attr(status, 'role', 'status');
62
+ const tell = (message: string) => { if (!scope.disposed && status) status.textContent = message; };
63
+ const sync = () => {
64
+ if (scope.disposed) return;
65
+ const duration = Number.isFinite(video.duration) ? Math.max(0, video.duration) : 0;
66
+ scope.attr(root, 'data-mk-trailer-state', video.ended ? 'ended' : video.paused ? 'paused' : 'playing');
67
+ if (playButton) {
68
+ scope.attr(playButton, 'aria-label', video.paused || video.ended ? 'Reproducir' : 'Pausar');
69
+ scope.attr(playButton, 'aria-pressed', String(!video.paused && !video.ended));
70
+ }
71
+ if (muteButton) {
72
+ scope.attr(muteButton, 'aria-label', video.muted ? 'Activar sonido' : 'Silenciar');
73
+ scope.attr(muteButton, 'aria-pressed', String(video.muted));
74
+ }
75
+ if (seekInput) {
76
+ scope.attr(seekInput, 'max', String(duration || 1));
77
+ scope.attr(seekInput, 'disabled', duration ? null : '');
78
+ scope.attr(seekInput, 'aria-valuetext', `${time(video.currentTime)} de ${time(duration)}`);
79
+ seekInput.value = String(Number.isFinite(video.currentTime) ? video.currentTime : 0);
80
+ }
81
+ if (volumeInput) volumeInput.value = String(video.muted ? 0 : video.volume);
82
+ if (captionsButton) {
83
+ scope.attr(captionsButton, 'disabled', video.textTracks.length ? null : '');
84
+ scope.attr(captionsButton, 'aria-pressed', String(video.textTracks[0]?.mode === 'showing'));
85
+ }
86
+ if (output) output.textContent = `${time(video.currentTime)} / ${time(duration)}`;
87
+ };
88
+ let playRequest = 0;
89
+ let wantsPlayback = false;
90
+ const pause = () => { if (!scope.disposed) { wantsPlayback = false; ++playRequest; video.pause(); sync(); } };
91
+ const play = async () => {
92
+ if (scope.disposed) return false;
93
+ wantsPlayback = true;
94
+ const request = ++playRequest;
95
+ try {
96
+ await video.play();
97
+ if (scope.disposed || request !== playRequest) {
98
+ // A stale request must not pause a newer play after a quick close/reopen.
99
+ if (scope.disposed || !wantsPlayback) video.pause();
100
+ return false;
101
+ }
102
+ tell('');
103
+ sync();
104
+ return true;
105
+ } catch {
106
+ if (scope.disposed || request !== playRequest) return false;
107
+ wantsPlayback = false;
108
+ tell('No se pudo reproducir el video. Revisa la fuente y vuelve a intentarlo.');
109
+ sync();
110
+ return false;
111
+ }
112
+ };
113
+ const seek = (seconds: number) => {
114
+ if (scope.disposed || !Number.isFinite(seconds) || !Number.isFinite(video.duration) || video.duration <= 0) return;
115
+ video.currentTime = Math.max(0, Math.min(video.duration, seconds));
116
+ sync();
117
+ };
118
+ const setVolume = (volume: number) => {
119
+ if (scope.disposed || !Number.isFinite(volume)) return;
120
+ video.volume = Math.max(0, Math.min(1, volume));
121
+ video.muted = volume <= 0;
122
+ sync();
123
+ };
124
+ const exitFullscreen = () => {
125
+ if (document.fullscreenElement !== fullTarget) return;
126
+ void document.exitFullscreen?.().catch(() => {});
127
+ };
128
+ const dialog = createDialog(root, {
129
+ triggers: options.triggers,
130
+ closeOnBackdrop: options.closeOnBackdrop ?? true,
131
+ drag: false,
132
+ autoResize: false,
133
+ animateContent: false,
134
+ onOpen: () => { sync(); if (options.autoplay) void play(); options.onOpen?.(); },
135
+ onClose: () => { ++playRequest; pause(); exitFullscreen(); options.onClose?.(); },
136
+ });
137
+ if (playButton) scope.on(playButton, 'click', () => video.paused || video.ended ? void play() : pause());
138
+ if (muteButton) scope.on(muteButton, 'click', () => { video.muted = !video.muted; sync(); });
139
+ if (seekInput) scope.on(seekInput, 'input', () => seek(Number(seekInput.value)));
140
+ if (volumeInput) scope.on(volumeInput, 'input', () => setVolume(Number(volumeInput.value)));
141
+ if (captionsButton) scope.on(captionsButton, 'click', () => {
142
+ const track = video.textTracks[0];
143
+ if (track) track.mode = track.mode === 'showing' ? 'hidden' : 'showing';
144
+ sync();
145
+ });
146
+ const fullscreen = async () => {
147
+ if (scope.disposed) return;
148
+ try {
149
+ if (document.fullscreenElement === fullTarget) await document.exitFullscreen?.();
150
+ else if (fullTarget.requestFullscreen) {
151
+ await fullTarget.requestFullscreen();
152
+ if (scope.disposed || !dialog.isOpen) exitFullscreen();
153
+ }
154
+ else tell('La pantalla completa no está disponible en este navegador.');
155
+ } catch { tell('No se pudo abrir la pantalla completa.'); }
156
+ };
157
+ if (fullButton) {
158
+ scope.attr(fullButton, 'aria-label', 'Pantalla completa');
159
+ scope.on(fullButton, 'click', () => { void fullscreen(); });
160
+ scope.on(document, 'fullscreenchange', () => {
161
+ scope.attr(fullButton, 'aria-label', document.fullscreenElement === fullTarget ? 'Salir de pantalla completa' : 'Pantalla completa');
162
+ });
163
+ }
164
+ for (const event of ['loadedmetadata', 'durationchange', 'timeupdate', 'play', 'pause', 'ended', 'volumechange']) scope.on(video, event, sync);
165
+ scope.on(video, 'error', () => { tell('El video no está disponible.'); scope.attr(root, 'data-mk-trailer-state', 'error'); });
166
+ scope.on(root, 'keydown', (event) => {
167
+ if (!dialog.isOpen || (event.target instanceof Element && event.target.closest('button, input, select, textarea, a, [contenteditable]'))) return;
168
+ if (event.key === ' ' || event.key.toLowerCase() === 'k') { event.preventDefault(); video.paused ? void play() : pause(); }
169
+ else if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') { event.preventDefault(); seek(video.currentTime + (event.key === 'ArrowRight' ? 5 : -5)); }
170
+ else if (event.key.toLowerCase() === 'm') { video.muted = !video.muted; sync(); }
171
+ });
172
+ sync();
173
+ return {
174
+ open: (origin) => { if (!scope.disposed) dialog.open(origin); },
175
+ close: () => { if (!scope.disposed) dialog.close(); },
176
+ toggle: (origin) => { if (!scope.disposed) dialog.toggle(origin); },
177
+ play, pause, seek, setVolume,
178
+ get isOpen() { return dialog.isOpen; },
179
+ destroy() {
180
+ if (scope.disposed) return;
181
+ wantsPlayback = false;
182
+ ++playRequest;
183
+ video.pause();
184
+ exitFullscreen();
185
+ dialog.destroy();
186
+ scope.dispose();
187
+ },
188
+ };
189
+ }
@@ -0,0 +1,16 @@
1
+ 'use client';
2
+ import { useRef, type DialogHTMLAttributes } from 'react';
3
+ import { useRecipe } from '../../react/use-recipe';
4
+ import { createTrailer, type TrailerOptions } from './engine';
5
+
6
+ export interface TrailerProps extends Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClose'> {
7
+ options?: TrailerOptions;
8
+ }
9
+ /** Consumer supplies <video>, optional controls and tracks; no remote media is injected. */
10
+ export function Trailer({ options, className, children, ...props }: TrailerProps) {
11
+ const ref = useRef<HTMLDialogElement>(null);
12
+ useRecipe(ref, createTrailer, options);
13
+ return <dialog {...props} ref={ref} className={['mk-trailer', className].filter(Boolean).join(' ')}>{children}</dialog>;
14
+ }
15
+
16
+ export { useTrailer } from '../../react/use-trailer';
@@ -0,0 +1,36 @@
1
+ .mk-trailer {
2
+ --mk-trailer-ink: #f6f3e8;
3
+ --mk-trailer-paper: #131816;
4
+ width: min(1100px, calc(100vw - 32px));
5
+ max-width: none;
6
+ max-height: calc(100dvh - 32px);
7
+ padding: 0;
8
+ border: 0;
9
+ border-radius: 0;
10
+ color: var(--mk-trailer-ink);
11
+ background: var(--mk-trailer-paper);
12
+ overflow: auto;
13
+ margin: auto;
14
+ }
15
+ .mk-trailer::backdrop { background: rgb(4 10 7 / .8); }
16
+ .mk-trailer__header { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px 20px; }
17
+ .mk-trailer__title { font: 500 .75rem/1.5 var(--mk-font-mono, monospace); text-transform: uppercase; letter-spacing: .16em; margin: 0; }
18
+ .mk-trailer video { display: block; width: 100%; aspect-ratio: 16 / 9; object-fit: contain; background: #060b08; }
19
+ .mk-trailer [data-mk-trailer-stage]:fullscreen { display: flex; flex-direction: column; justify-content: center; width: 100%; height: 100%; background: var(--mk-trailer-paper); color: var(--mk-trailer-ink); }
20
+ .mk-trailer [data-mk-trailer-stage]:fullscreen video { max-height: calc(100dvh - 90px); }
21
+ .mk-trailer__controls { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 10px 20px 16px; font: .7rem/1.5 var(--mk-font-mono, monospace); }
22
+ .mk-trailer button { display: grid; place-items: center; min-width: 44px; min-height: 44px; border: 1px solid currentColor; color: inherit; background: transparent; cursor: pointer; font: inherit; }
23
+ .mk-trailer button:hover { background: rgb(255 255 255 / .12); }
24
+ .mk-trailer button:focus-visible, .mk-trailer input:focus-visible { outline: 2px solid #e5ff96; outline-offset: 4px; }
25
+ .mk-trailer button svg { width: 18px; height: 18px; fill: currentColor; }
26
+ .mk-trailer [data-mk-trailer-seek] { flex: 1; min-width: 70px; accent-color: #d6f2ac; }
27
+ .mk-trailer [data-mk-trailer-volume] { width: 72px; accent-color: #d6f2ac; }
28
+ .mk-trailer [data-mk-trailer-time] { white-space: nowrap; }
29
+ .mk-trailer [data-mk-trailer-status] { padding: 0 20px; font: .8rem/1.5 var(--mk-font, sans-serif); }
30
+ .mk-trailer [data-mk-trailer-status]:empty { display: none; }
31
+ .mk-trailer__pause-icon, .mk-trailer__sound-off { display: none; }
32
+ .mk-trailer [data-mk-trailer-play][aria-pressed="true"] .mk-trailer__play-icon,
33
+ .mk-trailer [data-mk-trailer-mute][aria-pressed="true"] .mk-trailer__sound-on { display: none; }
34
+ .mk-trailer [data-mk-trailer-play][aria-pressed="true"] .mk-trailer__pause-icon,
35
+ .mk-trailer [data-mk-trailer-mute][aria-pressed="true"] .mk-trailer__sound-off { display: block; }
36
+ @media (max-width: 540px) { .mk-trailer [data-mk-trailer-volume] { display: none; } .mk-trailer__controls { gap: 8px; padding-inline: 12px; } .mk-trailer [data-mk-trailer-seek] { flex-basis: 100%; order: -1; } }
@@ -1,7 +1,7 @@
1
1
  import { gsap } from 'gsap';
2
2
 
3
3
  /**
4
- * Stepbro's per-property motion channels (ported from Kayro's spring-channels):
4
+ * per-property motion channels:
5
5
  * each value moves toward its target either on a spring (stiffness, damping, an
6
6
  * initial kick) or on an eased duration, all stepped on GSAP's ticker. Used by
7
7
  * surfaces that grow out of a control (liquid dialog, toasts) and floating windows.
@@ -25,7 +25,7 @@ export const easeOut = (t: number) => 1 - (1 - t) * (1 - t);
25
25
  export const easeOutCubic = (t: number) => 1 - (1 - t) ** 3;
26
26
  export const easeInCubic = (t: number) => t * t * t;
27
27
 
28
- /** A cubic-bezier as a function of progress (Stepbro's `cr.bezier`). */
28
+ /** A cubic-bezier as a function of progress. */
29
29
  export function bezier(x1: number, y1: number, x2: number, y2: number) {
30
30
  const at = (t: number, a: number, b: number) => {
31
31
  const u = 1 - t;
@@ -154,7 +154,7 @@ export function channels<K extends string>(start: Record<K, number>, onChange: (
154
154
  };
155
155
  }
156
156
 
157
- /** A damped spring as an ease function of progress (Stepbro's `Vw`). */
157
+ /** A damped spring as an ease function of progress. */
158
158
  export function springEase(ratio: number, velocity = 0) {
159
159
  const damping = Math.min(Math.max(ratio, 0.05), 0.999);
160
160
  const stiffness = 5.6;
@@ -1,4 +1,4 @@
1
- /** Stepbro's curve paths (CustomEase SVG syntax). */
1
+ /** curve paths (CustomEase SVG syntax). */
2
2
  export const PATHS = {
3
3
  /** The signature: a strong deceleration that is not a plain ease-out. */
4
4
  signature: 'M0,0 C0.308,0.19 0.107,0.633 0.288,0.866 0.382,0.987 0.656,1 1,1',
@@ -9,7 +9,7 @@ export const PATHS = {
9
9
  } as const;
10
10
 
11
11
  /**
12
- * A damped spring sampled as an ease path (Stepbro's `FA`): `ratio` below 1
12
+ * A damped spring sampled as an ease path: `ratio` below 1
13
13
  * overshoots and settles, `velocity` is the starting speed.
14
14
  */
15
15
  export function springPath(ratio: number, velocity = 0, steps = 72): string {
@@ -28,7 +28,7 @@ export function springPath(ratio: number, velocity = 0, steps = 72): string {
28
28
  }
29
29
 
30
30
  /**
31
- * Stepbro's own linear() sampling of the signature curve (its backdrop tint),
31
+ * own linear() sampling of the signature curve (its backdrop tint),
32
32
  * for CSS and the Web Animations API.
33
33
  */
34
34
  export const SIGNATURE_LINEAR =
@@ -0,0 +1,98 @@
1
+ /*
2
+ * Calendar date helpers on local dates (no time zone maths): the calendar and
3
+ * slot-picker components share them. Days are compared by their YYYY-MM-DD key.
4
+ */
5
+
6
+ export type DateInput = Date | string | number;
7
+
8
+ /** Local midnight of a date; "2026-10-23" is read as a local day, not UTC. */
9
+ export function toDate(input: DateInput | null | undefined, fallback = new Date()): Date {
10
+ if (input instanceof Date) return Number.isNaN(input.getTime()) ? startOfDay(fallback) : startOfDay(input);
11
+ if (typeof input === 'string') {
12
+ const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(input.trim());
13
+ if (day) return new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]));
14
+ }
15
+ if (typeof input === 'string' || typeof input === 'number') {
16
+ const parsed = new Date(input);
17
+ if (!Number.isNaN(parsed.getTime())) return startOfDay(parsed);
18
+ }
19
+ return startOfDay(fallback);
20
+ }
21
+
22
+ /** A full date-time (events keep their hour). */
23
+ export function toDateTime(input: DateInput): Date | null {
24
+ if (input instanceof Date) return Number.isNaN(input.getTime()) ? null : input;
25
+ if (typeof input === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(input.trim())) return toDate(input);
26
+ const parsed = new Date(input);
27
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
28
+ }
29
+
30
+ export const startOfDay = (date: Date) => new Date(date.getFullYear(), date.getMonth(), date.getDate());
31
+
32
+ export const addDays = (date: Date, days: number) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
33
+
34
+ /** Same day number in another month, clamped to that month's length (Jan 31 + 1 → Feb 28/29). */
35
+ export function addMonths(date: Date, months: number): Date {
36
+ const target = new Date(date.getFullYear(), date.getMonth() + months, 1);
37
+ const last = new Date(target.getFullYear(), target.getMonth() + 1, 0).getDate();
38
+ return new Date(target.getFullYear(), target.getMonth(), Math.min(date.getDate(), last));
39
+ }
40
+
41
+ export const startOfMonth = (date: Date) => new Date(date.getFullYear(), date.getMonth(), 1);
42
+
43
+ export function startOfWeek(date: Date, firstDay = 0): Date {
44
+ const shift = (date.getDay() - firstDay + 7) % 7;
45
+ return addDays(startOfDay(date), -shift);
46
+ }
47
+
48
+ export const sameDay = (a: Date | null | undefined, b: Date | null | undefined) =>
49
+ !!a && !!b && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
50
+
51
+ export const sameMonth = (a: Date, b: Date) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
52
+
53
+ const pad = (n: number) => String(n).padStart(2, '0');
54
+
55
+ /** YYYY-MM-DD of the local day. */
56
+ export const dayKey = (date: Date) => `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
57
+
58
+ /** The weeks shown for a month: from the week of the 1st to the week of the last day (4–6 rows). */
59
+ export function monthWeeks(date: Date, firstDay = 0): Date[][] {
60
+ const first = startOfMonth(date);
61
+ const last = new Date(date.getFullYear(), date.getMonth() + 1, 0);
62
+ const weeks: Date[][] = [];
63
+ for (let start = startOfWeek(first, firstDay); start <= last; start = addDays(start, 7)) {
64
+ weeks.push(Array.from({ length: 7 }, (_, i) => addDays(start, i)));
65
+ }
66
+ return weeks;
67
+ }
68
+
69
+ export const weekDays = (date: Date, firstDay = 0) => Array.from({ length: 7 }, (_, i) => addDays(startOfWeek(date, firstDay), i));
70
+
71
+ /** Weekday names in display order, e.g. ["dom", "lun", …] for es with firstDay 0. */
72
+ export function weekdayNames(locale: string, firstDay = 0, width: 'long' | 'short' | 'narrow' = 'short'): string[] {
73
+ const format = safeFormat(locale, { weekday: width });
74
+ // 2024-09-01 was a Sunday.
75
+ return Array.from({ length: 7 }, (_, i) => format.format(new Date(2024, 8, 1 + ((firstDay + i) % 7))));
76
+ }
77
+
78
+ export function safeFormat(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
79
+ try {
80
+ return new Intl.DateTimeFormat(locale, options);
81
+ } catch {
82
+ return new Intl.DateTimeFormat(undefined, options);
83
+ }
84
+ }
85
+
86
+ export const capitalize = (text: string) => text.charAt(0).toLocaleUpperCase() + text.slice(1);
87
+
88
+ /** Options given as JSON text (data-mk-* attributes through autoInit) become values. */
89
+ export function fromJson<T>(value: T | string | undefined): T | undefined {
90
+ if (typeof value !== 'string') return value;
91
+ try {
92
+ return JSON.parse(value) as T;
93
+ } catch {
94
+ return undefined;
95
+ }
96
+ }
97
+
98
+ export const clampFirstDay =(n: unknown) => (Number.isInteger(n) && (n as number) >= 0 && (n as number) <= 6 ? (n as number) : 0);
package/src/core/gsap.ts CHANGED
@@ -5,10 +5,10 @@ import { CustomEase } from 'gsap/CustomEase';
5
5
  import { Flip } from 'gsap/Flip';
6
6
  import { PATHS, springPath } from './curves';
7
7
 
8
- /** Spencer's elastic overshoot; close to the `--mk-ease-elastic` CSS curve. */
8
+ /** elastic overshoot; close to the `--mk-ease-elastic` CSS curve. */
9
9
  export const ELASTIC = 'elastic.out(1, 0.45)';
10
10
 
11
- /** Custom eases registered by `registerMotion()` (Stepbro curves). Use the names as GSAP `ease` values. */
11
+ /** Custom eases registered by `registerMotion()`. Use the names as GSAP `ease` values. */
12
12
  export const EASE = {
13
13
  signature: 'mk-signature',
14
14
  flow: 'mk-flow',
package/src/core/loop.ts CHANGED
@@ -12,7 +12,7 @@ function tick(_time: number, deltaMs: number) {
12
12
  subscribers.forEach((fn) => fn(dt));
13
13
  }
14
14
 
15
- /** One shared ticker callback for every recipe (spencergabor.com's single rAF loop). */
15
+ /** One shared ticker callback for every recipe. */
16
16
  export function subscribe(fn: Tick): () => void {
17
17
  if (!subscribers.size) gsap.ticker.add(tick);
18
18
  subscribers.add(fn);
package/src/core/math.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v));
2
2
  export const lerp = (a: number, b: number, t: number) => (1 - t) * a + t * b;
3
- /** Spencer's frame-rate independent smoothing: lerp by 1 - e^(-lambda·dt). */
3
+ /** frame-rate independent smoothing: lerp by 1 - e^(-lambda·dt). */
4
4
  export const damp = (a: number, b: number, lambda: number, dt: number) => lerp(a, b, 1 - Math.exp(-lambda * dt));
5
5
  export const wrap = (v: number, length: number) => ((v % length) + length) % length;
6
6
  export function mapRange(v: number, inMin: number, inMax: number, outMin: number, outMax: number, clamped = false) {
@@ -1,8 +1,8 @@
1
1
  /*
2
2
  * Motion/Framer's physical spring — stiffness, damping and mass, released from
3
- * rest at 0 toward 1 — the way Dousan declares its motion (400/10, 260/18…).
3
+ * rest at 0 toward 1, with parameter pairs such as (400/10, 260/18…).
4
4
  * motionSpring() gives a GSAP ease plus the time it takes to settle;
5
- * springLinear() samples it as a CSS linear(). Stepbro's own springs (FA)
5
+ * springLinear() samples it as a CSS linear(). Ratio-based springs
6
6
  * stay in springPath / springEase.
7
7
  */
8
8
 
@@ -3,10 +3,8 @@ import { whileVisible } from './loop';
3
3
  /*
4
4
  * How far an element has travelled through the viewport, 0 → 1, between two
5
5
  * ScrollTrigger-style positions ("<element edge> <viewport line>"), read from
6
- * its box every frame while it is near the screen. Siena's trackers read a
7
- * smooth-scrolled position (Lenis, .1 a frame); on a page that scrolls natively
8
- * the effects follow it exactly (lerp 1), so a picture never drifts behind its
9
- * frame — pass lerp .1 to smooth the effect alone. Parallax, depth grid and
6
+ * its box every frame while it is near the screen. Native scroll is followed
7
+ * exactly with lerp 1; pass lerp .1 to smooth the effect alone. Parallax, depth grid and
10
8
  * frame zoom render from it.
11
9
  */
12
10
 
@@ -6,7 +6,7 @@ export interface Spring {
6
6
  update(dt?: number): void;
7
7
  }
8
8
 
9
- /** Spencer Gabor's spring (stiffness .05, friction .8), normalised so dt = 1 is one 60 Hz frame. */
9
+ /** spring (stiffness .05, friction .8), normalised so dt = 1 is one 60 Hz frame. */
10
10
  export function createSpring(stiffness = 0.05, friction = 0.8): Spring {
11
11
  const current = { x: 0, y: 0 };
12
12
  const target = { x: 0, y: 0 };
@@ -1,8 +1,7 @@
1
1
  /**
2
- * Every Stepbro duration in one place (seconds, like GSAP, unless the name says
2
+ * Every duration in one place (seconds, like GSAP, unless the name says
3
3
  * ms). Recipes and components take their defaults from here, so a timing is
4
- * changed once. Sources: Stepbro replication kit, tasks/especificacion-replicacion.json
5
- * and money/replication-catalog.json.
4
+ * changed once.
6
5
  */
7
6
  export const TIMING = Object.freeze({
8
7
  flow: 0.5,
@@ -104,9 +103,9 @@ export const TIMING = Object.freeze({
104
103
  charsDelay: 0.225,
105
104
  charsStagger: 0.02,
106
105
  }),
107
- /** Dousan's glass (CSS): surface/shadow transitions, ring and shine. */
106
+ /** glass (CSS): surface/shadow transitions, ring and shine. */
108
107
  glass: Object.freeze({ hover: 0.4, ring: 0.5 }),
109
- /** Stepbro's utility animations (css/effects/anim.css), by name. */
108
+ /** utility animations (css/effects/anim.css), by name. */
110
109
  effects: Object.freeze({
111
110
  out: 0.5,
112
111
  'out-left': 0.5,
@@ -119,7 +118,7 @@ export const TIMING = Object.freeze({
119
118
  wipe: 0.7,
120
119
  'progress-in': 1,
121
120
  }),
122
- /** Dousan's success confetti, per 60 fps frame. */
121
+ /** success confetti, per 60 fps frame. */
123
122
  confetti: Object.freeze({
124
123
  count: 45,
125
124
  speed: [2, 8] as const,
@@ -130,9 +129,9 @@ export const TIMING = Object.freeze({
130
129
  drag: 0.98,
131
130
  spin: 2.5,
132
131
  }),
133
- /** Dousan's project sheet. */
132
+ /** project sheet. */
134
133
  sheet: Object.freeze({ open: 0.72, close: 0.72, underlayScale: 0.98, underlayOpacity: 0.4 }),
135
- /** Dousan's springs as Motion declares them: [stiffness, damping]. */
134
+ /** Physical spring pairs: [stiffness, damping]. */
136
135
  springs: Object.freeze({
137
136
  press: [400, 10] as const,
138
137
  success: [260, 18] as const,
@@ -144,13 +143,13 @@ export const TIMING = Object.freeze({
144
143
  bentoRepel: [300, 25] as const,
145
144
  dockPill: [380, 30] as const,
146
145
  }),
147
- /** OFFFICE's masked type (Siena's case titles start at 120 %). */
146
+ /** masked type. */
148
147
  maskText: Object.freeze({ duration: 1.2, delay: 0.1, stagger: 0.02, exitStagger: 0.002, y: 100, threshold: 0.8 }),
149
- /** Siena's reveal group (rules, fades, rises). */
148
+ /** reveal group (rules, fades, rises). */
150
149
  cascade: Object.freeze({ duration: 1.2, step: 0.03, rise: 200, threshold: 0.3 }),
151
- /** Siena's slot title: each letter turns 8–14 times inside a 2 s expo.out envelope. */
150
+ /** slot title: each letter turns 8–14 times inside a 2 s expo.out envelope. */
152
151
  textRoll: Object.freeze({ duration: 2, stagger: 0.04, turns: [8, 14] as const, interval: 6000, quietMs: 1000 }),
153
- /** Dousan's hero letters: scale on hover, pastel blink. */
152
+ /** hero letters: scale on hover, pastel blink. */
154
153
  letters: Object.freeze({
155
154
  scale: 1.3,
156
155
  y: -5,
@@ -160,9 +159,9 @@ export const TIMING = Object.freeze({
160
159
  blinkRepeatDelay: 0.1,
161
160
  blinkDelays: [0.1, 0.6, 0.2, 1, 0.4, 1.2, 0.15, 0.8, 0.3] as const,
162
161
  }),
163
- /** Siena's image parallax. */
162
+ /** image parallax. */
164
163
  parallax: Object.freeze({ travel: 10, scale: 1.2, strongScale: 1.3, lerp: 1 }),
165
- /** Siena's About grid gathering from depth. */
164
+ /** About grid gathering from depth. */
166
165
  depthGrid: Object.freeze({
167
166
  perspective: 600,
168
167
  distance: 900,
@@ -173,7 +172,7 @@ export const TIMING = Object.freeze({
173
172
  stagger: 0.15,
174
173
  lerp: 1,
175
174
  }),
176
- /** Siena's video mask: the frame and the picture inside scale in opposite directions. */
175
+ /** video mask: the frame and the picture inside scale in opposite directions. */
177
176
  frameZoom: Object.freeze({
178
177
  outer: [80, 20] as const,
179
178
  inner: [130, 20] as const,
@@ -182,19 +181,19 @@ export const TIMING = Object.freeze({
182
181
  open: 1,
183
182
  lerp: 1,
184
183
  }),
185
- /** Siena's marquee: seconds per item, at most 14 counted. */
184
+ /** marquee: seconds per item, at most 14 counted. */
186
185
  marquee: Object.freeze({ perItem: 2, maxItems: 14 }),
187
- /** Dousan's glass chip and Siena's dot cursor. */
186
+ /** glass chip and dot cursor. */
188
187
  cursor: Object.freeze({ gutter: 10, circle: 96, dotLerp: 0.2, light: 180 }),
189
- /** iyo.ai's floating navbar: enters from −300 % over 1.8 s; rows follow 0.15 s + 0.025 s each. */
188
+ /** floating navbar: enters from −300 % over 1.8 s; rows follow 0.15 s + 0.025 s each. */
190
189
  island: Object.freeze({ entrance: 1.8, offset: -300, delay: 0.15, step: 0.025, focus: 150 }),
191
- /** iyo.ai's product dock: spread for 1.6 s on load; tablet range re-checked 250 ms after a resize. */
190
+ /** product dock: spread for 1.6 s on load; tablet range re-checked 250 ms after a resize. */
192
191
  cardDock: Object.freeze({ intro: 1600, resizeDebounce: 250 }),
193
- /** iyo.ai's phone cards: 5 s a card, 2 s wait after a touch, 0.5 s after it shows. */
192
+ /** phone cards: 5 s a card, 2 s wait after a touch, 0.5 s after it shows. */
194
193
  snapStrip: Object.freeze({ interval: 5000, resume: 2000, delay: 500 }),
195
- /** iyo.ai's fill-text: letters from .2, 0.5 s each, 0.1 s apart, top 90 % → centre 40 %. */
194
+ /** fill-text: letters from .2, 0.5 s each, 0.1 s apart, top 90 % → centre 40 %. */
196
195
  fillText: Object.freeze({ from: 0.2, duration: 0.5, stagger: 0.1, start: 'top 90%', end: 'center 40%' }),
197
- /** iyo.ai's particle hero (adapted): fade in 1 s after 0.3 s, morph cycles, pointer push. */
196
+ /** particle hero: fade in 1 s after 0.3 s, morph cycles, pointer push. */
198
197
  particleHero: Object.freeze({ fadeIn: 1, fadeDelay: 0.3, size: 128, pointSize: 1.7, maxDpr: 1.5, stillSteps: 110 }),
199
198
  ticketMenu: Object.freeze({ open: 0.9, content: 1.1, stagger: 0.02, from: 150 }),
200
199
  infiniteReel: Object.freeze({ friction: 0.95, wheel: 0.05, dragThreshold: 5 }),