@yewdiel21/motion-kit 0.0.0-stage → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (391) hide show
  1. package/NOTICE +38 -0
  2. package/README.md +80 -2
  3. package/bin/motion-kit.mjs +242 -0
  4. package/css.d.ts +2 -0
  5. package/dist/auto/index.d.ts +63 -0
  6. package/dist/auto/index.js +207 -0
  7. package/dist/auto/index.js.map +1 -0
  8. package/dist/chunk-2BQGFOA7.js +177 -0
  9. package/dist/chunk-2BQGFOA7.js.map +1 -0
  10. package/dist/chunk-34XYOXHG.js +2374 -0
  11. package/dist/chunk-34XYOXHG.js.map +1 -0
  12. package/dist/chunk-6OJAP6ZL.js +832 -0
  13. package/dist/chunk-6OJAP6ZL.js.map +1 -0
  14. package/dist/chunk-ABNRDDO7.js +28 -0
  15. package/dist/chunk-ABNRDDO7.js.map +1 -0
  16. package/dist/chunk-FS2HJVVY.js +3320 -0
  17. package/dist/chunk-FS2HJVVY.js.map +1 -0
  18. package/dist/chunk-J27KH2A2.js +157 -0
  19. package/dist/chunk-J27KH2A2.js.map +1 -0
  20. package/dist/chunk-KLY5BRRI.js +18 -0
  21. package/dist/chunk-KLY5BRRI.js.map +1 -0
  22. package/dist/chunk-Q4LVFNLN.js +1617 -0
  23. package/dist/chunk-Q4LVFNLN.js.map +1 -0
  24. package/dist/chunk-RFDIZYFX.js +304 -0
  25. package/dist/chunk-RFDIZYFX.js.map +1 -0
  26. package/dist/chunk-TGV27ZWM.js +17 -0
  27. package/dist/chunk-TGV27ZWM.js.map +1 -0
  28. package/dist/confetti-BSX1CLLV.d.ts +41 -0
  29. package/dist/css/components/chat-background.css +87 -0
  30. package/dist/css/components/chat.css +237 -0
  31. package/dist/css/components/cursor.css +178 -0
  32. package/dist/css/components/dialog.css +259 -0
  33. package/dist/css/components/island.css +301 -0
  34. package/dist/css/components/menu.css +105 -0
  35. package/dist/css/components/nav.css +1054 -0
  36. package/dist/css/components/steps.css +50 -0
  37. package/dist/css/components/theme-picker.css +115 -0
  38. package/dist/css/components/toast.css +115 -0
  39. package/dist/css/controls.css +950 -0
  40. package/dist/css/effects.css +587 -0
  41. package/dist/css/fonts.css +3 -0
  42. package/dist/css/themes/black-green.css +126 -0
  43. package/dist/css/themes/black.css +126 -0
  44. package/dist/css/themes/blue.css +126 -0
  45. package/dist/css/themes/brown.css +126 -0
  46. package/dist/css/themes/cold-blue.css +126 -0
  47. package/dist/css/themes/funny.css +126 -0
  48. package/dist/css/themes/gray.css +126 -0
  49. package/dist/css/themes/green.css +126 -0
  50. package/dist/css/themes/modern-1.css +126 -0
  51. package/dist/css/themes/neogreen.css +126 -0
  52. package/dist/css/themes/new.css +126 -0
  53. package/dist/css/themes/new2.css +126 -0
  54. package/dist/css/themes/oled.css +126 -0
  55. package/dist/css/themes/orange.css +126 -0
  56. package/dist/css/themes/pastel-pink.css +126 -0
  57. package/dist/css/themes/pink.css +126 -0
  58. package/dist/css/themes/purple.css +126 -0
  59. package/dist/css/themes/red.css +126 -0
  60. package/dist/css/themes/super-blue.css +126 -0
  61. package/dist/css/themes.css +2431 -0
  62. package/dist/css/tokens.css +452 -0
  63. package/dist/elements/index.d.ts +183 -0
  64. package/dist/elements/index.js +472 -0
  65. package/dist/elements/index.js.map +1 -0
  66. package/dist/engine-CUr9QijM.d.ts +46 -0
  67. package/dist/gl/index.d.ts +61 -0
  68. package/dist/gl/index.js +18 -0
  69. package/dist/gl/index.js.map +1 -0
  70. package/dist/index.css +5296 -0
  71. package/dist/index.d.ts +440 -0
  72. package/dist/index.js +390 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/react/index.d.ts +476 -0
  75. package/dist/react/index.js +969 -0
  76. package/dist/react/index.js.map +1 -0
  77. package/dist/reduced-BM4Ls0g5.d.ts +566 -0
  78. package/dist/reveal-WFUWDrei.d.ts +416 -0
  79. package/dist/store-Bqy_obnD.d.ts +195 -0
  80. package/dist/theme/generate.d.ts +18 -0
  81. package/dist/theme/generate.js +64 -0
  82. package/dist/theme/generate.js.map +1 -0
  83. package/dist/theme/index.d.ts +17 -0
  84. package/dist/theme/index.js +32 -0
  85. package/dist/theme/index.js.map +1 -0
  86. package/dist/theme-BFMoCzi6.d.ts +158 -0
  87. package/dist/types-BkS_raL8.d.ts +7 -0
  88. package/package.json +114 -3
  89. package/registry.json +469 -0
  90. package/skills/motion-kit/SKILL.md +189 -0
  91. package/skills/motion-kit/reference/components.md +86 -0
  92. package/skills/motion-kit/reference/design.md +92 -0
  93. package/skills/motion-kit/reference/recipes.md +172 -0
  94. package/src/auto/auto-init.ts +151 -0
  95. package/src/auto/index.ts +3 -0
  96. package/src/auto/parse-options.ts +68 -0
  97. package/src/components/chat/README.md +90 -0
  98. package/src/components/chat/chat.css +236 -0
  99. package/src/components/chat/element.ts +98 -0
  100. package/src/components/chat/engine.ts +295 -0
  101. package/src/components/chat/react.tsx +196 -0
  102. package/src/components/chat-background/README.md +50 -0
  103. package/src/components/chat-background/audio.ts +56 -0
  104. package/src/components/chat-background/chat-background.css +86 -0
  105. package/src/components/chat-background/element.ts +31 -0
  106. package/src/components/chat-background/engine.ts +108 -0
  107. package/src/components/chat-background/react.tsx +36 -0
  108. package/src/components/cursor/README.md +53 -0
  109. package/src/components/cursor/cursor.css +177 -0
  110. package/src/components/cursor/element.ts +23 -0
  111. package/src/components/cursor/engine.ts +254 -0
  112. package/src/components/cursor/react.tsx +26 -0
  113. package/src/components/dialog/README.md +106 -0
  114. package/src/components/dialog/dialog.css +258 -0
  115. package/src/components/dialog/element.ts +118 -0
  116. package/src/components/dialog/engine.ts +432 -0
  117. package/src/components/dialog/react.tsx +200 -0
  118. package/src/components/icon.tsx +10 -0
  119. package/src/components/icons.ts +24 -0
  120. package/src/components/island/README.md +91 -0
  121. package/src/components/island/element.ts +45 -0
  122. package/src/components/island/engine.ts +200 -0
  123. package/src/components/island/island.css +300 -0
  124. package/src/components/island/react.tsx +115 -0
  125. package/src/components/menu/README.md +67 -0
  126. package/src/components/menu/element.ts +55 -0
  127. package/src/components/menu/engine.ts +142 -0
  128. package/src/components/menu/menu.css +104 -0
  129. package/src/components/menu/react.tsx +82 -0
  130. package/src/components/nav/README.md +85 -0
  131. package/src/components/nav/element.ts +39 -0
  132. package/src/components/nav/engine.ts +164 -0
  133. package/src/components/nav/nav.css +1053 -0
  134. package/src/components/nav/react.tsx +90 -0
  135. package/src/components/steps/README.md +70 -0
  136. package/src/components/steps/element.ts +43 -0
  137. package/src/components/steps/engine.ts +185 -0
  138. package/src/components/steps/react.tsx +91 -0
  139. package/src/components/steps/steps.css +49 -0
  140. package/src/components/theme-picker/README.md +57 -0
  141. package/src/components/theme-picker/element.ts +20 -0
  142. package/src/components/theme-picker/engine.ts +91 -0
  143. package/src/components/theme-picker/react.tsx +31 -0
  144. package/src/components/theme-picker/theme-picker.css +114 -0
  145. package/src/components/toast/README.md +60 -0
  146. package/src/components/toast/element.ts +25 -0
  147. package/src/components/toast/engine.ts +483 -0
  148. package/src/components/toast/react.tsx +22 -0
  149. package/src/components/toast/store.ts +122 -0
  150. package/src/components/toast/toast.css +114 -0
  151. package/src/core/channels.ts +165 -0
  152. package/src/core/curves.ts +35 -0
  153. package/src/core/frame.ts +12 -0
  154. package/src/core/gsap.ts +65 -0
  155. package/src/core/loop.ts +44 -0
  156. package/src/core/math.ts +9 -0
  157. package/src/core/physics-spring.ts +59 -0
  158. package/src/core/pointer.ts +63 -0
  159. package/src/core/reduced.ts +21 -0
  160. package/src/core/scope.ts +70 -0
  161. package/src/core/scroll-track.ts +106 -0
  162. package/src/core/split.ts +68 -0
  163. package/src/core/spring.ts +22 -0
  164. package/src/core/styles.ts +31 -0
  165. package/src/core/timing.ts +204 -0
  166. package/src/core/types.ts +20 -0
  167. package/src/core/visible.ts +19 -0
  168. package/src/css/base.css +142 -0
  169. package/src/css/controls/README.md +122 -0
  170. package/src/css/controls/burger.css +67 -0
  171. package/src/css/controls/button.css +226 -0
  172. package/src/css/controls/checkbox.css +53 -0
  173. package/src/css/controls/chip.css +85 -0
  174. package/src/css/controls/field.css +124 -0
  175. package/src/css/controls/glass.css +220 -0
  176. package/src/css/controls/icon.css +26 -0
  177. package/src/css/controls/segmented.css +60 -0
  178. package/src/css/controls/switch.css +72 -0
  179. package/src/css/default-theme.css +123 -0
  180. package/src/css/effects/README.md +134 -0
  181. package/src/css/effects/anim.css +224 -0
  182. package/src/css/effects/fade.css +15 -0
  183. package/src/css/effects/float.css +64 -0
  184. package/src/css/effects/frost.css +62 -0
  185. package/src/css/effects/grain.css +58 -0
  186. package/src/css/effects/loading.css +60 -0
  187. package/src/css/effects/success.css +22 -0
  188. package/src/css/effects/text.css +67 -0
  189. package/src/css/fonts.css +3 -0
  190. package/src/css/states.css +51 -0
  191. package/src/css/themes/black-green.css +126 -0
  192. package/src/css/themes/black.css +126 -0
  193. package/src/css/themes/blue.css +126 -0
  194. package/src/css/themes/brown.css +126 -0
  195. package/src/css/themes/cold-blue.css +126 -0
  196. package/src/css/themes/funny.css +126 -0
  197. package/src/css/themes/gray.css +126 -0
  198. package/src/css/themes/green.css +126 -0
  199. package/src/css/themes/modern-1.css +126 -0
  200. package/src/css/themes/neogreen.css +126 -0
  201. package/src/css/themes/new.css +126 -0
  202. package/src/css/themes/new2.css +126 -0
  203. package/src/css/themes/oled.css +126 -0
  204. package/src/css/themes/orange.css +126 -0
  205. package/src/css/themes/pastel-pink.css +126 -0
  206. package/src/css/themes/pink.css +126 -0
  207. package/src/css/themes/purple.css +126 -0
  208. package/src/css/themes/red.css +126 -0
  209. package/src/css/themes/super-blue.css +126 -0
  210. package/src/css/tokens.css +182 -0
  211. package/src/elements/base.ts +76 -0
  212. package/src/elements/index.ts +48 -0
  213. package/src/gl/index.ts +15 -0
  214. package/src/gl/liquid-blend/README.md +23 -0
  215. package/src/gl/liquid-blend/create-liquid-blend.ts +7 -0
  216. package/src/gl/liquid-blend/shader.ts +9 -0
  217. package/src/gl/matcap/README.md +23 -0
  218. package/src/gl/matcap/create-matcap.ts +7 -0
  219. package/src/gl/matcap/shader.ts +16 -0
  220. package/src/gl/particle-hero/README.md +41 -0
  221. package/src/gl/particle-hero/create-particle-hero.ts +544 -0
  222. package/src/gl/particle-hero/particle-hero.css +19 -0
  223. package/src/gl/particle-hero/shaders.ts +84 -0
  224. package/src/gl/shader-blur/README.md +23 -0
  225. package/src/gl/shader-blur/create-shader-blur.ts +7 -0
  226. package/src/gl/shader-blur/shader.ts +11 -0
  227. package/src/gl/shader-grain/README.md +23 -0
  228. package/src/gl/shader-grain/create-shader-grain.ts +7 -0
  229. package/src/gl/shader-grain/shader.ts +6 -0
  230. package/src/gl/shared/create-shader-effect.ts +186 -0
  231. package/src/gl/shared/shader.css +3 -0
  232. package/src/gl/shared/shaders.ts +30 -0
  233. package/src/index.ts +177 -0
  234. package/src/motion/anchor.ts +45 -0
  235. package/src/motion/chat.ts +97 -0
  236. package/src/motion/confetti.ts +130 -0
  237. package/src/motion/count.ts +95 -0
  238. package/src/motion/details.ts +70 -0
  239. package/src/motion/drag-dismiss.ts +231 -0
  240. package/src/motion/effects.ts +81 -0
  241. package/src/motion/entrances.ts +27 -0
  242. package/src/motion/floating.ts +389 -0
  243. package/src/motion/flow.ts +115 -0
  244. package/src/motion/indicator.ts +44 -0
  245. package/src/motion/layout.ts +39 -0
  246. package/src/motion/liquid.ts +227 -0
  247. package/src/motion/morph.ts +540 -0
  248. package/src/motion/reveal.ts +112 -0
  249. package/src/motion/sheet.ts +65 -0
  250. package/src/motion/surface.ts +108 -0
  251. package/src/react/animated-number.tsx +30 -0
  252. package/src/react/flip-group.tsx +44 -0
  253. package/src/react/frost.tsx +33 -0
  254. package/src/react/index.ts +80 -0
  255. package/src/react/slot-text.tsx +44 -0
  256. package/src/react/theme-provider.tsx +42 -0
  257. package/src/react/use-card-dock.ts +9 -0
  258. package/src/react/use-cascade.ts +9 -0
  259. package/src/react/use-contrast.ts +9 -0
  260. package/src/react/use-counter.ts +8 -0
  261. package/src/react/use-curved-carousel.ts +10 -0
  262. package/src/react/use-depth-grid.ts +9 -0
  263. package/src/react/use-details.ts +7 -0
  264. package/src/react/use-dock-pill.ts +10 -0
  265. package/src/react/use-drag-ruler.ts +10 -0
  266. package/src/react/use-fill-text.ts +9 -0
  267. package/src/react/use-fit-text.ts +8 -0
  268. package/src/react/use-flow.ts +8 -0
  269. package/src/react/use-frame-zoom.ts +9 -0
  270. package/src/react/use-infinite-reel.ts +8 -0
  271. package/src/react/use-letters.ts +9 -0
  272. package/src/react/use-liquid-blend.ts +8 -0
  273. package/src/react/use-marquee.ts +9 -0
  274. package/src/react/use-mask-text.ts +9 -0
  275. package/src/react/use-matcap.ts +8 -0
  276. package/src/react/use-parallax.ts +9 -0
  277. package/src/react/use-particle-hero.ts +9 -0
  278. package/src/react/use-pile.ts +8 -0
  279. package/src/react/use-pop.ts +8 -0
  280. package/src/react/use-radial-menu.ts +10 -0
  281. package/src/react/use-recipe.ts +38 -0
  282. package/src/react/use-reflow.ts +9 -0
  283. package/src/react/use-repel-bento.ts +8 -0
  284. package/src/react/use-reveal.ts +8 -0
  285. package/src/react/use-rotator.ts +10 -0
  286. package/src/react/use-shader-blur.ts +8 -0
  287. package/src/react/use-shader-grain.ts +8 -0
  288. package/src/react/use-slot-text.ts +17 -0
  289. package/src/react/use-snap-strip.ts +9 -0
  290. package/src/react/use-sortable.ts +14 -0
  291. package/src/react/use-spring-cards.ts +8 -0
  292. package/src/react/use-stretch-footer.ts +20 -0
  293. package/src/react/use-tabs.ts +10 -0
  294. package/src/react/use-text-roll.ts +9 -0
  295. package/src/react/use-ticket-menu.ts +15 -0
  296. package/src/react/use-words.ts +8 -0
  297. package/src/recipes/card-dock/README.md +36 -0
  298. package/src/recipes/card-dock/card-dock.css +178 -0
  299. package/src/recipes/card-dock/create-card-dock.ts +97 -0
  300. package/src/recipes/cascade/README.md +33 -0
  301. package/src/recipes/cascade/create-cascade.ts +111 -0
  302. package/src/recipes/contrast/README.md +29 -0
  303. package/src/recipes/contrast/create-contrast.ts +68 -0
  304. package/src/recipes/counter/README.md +42 -0
  305. package/src/recipes/counter/create-counter.ts +84 -0
  306. package/src/recipes/curved-carousel/README.md +50 -0
  307. package/src/recipes/curved-carousel/carousel-math.ts +20 -0
  308. package/src/recipes/curved-carousel/create-curved-carousel.ts +257 -0
  309. package/src/recipes/curved-carousel/curved-carousel.css +50 -0
  310. package/src/recipes/depth-grid/README.md +30 -0
  311. package/src/recipes/depth-grid/create-depth-grid.ts +120 -0
  312. package/src/recipes/details/README.md +31 -0
  313. package/src/recipes/details/create-details.ts +27 -0
  314. package/src/recipes/dock-pill/README.md +23 -0
  315. package/src/recipes/dock-pill/create-dock-pill.ts +68 -0
  316. package/src/recipes/dock-pill/dock-pill.css +5 -0
  317. package/src/recipes/drag-ruler/README.md +21 -0
  318. package/src/recipes/drag-ruler/create-drag-ruler.ts +95 -0
  319. package/src/recipes/drag-ruler/drag-ruler.css +16 -0
  320. package/src/recipes/fill-text/README.md +27 -0
  321. package/src/recipes/fill-text/create-fill-text.ts +93 -0
  322. package/src/recipes/fit-text/README.md +37 -0
  323. package/src/recipes/fit-text/create-fit-text.ts +55 -0
  324. package/src/recipes/fit-text/fit-text.css +6 -0
  325. package/src/recipes/flow/README.md +46 -0
  326. package/src/recipes/flow/create-flow.ts +55 -0
  327. package/src/recipes/frame-zoom/README.md +29 -0
  328. package/src/recipes/frame-zoom/create-frame-zoom.ts +91 -0
  329. package/src/recipes/infinite-reel/README.md +23 -0
  330. package/src/recipes/infinite-reel/create-infinite-reel.ts +140 -0
  331. package/src/recipes/infinite-reel/infinite-reel.css +8 -0
  332. package/src/recipes/letters/README.md +30 -0
  333. package/src/recipes/letters/create-letters.ts +113 -0
  334. package/src/recipes/marquee/README.md +32 -0
  335. package/src/recipes/marquee/create-marquee.ts +74 -0
  336. package/src/recipes/marquee/marquee.css +41 -0
  337. package/src/recipes/mask-text/README.md +39 -0
  338. package/src/recipes/mask-text/create-mask-text.ts +134 -0
  339. package/src/recipes/parallax/README.md +31 -0
  340. package/src/recipes/parallax/create-parallax.ts +74 -0
  341. package/src/recipes/pile/README.md +48 -0
  342. package/src/recipes/pile/create-pile.ts +50 -0
  343. package/src/recipes/pile/pile-math.ts +27 -0
  344. package/src/recipes/pile/pile.css +63 -0
  345. package/src/recipes/pill/README.md +30 -0
  346. package/src/recipes/pill/pill.css +48 -0
  347. package/src/recipes/pop/README.md +36 -0
  348. package/src/recipes/pop/create-pop.ts +52 -0
  349. package/src/recipes/radial-menu/README.md +44 -0
  350. package/src/recipes/radial-menu/create-radial-menu.ts +96 -0
  351. package/src/recipes/radial-menu/radial-menu.css +45 -0
  352. package/src/recipes/reflow/README.md +47 -0
  353. package/src/recipes/reflow/create-reflow.ts +49 -0
  354. package/src/recipes/repel-bento/README.md +21 -0
  355. package/src/recipes/repel-bento/create-repel-bento.ts +65 -0
  356. package/src/recipes/repel-bento/repel-bento.css +3 -0
  357. package/src/recipes/reveal/README.md +36 -0
  358. package/src/recipes/reveal/create-reveal.ts +48 -0
  359. package/src/recipes/rotator/README.md +21 -0
  360. package/src/recipes/rotator/create-rotator.ts +66 -0
  361. package/src/recipes/rotator/rotator.css +3 -0
  362. package/src/recipes/slot-text/README.md +44 -0
  363. package/src/recipes/slot-text/create-slot-text.ts +244 -0
  364. package/src/recipes/slot-text/slot-text.css +42 -0
  365. package/src/recipes/snap-strip/README.md +31 -0
  366. package/src/recipes/snap-strip/create-snap-strip.ts +133 -0
  367. package/src/recipes/snap-strip/snap-strip.css +23 -0
  368. package/src/recipes/sortable/README.md +88 -0
  369. package/src/recipes/sortable/create-sortable.ts +863 -0
  370. package/src/recipes/sortable/sortable.css +40 -0
  371. package/src/recipes/spring-cards/README.md +48 -0
  372. package/src/recipes/spring-cards/create-spring-cards.ts +140 -0
  373. package/src/recipes/spring-cards/spring-cards.css +23 -0
  374. package/src/recipes/stretch-footer/README.md +44 -0
  375. package/src/recipes/stretch-footer/create-stretch-footer.ts +69 -0
  376. package/src/recipes/stretch-footer/stretch-footer.css +28 -0
  377. package/src/recipes/tabs/README.md +39 -0
  378. package/src/recipes/tabs/create-tabs.ts +91 -0
  379. package/src/recipes/tabs/tabs.css +19 -0
  380. package/src/recipes/text-roll/README.md +35 -0
  381. package/src/recipes/text-roll/create-text-roll.ts +137 -0
  382. package/src/recipes/text-roll/text-roll.css +21 -0
  383. package/src/recipes/ticket-menu/README.md +24 -0
  384. package/src/recipes/ticket-menu/create-ticket-menu.ts +6 -0
  385. package/src/recipes/words/README.md +43 -0
  386. package/src/recipes/words/create-words.ts +114 -0
  387. package/src/theme/css-format.ts +32 -0
  388. package/src/theme/generate.ts +72 -0
  389. package/src/theme/index.ts +15 -0
  390. package/src/theme/presets.ts +79 -0
  391. package/src/theme/theme.ts +119 -0
@@ -0,0 +1,114 @@
1
+ /* toast — Stepbro's deck at the top of the screen, above dialogs (top layer when popovers exist). */
2
+ .mk-toaster {
3
+ position: fixed;
4
+ inset: 16px 0 auto;
5
+ z-index: 2147483000;
6
+ width: 100%;
7
+ height: 0;
8
+ margin: 0;
9
+ padding: 0;
10
+ overflow: visible;
11
+ border: 0;
12
+ background: transparent;
13
+ pointer-events: none;
14
+ }
15
+
16
+ .mk-toaster:popover-open {
17
+ display: block;
18
+ }
19
+
20
+ mk-toaster {
21
+ display: contents;
22
+ }
23
+
24
+ .mk-toast {
25
+ --mk-toast-bg: var(--mk-surface-4);
26
+ --mk-toast-radius: 20px;
27
+ position: absolute;
28
+ top: 0;
29
+ left: 50%;
30
+ max-width: min(420px, calc(100vw - 32px));
31
+ transform-origin: 50% 0;
32
+ touch-action: none;
33
+ pointer-events: auto;
34
+ }
35
+
36
+ .mk-toast__shell {
37
+ overflow: hidden;
38
+ border-radius: var(--mk-toast-radius);
39
+ background: var(--mk-toast-bg);
40
+ box-shadow: 0 12px 32px -12px rgb(0 0 0 / 0.35);
41
+ color: var(--mk-text);
42
+ }
43
+
44
+ .mk-toast__body {
45
+ display: flex;
46
+ align-items: center;
47
+ gap: 10px;
48
+ min-height: 48px;
49
+ padding: 8px 8px 8px 16px;
50
+ font-family: var(--mk-font);
51
+ font-size: var(--md-sys-typescale-label-large-size);
52
+ font-weight: var(--md-sys-typescale-label-large-weight);
53
+ letter-spacing: var(--md-sys-typescale-label-large-tracking);
54
+ white-space: nowrap;
55
+ }
56
+
57
+ .mk-toast__icon {
58
+ display: grid;
59
+ flex: none;
60
+ place-items: center;
61
+ color: var(--mk-accent);
62
+ }
63
+
64
+ .mk-toast[data-mk-tone='error'] .mk-toast__icon {
65
+ color: var(--mk-error);
66
+ }
67
+
68
+ .mk-toast[data-mk-tone='info'] .mk-toast__icon {
69
+ color: var(--mk-muted);
70
+ }
71
+
72
+ .mk-toast__message {
73
+ flex: 1;
74
+ }
75
+
76
+ .mk-toast__action {
77
+ flex: none;
78
+ height: 32px;
79
+ padding: 0 12px;
80
+ border: 0;
81
+ border-radius: var(--mk-radius-full);
82
+ background: transparent;
83
+ color: var(--mk-accent);
84
+ font: inherit;
85
+ cursor: pointer;
86
+ }
87
+
88
+ .mk-toast__action:hover {
89
+ background: color-mix(in srgb, currentColor 10%, transparent);
90
+ }
91
+
92
+ .mk-toast__dismiss {
93
+ display: grid;
94
+ flex: none;
95
+ place-items: center;
96
+ width: 28px;
97
+ height: 28px;
98
+ padding: 0;
99
+ border: 0;
100
+ border-radius: 50%;
101
+ background: transparent;
102
+ color: var(--mk-muted);
103
+ cursor: pointer;
104
+ }
105
+
106
+ .mk-toast__dismiss:hover {
107
+ background: color-mix(in srgb, currentColor 12%, transparent);
108
+ color: var(--mk-text);
109
+ }
110
+
111
+ .mk-toast :is(.mk-toast__action, .mk-toast__dismiss):focus-visible {
112
+ outline: 2px solid var(--mk-accent);
113
+ outline-offset: 1px;
114
+ }
@@ -0,0 +1,165 @@
1
+ import { gsap } from 'gsap';
2
+
3
+ /**
4
+ * Stepbro's per-property motion channels (ported from Kayro's spring-channels):
5
+ * each value moves toward its target either on a spring (stiffness, damping, an
6
+ * initial kick) or on an eased duration, all stepped on GSAP's ticker. Used by
7
+ * surfaces that grow out of a control (liquid dialog, toasts) and floating windows.
8
+ */
9
+
10
+ export type Channel =
11
+ | { type: 'spring'; stiffness: number; damping: number; mass?: number; velocity?: number; direction?: number }
12
+ | { type: 'ease'; duration: number; ease: (progress: number) => number };
13
+
14
+ export interface ChannelState {
15
+ current: number;
16
+ origin: number;
17
+ target: number;
18
+ velocity: number;
19
+ elapsed: number;
20
+ active: boolean;
21
+ config: Channel | null;
22
+ }
23
+
24
+ export const easeOut = (t: number) => 1 - (1 - t) * (1 - t);
25
+ export const easeOutCubic = (t: number) => 1 - (1 - t) ** 3;
26
+ export const easeInCubic = (t: number) => t * t * t;
27
+
28
+ /** A cubic-bezier as a function of progress (Stepbro's `cr.bezier`). */
29
+ export function bezier(x1: number, y1: number, x2: number, y2: number) {
30
+ const at = (t: number, a: number, b: number) => {
31
+ const u = 1 - t;
32
+ return 3 * u * u * t * a + 3 * u * t * t * b + t * t * t;
33
+ };
34
+ return (progress: number) => {
35
+ if (progress <= 0 || progress >= 1) return Math.min(1, Math.max(0, progress));
36
+ let low = 0;
37
+ let high = 1;
38
+ let t = progress;
39
+ for (let step = 0; step < 24; step += 1) {
40
+ const x = at(t, x1, x2);
41
+ if (Math.abs(x - progress) < 1e-5) break;
42
+ if (x < progress) low = t;
43
+ else high = t;
44
+ t = (low + high) / 2;
45
+ }
46
+ return at(t, y1, y2);
47
+ };
48
+ }
49
+
50
+ export const spring = (stiffness: number, damping: number, extra: { mass?: number; velocity?: number; direction?: number } = {}): Channel => ({
51
+ type: 'spring',
52
+ stiffness,
53
+ damping,
54
+ ...extra,
55
+ });
56
+
57
+ export const eased = (duration: number, ease: (progress: number) => number): Channel => ({ type: 'ease', duration, ease });
58
+
59
+ export const createChannelState = (value: number): ChannelState => ({
60
+ current: value,
61
+ origin: value,
62
+ target: value,
63
+ velocity: 0,
64
+ elapsed: 0,
65
+ active: false,
66
+ config: null,
67
+ });
68
+
69
+ /** Advance one channel by `seconds`. Pure: exported for tests and custom loops. */
70
+ export function stepChannel(channel: ChannelState, seconds: number) {
71
+ const config = channel.config;
72
+ if (!channel.active || !config) return;
73
+ if (config.type === 'spring') {
74
+ // Sub-steps of at most 1/60 s keep stiff springs stable; a long frame is capped.
75
+ const time = Math.min(0.064, Math.max(0, seconds));
76
+ const steps = Math.max(1, Math.ceil(time / (1 / 60) - 1e-9));
77
+ const step = time / steps;
78
+ for (let index = 0; index < steps; index += 1) {
79
+ const force = -config.stiffness * (channel.current - channel.target) - config.damping * channel.velocity;
80
+ channel.velocity += (force / (config.mass ?? 1)) * step;
81
+ channel.current += channel.velocity * step;
82
+ }
83
+ if (Math.abs(channel.velocity) <= 0.01 && Math.abs(channel.target - channel.current) <= 0.01) {
84
+ channel.current = channel.target;
85
+ channel.velocity = 0;
86
+ channel.active = false;
87
+ }
88
+ } else {
89
+ channel.elapsed += Math.max(0, seconds);
90
+ const progress = Math.min(1, channel.elapsed / config.duration);
91
+ channel.current = progress >= 1 ? channel.target : channel.origin + (channel.target - channel.origin) * config.ease(progress);
92
+ if (progress >= 1) channel.active = false;
93
+ }
94
+ }
95
+
96
+ /** Animate named values toward targets, each on its own channel, calling `onChange` per frame. */
97
+ export function channels<K extends string>(start: Record<K, number>, onChange: (name: K, value: number) => void) {
98
+ const names = Object.keys(start) as K[];
99
+ const state = Object.fromEntries(names.map((name) => [name, createChannelState(start[name])])) as Record<K, ChannelState>;
100
+ let last = 0;
101
+ let resolveDone: (() => void) | null = null;
102
+
103
+ const tick = () => {
104
+ const now = performance.now();
105
+ const elapsed = Math.max(0, (now - last) / 1000);
106
+ last = now;
107
+ let running = false;
108
+ for (const name of names) {
109
+ const channel = state[name];
110
+ if (!channel.active) continue;
111
+ stepChannel(channel, elapsed);
112
+ onChange(name, channel.current);
113
+ running ||= channel.active;
114
+ }
115
+ if (!running) {
116
+ gsap.ticker.remove(tick);
117
+ resolveDone?.();
118
+ resolveDone = null;
119
+ }
120
+ };
121
+
122
+ return {
123
+ state,
124
+ /** Start moving; resolves when every channel has settled (or `stop()` is called). */
125
+ animate(targets: Partial<Record<K, number>>, configs: Partial<Record<K, Channel>>): Promise<void> {
126
+ for (const name of Object.keys(targets) as K[]) {
127
+ const channel = state[name];
128
+ const config = configs[name];
129
+ if (!channel || !config) continue;
130
+ channel.config = config;
131
+ channel.origin = channel.current;
132
+ channel.target = targets[name] as number;
133
+ channel.elapsed = 0;
134
+ if (config.type === 'spring' && config.velocity) {
135
+ const sign = config.direction !== undefined ? Math.sign(config.direction) : Math.sign(channel.target - channel.current);
136
+ channel.velocity = Math.abs(config.velocity) * sign;
137
+ }
138
+ channel.active = channel.target !== channel.current || channel.velocity !== 0;
139
+ }
140
+ last = performance.now();
141
+ gsap.ticker.remove(tick);
142
+ gsap.ticker.add(tick);
143
+ resolveDone?.();
144
+ return new Promise((resolve) => {
145
+ resolveDone = resolve;
146
+ });
147
+ },
148
+ stop() {
149
+ gsap.ticker.remove(tick);
150
+ for (const name of names) state[name].active = false;
151
+ resolveDone?.();
152
+ resolveDone = null;
153
+ },
154
+ };
155
+ }
156
+
157
+ /** A damped spring as an ease function of progress (Stepbro's `Vw`). */
158
+ export function springEase(ratio: number, velocity = 0) {
159
+ const damping = Math.min(Math.max(ratio, 0.05), 0.999);
160
+ const stiffness = 5.6;
161
+ const frequency = (stiffness / damping) * Math.sqrt(1 - damping * damping);
162
+ const phase = (velocity - stiffness) / frequency;
163
+ return (progress: number) =>
164
+ progress >= 1 ? 1 : 1 + Math.exp(-stiffness * progress) * (-Math.cos(frequency * progress) + phase * Math.sin(frequency * progress));
165
+ }
@@ -0,0 +1,35 @@
1
+ /** Stepbro's curve paths (CustomEase SVG syntax). */
2
+ export const PATHS = {
3
+ /** The signature: a strong deceleration that is not a plain ease-out. */
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',
5
+ /** Lists and steps entering (`flowSoft`, also `numberSoft`). */
6
+ flow: 'M0,0 C0.25,0.1 0.25,1 1,1',
7
+ /** A conversation scrolling to its newest message. */
8
+ chatScroll: 'M0,0 C0.308,0.19 0.318,0.567 0.5,0.8 0.593,0.921 0.656,1 1,1',
9
+ } as const;
10
+
11
+ /**
12
+ * A damped spring sampled as an ease path (Stepbro's `FA`): `ratio` below 1
13
+ * overshoots and settles, `velocity` is the starting speed.
14
+ */
15
+ export function springPath(ratio: number, velocity = 0, steps = 72): string {
16
+ const damping = Math.min(Math.max(ratio, 0.05), 0.999);
17
+ const stiffness = 5.6;
18
+ const frequency = (stiffness / damping) * Math.sqrt(1 - damping * damping);
19
+ const phase = (velocity - stiffness) / frequency;
20
+ const at = (time: number) =>
21
+ 1 + Math.exp(-stiffness * time) * (-Math.cos(frequency * time) + phase * Math.sin(frequency * time));
22
+ let path = 'M0,0';
23
+ for (let step = 1; step <= steps; step += 1) {
24
+ const time = step / steps;
25
+ path += ` L${time.toFixed(4)},${(step === steps ? 1 : at(time)).toFixed(4)}`;
26
+ }
27
+ return path;
28
+ }
29
+
30
+ /**
31
+ * Stepbro's own linear() sampling of the signature curve (its backdrop tint),
32
+ * for CSS and the Web Animations API.
33
+ */
34
+ export const SIGNATURE_LINEAR =
35
+ 'linear(0 0%, .017 2.5%, .037 5%, .061 7.5%, .091 10%, .13 12.5%, .184 15%, .275 17.5%, .562 20%, .733 22.5%, .803 25%, .848 27.5%, .88 30%, .902 32.5%, .942 40%, .97 50%, .985 60%, .993 70%, .998 80%, 1 90%, 1 100%)';
@@ -0,0 +1,12 @@
1
+ /** requestAnimationFrame with a timer fallback (tests, old WebViews). Returns a cancel function. */
2
+ export function requestFrame(fn: (time: number) => void): () => void {
3
+ if (typeof requestAnimationFrame === 'function') {
4
+ const id = requestAnimationFrame(fn);
5
+ return () => cancelAnimationFrame(id);
6
+ }
7
+ const id = setTimeout(() => fn(Date.now()), 16);
8
+ return () => clearTimeout(id);
9
+ }
10
+
11
+ /** Resolves on the next frame, after the framework has painted a DOM change. */
12
+ export const nextFrame = () => new Promise<void>((resolve) => requestFrame(() => resolve()));
@@ -0,0 +1,65 @@
1
+ import { gsap } from 'gsap';
2
+ import { ScrollTrigger } from 'gsap/ScrollTrigger';
3
+ import { SplitText } from 'gsap/SplitText';
4
+ import { CustomEase } from 'gsap/CustomEase';
5
+ import { Flip } from 'gsap/Flip';
6
+ import { PATHS, springPath } from './curves';
7
+
8
+ /** Spencer's elastic overshoot; close to the `--mk-ease-elastic` CSS curve. */
9
+ export const ELASTIC = 'elastic.out(1, 0.45)';
10
+
11
+ /** Custom eases registered by `registerMotion()` (Stepbro curves). Use the names as GSAP `ease` values. */
12
+ export const EASE = {
13
+ signature: 'mk-signature',
14
+ flow: 'mk-flow',
15
+ /** A closing surface's fade (`.56,.27,0,1`). Kept from v0.1; same curve as `fadeAway`. */
16
+ close: 'mk-close',
17
+ fadeAway: 'mk-fade-away',
18
+ /** A closing surface's blur (`.37,.35,0,1`). */
19
+ blurAway: 'mk-blur-away',
20
+ chatScroll: 'mk-chat-scroll',
21
+ /** Items travelling to a new place in a list (spring ratio .52). */
22
+ layoutReflow: 'mk-layout-reflow',
23
+ /** A dragged item landing in its slot (spring ratio .62). */
24
+ sortableLand: 'mk-sortable-land',
25
+ slot: 'mk-slot',
26
+ slotResize: 'mk-slot-resize',
27
+ liquidSize: 'mk-liquid-size',
28
+ swatch: 'mk-swatch',
29
+ swatchLight: 'mk-swatch-light',
30
+ sheet: 'mk-sheet',
31
+ inOut: 'mk-in-out',
32
+ } as const;
33
+
34
+ const CURVES: Record<keyof typeof EASE, string> = {
35
+ signature: PATHS.signature,
36
+ flow: PATHS.flow,
37
+ close: '.56,.27,0,1',
38
+ fadeAway: '.56,.27,0,1',
39
+ blurAway: '.37,.35,0,1',
40
+ chatScroll: PATHS.chatScroll,
41
+ layoutReflow: springPath(0.52),
42
+ sortableLand: springPath(0.62),
43
+ slot: '.34,1.56,.64,1',
44
+ slotResize: '.2,0,0,1',
45
+ liquidSize: '.8,.3,.5,.8',
46
+ swatch: '.38,.49,0,1.5',
47
+ swatchLight: '.11,1,0,1',
48
+ sheet: '.16,1,.3,1',
49
+ inOut: '.42,0,.58,1',
50
+ };
51
+
52
+ let registered = false;
53
+
54
+ /** Register plugins and eases once. Safe to call from every recipe and during SSR (no-op). */
55
+ export function registerMotion() {
56
+ if (typeof window === 'undefined') return;
57
+ // Cancels the MK_HEAD_SCRIPT safety net: JS owns the entrance states from here on.
58
+ document.documentElement.classList.add('mk-ready');
59
+ if (registered) return;
60
+ registered = true;
61
+ gsap.registerPlugin(ScrollTrigger, SplitText, CustomEase, Flip);
62
+ for (const key of Object.keys(EASE) as Array<keyof typeof EASE>) CustomEase.create(EASE[key], CURVES[key]);
63
+ }
64
+
65
+ export { gsap, ScrollTrigger, SplitText, CustomEase, Flip };
@@ -0,0 +1,44 @@
1
+ import { gsap } from 'gsap';
2
+
3
+ /** Called every frame with dt = 1 at 60 Hz. */
4
+ export type Tick = (dt: number) => void;
5
+
6
+ const subscribers = new Set<Tick>();
7
+ const FRAME_MS = 1000 / 60;
8
+
9
+ function tick(_time: number, deltaMs: number) {
10
+ // Capped so a backgrounded tab does not explode the springs.
11
+ const dt = Math.min(deltaMs / FRAME_MS, 3);
12
+ subscribers.forEach((fn) => fn(dt));
13
+ }
14
+
15
+ /** One shared ticker callback for every recipe (spencergabor.com's single rAF loop). */
16
+ export function subscribe(fn: Tick): () => void {
17
+ if (!subscribers.size) gsap.ticker.add(tick);
18
+ subscribers.add(fn);
19
+ return () => {
20
+ subscribers.delete(fn);
21
+ if (!subscribers.size) gsap.ticker.remove(tick);
22
+ };
23
+ }
24
+
25
+ /** Run `fn` every frame only while `el` is near the viewport. */
26
+ export function whileVisible(el: Element, fn: Tick, rootMargin = '100px'): () => void {
27
+ let stop: (() => void) | null = null;
28
+ const io = new IntersectionObserver(
29
+ ([entry]) => {
30
+ if (entry?.isIntersecting) stop ??= subscribe(fn);
31
+ else {
32
+ stop?.();
33
+ stop = null;
34
+ }
35
+ },
36
+ { rootMargin },
37
+ );
38
+ io.observe(el);
39
+ return () => {
40
+ io.disconnect();
41
+ stop?.();
42
+ stop = null;
43
+ };
44
+ }
@@ -0,0 +1,9 @@
1
+ export const clamp = (v: number, min: number, max: number) => Math.min(max, Math.max(min, v));
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). */
4
+ export const damp = (a: number, b: number, lambda: number, dt: number) => lerp(a, b, 1 - Math.exp(-lambda * dt));
5
+ export const wrap = (v: number, length: number) => ((v % length) + length) % length;
6
+ export function mapRange(v: number, inMin: number, inMax: number, outMin: number, outMax: number, clamped = false) {
7
+ const t = (v - inMin) / (inMax - inMin);
8
+ return lerp(outMin, outMax, clamped ? clamp(t, 0, 1) : t);
9
+ }
@@ -0,0 +1,59 @@
1
+ /*
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…).
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)
6
+ * stay in springPath / springEase.
7
+ */
8
+
9
+ const REST = 0.001;
10
+ const STEP = 1 / 240;
11
+ const LIMIT = 10;
12
+
13
+ export interface MotionSpring {
14
+ /** Progress 0…1 over `duration` → position (overshoots when bouncy). */
15
+ ease: (progress: number) => number;
16
+ /** Seconds until it stays within 0.1 % of the target. */
17
+ duration: number;
18
+ }
19
+
20
+ function position(stiffness: number, damping: number, mass: number) {
21
+ const w0 = Math.sqrt(stiffness / mass);
22
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
23
+ if (zeta < 1) {
24
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
25
+ return (t: number) => 1 - Math.exp(-zeta * w0 * t) * (Math.cos(wd * t) + ((zeta * w0) / wd) * Math.sin(wd * t));
26
+ }
27
+ if (zeta === 1) return (t: number) => 1 - Math.exp(-w0 * t) * (1 + w0 * t);
28
+ const root = Math.sqrt(zeta * zeta - 1);
29
+ const r1 = -w0 * (zeta - root);
30
+ const r2 = -w0 * (zeta + root);
31
+ return (t: number) => 1 - (r2 * Math.exp(r1 * t) - r1 * Math.exp(r2 * t)) / (r2 - r1);
32
+ }
33
+
34
+ const springs = new Map<string, MotionSpring>();
35
+
36
+ export function motionSpring(stiffness: number, damping: number, mass = 1): MotionSpring {
37
+ const key = `${stiffness}/${damping}/${mass}`;
38
+ const known = springs.get(key);
39
+ if (known) return known;
40
+ const x = position(Math.max(stiffness, 1e-6), Math.max(damping, 0), Math.max(mass, 1e-6));
41
+ let settled = 0;
42
+ for (let i = 0; i * STEP <= LIMIT; i++) if (Math.abs(x(i * STEP) - 1) > REST) settled = (i + 1) * STEP;
43
+ const duration = Math.round(Math.min(settled, LIMIT) * 1000) / 1000;
44
+ const spring: MotionSpring = {
45
+ ease: (progress) => (progress <= 0 ? 0 : progress >= 1 ? 1 : x(progress * duration)),
46
+ duration,
47
+ };
48
+ springs.set(key, spring);
49
+ return spring;
50
+ }
51
+
52
+ /** Seconds a Motion spring takes to settle. */
53
+ export const springDuration = (stiffness: number, damping: number, mass = 1) => motionSpring(stiffness, damping, mass).duration;
54
+
55
+ /** The spring as a CSS linear() easing (pair it with springDuration). */
56
+ export function springLinear(stiffness: number, damping: number, mass = 1, points = 40) {
57
+ const { ease } = motionSpring(stiffness, damping, mass);
58
+ return `linear(${Array.from({ length: points + 1 }, (_, i) => Number(ease(i / points).toFixed(4))).join(', ')})`;
59
+ }
@@ -0,0 +1,63 @@
1
+ import { gsap } from 'gsap';
2
+
3
+ /** Live pointer position (viewport px) plus movement accumulated since the last frame. */
4
+ export const pointer = { x: 0, y: 0, active: false };
5
+
6
+ let mx = 0;
7
+ let my = 0;
8
+ let lastX: number | null = null;
9
+ let lastY: number | null = null;
10
+ let users = 0;
11
+
12
+ function onMove(e: PointerEvent) {
13
+ if (lastX !== null && lastY !== null) {
14
+ mx += e.clientX - lastX;
15
+ my += e.clientY - lastY;
16
+ }
17
+ lastX = pointer.x = e.clientX;
18
+ lastY = pointer.y = e.clientY;
19
+ pointer.active = true;
20
+ }
21
+
22
+ function onOut(e: PointerEvent) {
23
+ if (e.relatedTarget) return;
24
+ pointer.active = false;
25
+ lastX = lastY = null;
26
+ }
27
+
28
+ /** Start tracking (reference-counted). Call the returned function when the recipe is destroyed. */
29
+ export function bindPointer(): () => void {
30
+ if (users++ === 0) {
31
+ addEventListener('pointermove', onMove, { passive: true });
32
+ document.addEventListener('pointerout', onOut);
33
+ }
34
+ let released = false;
35
+ return () => {
36
+ if (released) return;
37
+ released = true;
38
+ if (--users > 0) return;
39
+ removeEventListener('pointermove', onMove);
40
+ document.removeEventListener('pointerout', onOut);
41
+ pointer.active = false;
42
+ lastX = lastY = null;
43
+ mx = my = 0;
44
+ };
45
+ }
46
+
47
+ let frameMovement = { mx: 0, my: 0 };
48
+ let movementFrame = -1;
49
+
50
+ /**
51
+ * Pointer movement since the previous animation frame. Every caller in the same
52
+ * frame gets the same value (two recipes on a page both feel the pointer); the
53
+ * next frame starts again from what moved since.
54
+ */
55
+ export function takeMovement() {
56
+ const frame = gsap.ticker.frame;
57
+ if (frame !== movementFrame) {
58
+ movementFrame = frame;
59
+ frameMovement = { mx, my };
60
+ mx = my = 0;
61
+ }
62
+ return frameMovement;
63
+ }
@@ -0,0 +1,21 @@
1
+ /** `prefers-reduced-motion: reduce`. Always false during SSR. */
2
+ export function reduced(): boolean {
3
+ return typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
4
+ }
5
+
6
+ /**
7
+ * Entrance animations run only with motion allowed and when the head safety net
8
+ * (see MK_HEAD_SCRIPT) has not given up on JavaScript.
9
+ */
10
+ export function entrances(): boolean {
11
+ if (reduced()) return false;
12
+ return !(typeof document !== 'undefined' && document.documentElement.classList.contains('mk-failed'));
13
+ }
14
+
15
+ /**
16
+ * Inline this in <head> (before CSS paints). It enables the hidden entrance states
17
+ * (`.mk-js`) and reveals everything again (`.mk-failed`) if no recipe has started
18
+ * within 2.5 s — e.g. the bundle failed to load.
19
+ */
20
+ export const MK_HEAD_SCRIPT =
21
+ "(function(){var r=document.documentElement;r.classList.add('mk-js');setTimeout(function(){if(!r.classList.contains('mk-ready'))r.classList.add('mk-failed')},2500)})();";
@@ -0,0 +1,70 @@
1
+ import type { Cleanup } from './types';
2
+
3
+ export interface Scope {
4
+ /** Register any cleanup; returns it for convenience. */
5
+ add(fn: Cleanup): Cleanup;
6
+ /** addEventListener that is removed on dispose. */
7
+ on<K extends keyof HTMLElementEventMap>(
8
+ target: EventTarget,
9
+ type: K | (string & {}),
10
+ listener: (event: HTMLElementEventMap[K]) => void,
11
+ options?: AddEventListenerOptions | boolean,
12
+ ): void;
13
+ /** Disconnects the observer on dispose and returns it. */
14
+ observe<O extends { disconnect(): void }>(observer: O): O;
15
+ /** setTimeout that is cleared on dispose. */
16
+ timeout(fn: () => void, ms: number): void;
17
+ /** Set (or remove, with null) an attribute; dispose puts the original value back. */
18
+ attr(el: Element, name: string, value: string | null): void;
19
+ /** Run every cleanup once, newest first. */
20
+ dispose(): void;
21
+ readonly disposed: boolean;
22
+ }
23
+
24
+ /** Collects the side effects of one recipe instance so `destroy()` can undo all of them. */
25
+ export function createScope(): Scope {
26
+ const cleanups: Cleanup[] = [];
27
+ const originals = new Map<Element, Map<string, string | null>>();
28
+ let disposed = false;
29
+ return {
30
+ add(fn) {
31
+ cleanups.push(fn);
32
+ return fn;
33
+ },
34
+ on(target, type, listener, options) {
35
+ target.addEventListener(type, listener as EventListener, options);
36
+ cleanups.push(() => target.removeEventListener(type, listener as EventListener, options));
37
+ },
38
+ observe(observer) {
39
+ cleanups.push(() => observer.disconnect());
40
+ return observer;
41
+ },
42
+ timeout(fn, ms) {
43
+ const id = setTimeout(fn, ms);
44
+ cleanups.push(() => clearTimeout(id));
45
+ },
46
+ attr(el, name, value) {
47
+ let saved = originals.get(el);
48
+ if (!saved) originals.set(el, (saved = new Map()));
49
+ if (!saved.has(name)) {
50
+ saved.set(name, el.getAttribute(name));
51
+ cleanups.push(() => {
52
+ const original = saved.get(name);
53
+ if (original === null || original === undefined) el.removeAttribute(name);
54
+ else el.setAttribute(name, original);
55
+ });
56
+ }
57
+ if (value === null) el.removeAttribute(name);
58
+ else el.setAttribute(name, value);
59
+ },
60
+ dispose() {
61
+ if (disposed) return;
62
+ disposed = true;
63
+ for (const fn of cleanups.reverse()) fn();
64
+ cleanups.length = 0;
65
+ },
66
+ get disposed() {
67
+ return disposed;
68
+ },
69
+ };
70
+ }