loopwind 0.17.0 → 0.18.1

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 (341) hide show
  1. package/dist/sdk/index.d.ts +2 -2
  2. package/dist/sdk/index.d.ts.map +1 -1
  3. package/dist/sdk/index.js +1 -1
  4. package/dist/sdk/index.js.map +1 -1
  5. package/dist/sdk/template.d.ts +97 -0
  6. package/dist/sdk/template.d.ts.map +1 -1
  7. package/dist/sdk/template.js +217 -0
  8. package/dist/sdk/template.js.map +1 -1
  9. package/examples/code-editor-templates.ts +173 -0
  10. package/examples/nextjs-template-import.ts +58 -0
  11. package/examples/visual-builder-templates.ts +336 -0
  12. package/package.json +3 -1
  13. package/test-jsx-support.mjs +120 -0
  14. package/test-sdk-user-templates.mjs +469 -0
  15. package/website/.astro/integrations/_inox-tools_astro-when/types.d.ts +1 -0
  16. package/website/.astro/types.d.ts +1 -0
  17. package/website/DEPLOYMENT.md +142 -0
  18. package/website/OG_IMAGES.md +141 -0
  19. package/website/astro.config.mjs +9 -1
  20. package/website/dist/.gitkeep +1 -0
  21. package/website/dist/_astro/agents.Yx-L_igG.css +1 -0
  22. package/website/dist/_routes.json +30 -0
  23. package/website/dist/_worker.js/_@astrojs-ssr-adapter.mjs +1033 -0
  24. package/website/dist/_worker.js/_astro-internal_middleware.mjs +40 -0
  25. package/website/dist/_worker.js/chunks/abap_BTmsHiP5.mjs +1 -0
  26. package/website/dist/_worker.js/chunks/actionscript-3_DmBelb1E.mjs +1 -0
  27. package/website/dist/_worker.js/chunks/ada_8-E0ahCN.mjs +1 -0
  28. package/website/dist/_worker.js/chunks/andromeeda_XI-CXx50.mjs +1 -0
  29. package/website/dist/_worker.js/chunks/angular-html_DKGh3gGH.mjs +1 -0
  30. package/website/dist/_worker.js/chunks/angular-ts_-qZGsJoA.mjs +1 -0
  31. package/website/dist/_worker.js/chunks/apache_ijTUt0Ee.mjs +1 -0
  32. package/website/dist/_worker.js/chunks/apex_agu1c6Sh.mjs +1 -0
  33. package/website/dist/_worker.js/chunks/apl_Bj2f7Art.mjs +1 -0
  34. package/website/dist/_worker.js/chunks/applescript_B_vXrOh3.mjs +1 -0
  35. package/website/dist/_worker.js/chunks/ara_DCEQ2rnh.mjs +1 -0
  36. package/website/dist/_worker.js/chunks/asciidoc_CGN_EkYS.mjs +1 -0
  37. package/website/dist/_worker.js/chunks/asm_BBWZgnDp.mjs +1 -0
  38. package/website/dist/_worker.js/chunks/astro/assets-service_j52rQLzU.mjs +721 -0
  39. package/website/dist/_worker.js/chunks/astro/server_Y5_QHO8v.mjs +2401 -0
  40. package/website/dist/_worker.js/chunks/astro-designed-error-pages_BNTLO-TA.mjs +542 -0
  41. package/website/dist/_worker.js/chunks/astro_Dr_hht3h.mjs +1 -0
  42. package/website/dist/_worker.js/chunks/aurora-x_9GHG8nSq.mjs +1 -0
  43. package/website/dist/_worker.js/chunks/awk_DHRvhXot.mjs +1 -0
  44. package/website/dist/_worker.js/chunks/ayu-dark_CcvqmEHE.mjs +1 -0
  45. package/website/dist/_worker.js/chunks/ballerina_C7SdeSZb.mjs +1 -0
  46. package/website/dist/_worker.js/chunks/bat_Dv4A3u45.mjs +1 -0
  47. package/website/dist/_worker.js/chunks/beancount_BfPf9Luv.mjs +1 -0
  48. package/website/dist/_worker.js/chunks/berry_B8rfM3lL.mjs +1 -0
  49. package/website/dist/_worker.js/chunks/bibtex_TcjYgtJM.mjs +1 -0
  50. package/website/dist/_worker.js/chunks/bicep_CrlFWCdN.mjs +1 -0
  51. package/website/dist/_worker.js/chunks/blade_lanKVYID.mjs +1 -0
  52. package/website/dist/_worker.js/chunks/bsl_BhppzXMB.mjs +1 -0
  53. package/website/dist/_worker.js/chunks/c_6FBALJTK.mjs +1 -0
  54. package/website/dist/_worker.js/chunks/cadence_2txU9LVE.mjs +1 -0
  55. package/website/dist/_worker.js/chunks/cairo_BkrFAIlP.mjs +1 -0
  56. package/website/dist/_worker.js/chunks/catppuccin-frappe_CkEqIYhU.mjs +1 -0
  57. package/website/dist/_worker.js/chunks/catppuccin-latte_DG4Gx_-v.mjs +1 -0
  58. package/website/dist/_worker.js/chunks/catppuccin-macchiato_Cwi3vCXf.mjs +1 -0
  59. package/website/dist/_worker.js/chunks/catppuccin-mocha_L9_OPlFX.mjs +1 -0
  60. package/website/dist/_worker.js/chunks/clarity_BEAe4Ulu.mjs +1 -0
  61. package/website/dist/_worker.js/chunks/clojure_VnUX6p2g.mjs +1 -0
  62. package/website/dist/_worker.js/chunks/cmake_0-SGkZEj.mjs +1 -0
  63. package/website/dist/_worker.js/chunks/cobol_92M_KGaE.mjs +1 -0
  64. package/website/dist/_worker.js/chunks/codeowners_CzMwskBv.mjs +1 -0
  65. package/website/dist/_worker.js/chunks/codeql_DWJZNHv1.mjs +1 -0
  66. package/website/dist/_worker.js/chunks/coffee_CQjKU2fh.mjs +1 -0
  67. package/website/dist/_worker.js/chunks/common-lisp_BBLWDpS5.mjs +1 -0
  68. package/website/dist/_worker.js/chunks/coq_hedRFV3D.mjs +1 -0
  69. package/website/dist/_worker.js/chunks/cpp_DlS1i6Zs.mjs +1 -0
  70. package/website/dist/_worker.js/chunks/crystal_D6n65fKV.mjs +1 -0
  71. package/website/dist/_worker.js/chunks/csharp_C6FCVFzc.mjs +1 -0
  72. package/website/dist/_worker.js/chunks/css_C5uJEgmJ.mjs +1 -0
  73. package/website/dist/_worker.js/chunks/csv_CtMYuuJl.mjs +1 -0
  74. package/website/dist/_worker.js/chunks/cue_BsPexqx6.mjs +1 -0
  75. package/website/dist/_worker.js/chunks/cypher_apzf6OBi.mjs +1 -0
  76. package/website/dist/_worker.js/chunks/d_DcvIRcgm.mjs +1 -0
  77. package/website/dist/_worker.js/chunks/dark-plus_C01ONtzj.mjs +1 -0
  78. package/website/dist/_worker.js/chunks/dart_WkzM5WrV.mjs +1 -0
  79. package/website/dist/_worker.js/chunks/dax_DjXAO5V4.mjs +1 -0
  80. package/website/dist/_worker.js/chunks/desktop_C92LCxdc.mjs +1 -0
  81. package/website/dist/_worker.js/chunks/diff_CVwM_9XJ.mjs +1 -0
  82. package/website/dist/_worker.js/chunks/docker_DPzgJf6Z.mjs +1 -0
  83. package/website/dist/_worker.js/chunks/dotenv_D_vgANvA.mjs +1 -0
  84. package/website/dist/_worker.js/chunks/dracula-soft_CLnUBwFm.mjs +1 -0
  85. package/website/dist/_worker.js/chunks/dracula_lBVpb6Lb.mjs +1 -0
  86. package/website/dist/_worker.js/chunks/dream-maker_DTLbzd_J.mjs +1 -0
  87. package/website/dist/_worker.js/chunks/edge_i54JYm3_.mjs +1 -0
  88. package/website/dist/_worker.js/chunks/elixir_BJCIjTu4.mjs +1 -0
  89. package/website/dist/_worker.js/chunks/elm_BbXD39-_.mjs +1 -0
  90. package/website/dist/_worker.js/chunks/emacs-lisp_pxa5cXaN.mjs +1 -0
  91. package/website/dist/_worker.js/chunks/erb_Ccjijeee.mjs +1 -0
  92. package/website/dist/_worker.js/chunks/erlang_B2VM_hi7.mjs +1 -0
  93. package/website/dist/_worker.js/chunks/everforest-dark_BxvIPBim.mjs +1 -0
  94. package/website/dist/_worker.js/chunks/everforest-light_B7VoyaJM.mjs +1 -0
  95. package/website/dist/_worker.js/chunks/fennel_D-uo7X6c.mjs +1 -0
  96. package/website/dist/_worker.js/chunks/fish_BjePoK3m.mjs +1 -0
  97. package/website/dist/_worker.js/chunks/fluent_C8fgkzLX.mjs +1 -0
  98. package/website/dist/_worker.js/chunks/fortran-fixed-form_D1pu5zrc.mjs +1 -0
  99. package/website/dist/_worker.js/chunks/fortran-free-form_CSGOhJD6.mjs +1 -0
  100. package/website/dist/_worker.js/chunks/fsharp_B0xy-A4Y.mjs +1 -0
  101. package/website/dist/_worker.js/chunks/gdresource_CWppjlHq.mjs +1 -0
  102. package/website/dist/_worker.js/chunks/gdscript_eQCHchcS.mjs +1 -0
  103. package/website/dist/_worker.js/chunks/gdshader_C4kxepX7.mjs +1 -0
  104. package/website/dist/_worker.js/chunks/genie_ACtQLcDW.mjs +1 -0
  105. package/website/dist/_worker.js/chunks/gherkin_BFp2uKUd.mjs +1 -0
  106. package/website/dist/_worker.js/chunks/git-commit_CLg9ZwMV.mjs +1 -0
  107. package/website/dist/_worker.js/chunks/git-rebase_DG8A80Nt.mjs +1 -0
  108. package/website/dist/_worker.js/chunks/github-dark-default_BI0EP2Kv.mjs +1 -0
  109. package/website/dist/_worker.js/chunks/github-dark-dimmed_a_NIC0Xb.mjs +1 -0
  110. package/website/dist/_worker.js/chunks/github-dark-high-contrast_jZGqT7hk.mjs +1 -0
  111. package/website/dist/_worker.js/chunks/github-dark_CHCDNd2O.mjs +1 -0
  112. package/website/dist/_worker.js/chunks/github-light-default_DRbOW5RG.mjs +1 -0
  113. package/website/dist/_worker.js/chunks/github-light-high-contrast_tn_kWutM.mjs +1 -0
  114. package/website/dist/_worker.js/chunks/github-light_D9brYzot.mjs +1 -0
  115. package/website/dist/_worker.js/chunks/gleam_Dmhu1oxW.mjs +1 -0
  116. package/website/dist/_worker.js/chunks/glimmer-js_BfZbXy8A.mjs +1 -0
  117. package/website/dist/_worker.js/chunks/glimmer-ts_B9QVICrD.mjs +1 -0
  118. package/website/dist/_worker.js/chunks/glsl_DD2PPwOs.mjs +1 -0
  119. package/website/dist/_worker.js/chunks/gnuplot_D2OYChUX.mjs +1 -0
  120. package/website/dist/_worker.js/chunks/go_DYGFTe3h.mjs +1 -0
  121. package/website/dist/_worker.js/chunks/graphql_B7XsT3nH.mjs +1 -0
  122. package/website/dist/_worker.js/chunks/groovy_BO12Uwkl.mjs +1 -0
  123. package/website/dist/_worker.js/chunks/hack_CB2_ztCP.mjs +1 -0
  124. package/website/dist/_worker.js/chunks/haml_CyfDcDD3.mjs +1 -0
  125. package/website/dist/_worker.js/chunks/handlebars_CfpxpWm2.mjs +1 -0
  126. package/website/dist/_worker.js/chunks/haskell_jUeC5uN5.mjs +1 -0
  127. package/website/dist/_worker.js/chunks/haxe_B6GxP1WB.mjs +1 -0
  128. package/website/dist/_worker.js/chunks/hcl_DwoHV2oh.mjs +1 -0
  129. package/website/dist/_worker.js/chunks/hjson_DV7cJRk4.mjs +1 -0
  130. package/website/dist/_worker.js/chunks/hlsl_BlFCscPI.mjs +1 -0
  131. package/website/dist/_worker.js/chunks/houston_COBFG1Mx.mjs +1 -0
  132. package/website/dist/_worker.js/chunks/html-derivative_C9pJ337h.mjs +1 -0
  133. package/website/dist/_worker.js/chunks/html_D1OkrZS5.mjs +1 -0
  134. package/website/dist/_worker.js/chunks/http_DIGXRqvJ.mjs +1 -0
  135. package/website/dist/_worker.js/chunks/hxml_DEwh9i-c.mjs +1 -0
  136. package/website/dist/_worker.js/chunks/hy_DDoIgW1K.mjs +1 -0
  137. package/website/dist/_worker.js/chunks/imba_B00zbHo4.mjs +1 -0
  138. package/website/dist/_worker.js/chunks/index_C1UTDwYg.mjs +1861 -0
  139. package/website/dist/_worker.js/chunks/ini_D7XQA_p8.mjs +1 -0
  140. package/website/dist/_worker.js/chunks/java_B9wdFd8K.mjs +1 -0
  141. package/website/dist/_worker.js/chunks/javascript_CLsPGOON.mjs +1 -0
  142. package/website/dist/_worker.js/chunks/jinja_jarBCAN1.mjs +1 -0
  143. package/website/dist/_worker.js/chunks/jison_oGg3J708.mjs +1 -0
  144. package/website/dist/_worker.js/chunks/json5_DlZ1Kyaa.mjs +1 -0
  145. package/website/dist/_worker.js/chunks/json_DaYk_FMp.mjs +1 -0
  146. package/website/dist/_worker.js/chunks/jsonc_DlwgfSDs.mjs +1 -0
  147. package/website/dist/_worker.js/chunks/jsonl_BbCCVaZF.mjs +1 -0
  148. package/website/dist/_worker.js/chunks/jsonnet_Dt-G75xe.mjs +1 -0
  149. package/website/dist/_worker.js/chunks/jssm_BtKFTj2A.mjs +1 -0
  150. package/website/dist/_worker.js/chunks/jsx_DDx_xAZ8.mjs +1 -0
  151. package/website/dist/_worker.js/chunks/julia_CK0lv68l.mjs +1 -0
  152. package/website/dist/_worker.js/chunks/kanagawa-dragon_BldAK3Oo.mjs +1 -0
  153. package/website/dist/_worker.js/chunks/kanagawa-lotus_DVM8FX9_.mjs +1 -0
  154. package/website/dist/_worker.js/chunks/kanagawa-wave_Dpih0AKP.mjs +1 -0
  155. package/website/dist/_worker.js/chunks/kotlin_kWneB9V_.mjs +1 -0
  156. package/website/dist/_worker.js/chunks/kusto_BKVATd95.mjs +1 -0
  157. package/website/dist/_worker.js/chunks/laserwave_BqatxsVl.mjs +1 -0
  158. package/website/dist/_worker.js/chunks/latex_LVDcGBbc.mjs +1 -0
  159. package/website/dist/_worker.js/chunks/lean_W7qo-5M2.mjs +1 -0
  160. package/website/dist/_worker.js/chunks/less_DFNwJnBH.mjs +1 -0
  161. package/website/dist/_worker.js/chunks/light-plus_Dp0AoWsO.mjs +1 -0
  162. package/website/dist/_worker.js/chunks/liquid_D24qs0pc.mjs +1 -0
  163. package/website/dist/_worker.js/chunks/log_IPWMXriF.mjs +1 -0
  164. package/website/dist/_worker.js/chunks/logo_C6KaatrQ.mjs +1 -0
  165. package/website/dist/_worker.js/chunks/lua_CwnEf-T7.mjs +1 -0
  166. package/website/dist/_worker.js/chunks/luau_Br3-CXjS.mjs +1 -0
  167. package/website/dist/_worker.js/chunks/make_UBNG-kOo.mjs +1 -0
  168. package/website/dist/_worker.js/chunks/markdown_C7mhJFCm.mjs +1 -0
  169. package/website/dist/_worker.js/chunks/marko_4tchUvI7.mjs +1 -0
  170. package/website/dist/_worker.js/chunks/material-theme-darker_SKtaNEPn.mjs +1 -0
  171. package/website/dist/_worker.js/chunks/material-theme-lighter_zOX_DZCH.mjs +1 -0
  172. package/website/dist/_worker.js/chunks/material-theme-ocean_BN9WbhdC.mjs +1 -0
  173. package/website/dist/_worker.js/chunks/material-theme-palenight_DT_covjH.mjs +1 -0
  174. package/website/dist/_worker.js/chunks/material-theme_6RpeM3kc.mjs +1 -0
  175. package/website/dist/_worker.js/chunks/matlab_DCOXsPKR.mjs +1 -0
  176. package/website/dist/_worker.js/chunks/mdc_B9gb2UFP.mjs +1 -0
  177. package/website/dist/_worker.js/chunks/mdx_DGU7Nu9u.mjs +1 -0
  178. package/website/dist/_worker.js/chunks/mermaid_B69URzsZ.mjs +1 -0
  179. package/website/dist/_worker.js/chunks/min-dark_BgxifOMI.mjs +1 -0
  180. package/website/dist/_worker.js/chunks/min-light_BrPjXxUp.mjs +1 -0
  181. package/website/dist/_worker.js/chunks/mipsasm_9U-4_t7k.mjs +1 -0
  182. package/website/dist/_worker.js/chunks/mojo_B0wt7ug3.mjs +1 -0
  183. package/website/dist/_worker.js/chunks/monokai_B6Pxpoyi.mjs +1 -0
  184. package/website/dist/_worker.js/chunks/move_1eid4CyR.mjs +1 -0
  185. package/website/dist/_worker.js/chunks/narrat_Ds6-p5JZ.mjs +1 -0
  186. package/website/dist/_worker.js/chunks/nextflow_v2N1Qlqa.mjs +1 -0
  187. package/website/dist/_worker.js/chunks/nginx_Bp9Ab2NH.mjs +1 -0
  188. package/website/dist/_worker.js/chunks/night-owl_CdwOw_sc.mjs +1 -0
  189. package/website/dist/_worker.js/chunks/nim_BXGDUe53.mjs +1 -0
  190. package/website/dist/_worker.js/chunks/nix_CUig1nJH.mjs +1 -0
  191. package/website/dist/_worker.js/chunks/noop-middleware_DlWGj5t5.mjs +10 -0
  192. package/website/dist/_worker.js/chunks/nord_SPoG1iae.mjs +1 -0
  193. package/website/dist/_worker.js/chunks/nushell_DJw1Lca8.mjs +1 -0
  194. package/website/dist/_worker.js/chunks/objective-c_Bktzl_CO.mjs +1 -0
  195. package/website/dist/_worker.js/chunks/objective-cpp_CP4DWdDp.mjs +1 -0
  196. package/website/dist/_worker.js/chunks/ocaml_CeEAs7bZ.mjs +1 -0
  197. package/website/dist/_worker.js/chunks/one-dark-pro_-hIwCNMi.mjs +1 -0
  198. package/website/dist/_worker.js/chunks/one-light_DSmYvJ05.mjs +1 -0
  199. package/website/dist/_worker.js/chunks/pascal_C-S_Ms_o.mjs +1 -0
  200. package/website/dist/_worker.js/chunks/perl_CKamvo15.mjs +1 -0
  201. package/website/dist/_worker.js/chunks/php_BlmcX_F3.mjs +1 -0
  202. package/website/dist/_worker.js/chunks/plastic_Ryt8tVoA.mjs +1 -0
  203. package/website/dist/_worker.js/chunks/plsql_Cb3v7cBj.mjs +1 -0
  204. package/website/dist/_worker.js/chunks/po_DZbdNRlo.mjs +1 -0
  205. package/website/dist/_worker.js/chunks/poimandres_bYmE3_5d.mjs +1 -0
  206. package/website/dist/_worker.js/chunks/polar_pJkMGwoW.mjs +1 -0
  207. package/website/dist/_worker.js/chunks/postcss_BAXSOKgk.mjs +1 -0
  208. package/website/dist/_worker.js/chunks/powerquery_oITMGN4x.mjs +1 -0
  209. package/website/dist/_worker.js/chunks/powershell_6306-xIF.mjs +1 -0
  210. package/website/dist/_worker.js/chunks/prisma_DSDxnZGz.mjs +1 -0
  211. package/website/dist/_worker.js/chunks/prolog_CxG7tjZR.mjs +1 -0
  212. package/website/dist/_worker.js/chunks/proto_CS9ByXm1.mjs +1 -0
  213. package/website/dist/_worker.js/chunks/pug_BMtLJo6U.mjs +1 -0
  214. package/website/dist/_worker.js/chunks/puppet_BfeeSzee.mjs +1 -0
  215. package/website/dist/_worker.js/chunks/purescript_BFfueNaH.mjs +1 -0
  216. package/website/dist/_worker.js/chunks/python_Cc4Faapv.mjs +1 -0
  217. package/website/dist/_worker.js/chunks/qml_C1CTJTK8.mjs +1 -0
  218. package/website/dist/_worker.js/chunks/qmldir_nG1KaqKR.mjs +1 -0
  219. package/website/dist/_worker.js/chunks/qss_Cncxk263.mjs +1 -0
  220. package/website/dist/_worker.js/chunks/r_ChR54Ihi.mjs +1 -0
  221. package/website/dist/_worker.js/chunks/racket_BDrhptDs.mjs +1 -0
  222. package/website/dist/_worker.js/chunks/raku_07OUHa0P.mjs +1 -0
  223. package/website/dist/_worker.js/chunks/razor_DIP3INLa.mjs +1 -0
  224. package/website/dist/_worker.js/chunks/red_DOPXfj-6.mjs +1 -0
  225. package/website/dist/_worker.js/chunks/reg_B64SwEDj.mjs +1 -0
  226. package/website/dist/_worker.js/chunks/regexp_ButFGoB5.mjs +1 -0
  227. package/website/dist/_worker.js/chunks/rel_BWJAWqZD.mjs +1 -0
  228. package/website/dist/_worker.js/chunks/riscv_79gXlbsF.mjs +1 -0
  229. package/website/dist/_worker.js/chunks/rose-pine-dawn_DHIjVGd3.mjs +1 -0
  230. package/website/dist/_worker.js/chunks/rose-pine-moon_t86aEbs0.mjs +1 -0
  231. package/website/dist/_worker.js/chunks/rose-pine_BHgrcDCs.mjs +1 -0
  232. package/website/dist/_worker.js/chunks/rst_D3F4Fcpj.mjs +1 -0
  233. package/website/dist/_worker.js/chunks/ruby_Cs7vM9iv.mjs +1 -0
  234. package/website/dist/_worker.js/chunks/rust_DpyRVatH.mjs +1 -0
  235. package/website/dist/_worker.js/chunks/sas_DW45xZXN.mjs +1 -0
  236. package/website/dist/_worker.js/chunks/sass_C6SiMwN_.mjs +1 -0
  237. package/website/dist/_worker.js/chunks/scala_DlZOjNZk.mjs +1 -0
  238. package/website/dist/_worker.js/chunks/scheme_D2ezSJXu.mjs +1 -0
  239. package/website/dist/_worker.js/chunks/scss_DG5Spjqu.mjs +1 -0
  240. package/website/dist/_worker.js/chunks/sdbl_ZCYaj4VN.mjs +1 -0
  241. package/website/dist/_worker.js/chunks/shaderlab_CAcRkg1_.mjs +1 -0
  242. package/website/dist/_worker.js/chunks/shellscript_BWwhkDVh.mjs +1 -0
  243. package/website/dist/_worker.js/chunks/shellsession_BfEA3juK.mjs +1 -0
  244. package/website/dist/_worker.js/chunks/slack-dark_CL3lSpCc.mjs +1 -0
  245. package/website/dist/_worker.js/chunks/slack-ochin_DdZKOQVh.mjs +1 -0
  246. package/website/dist/_worker.js/chunks/smalltalk_DgilzSui.mjs +1 -0
  247. package/website/dist/_worker.js/chunks/snazzy-light_eJU08Pz_.mjs +1 -0
  248. package/website/dist/_worker.js/chunks/solarized-dark_Dg_YQywx.mjs +1 -0
  249. package/website/dist/_worker.js/chunks/solarized-light_BnIsrA6p.mjs +1 -0
  250. package/website/dist/_worker.js/chunks/solidity_DkseH8pQ.mjs +1 -0
  251. package/website/dist/_worker.js/chunks/soy_DU7bOYoG.mjs +1 -0
  252. package/website/dist/_worker.js/chunks/sparql_BuI1DBDH.mjs +1 -0
  253. package/website/dist/_worker.js/chunks/splunk_B8Ha9Pkg.mjs +1 -0
  254. package/website/dist/_worker.js/chunks/sql_BniHwea5.mjs +1 -0
  255. package/website/dist/_worker.js/chunks/ssh-config_CkE1GuVe.mjs +1 -0
  256. package/website/dist/_worker.js/chunks/stata_Dtqpbd_l.mjs +1 -0
  257. package/website/dist/_worker.js/chunks/stylus_CXTtglzO.mjs +1 -0
  258. package/website/dist/_worker.js/chunks/svelte_BjWYcUCN.mjs +1 -0
  259. package/website/dist/_worker.js/chunks/swift_BzHql_rM.mjs +1 -0
  260. package/website/dist/_worker.js/chunks/synthwave-84_DLRNhxNA.mjs +1 -0
  261. package/website/dist/_worker.js/chunks/system-verilog_ChyInPph.mjs +1 -0
  262. package/website/dist/_worker.js/chunks/systemd_Bi9Qa2qD.mjs +1 -0
  263. package/website/dist/_worker.js/chunks/talonscript_B3sH_Y-V.mjs +1 -0
  264. package/website/dist/_worker.js/chunks/tasl_BJ5yipRs.mjs +1 -0
  265. package/website/dist/_worker.js/chunks/tcl_CoJQjNoP.mjs +1 -0
  266. package/website/dist/_worker.js/chunks/templ_CrU7Ffil.mjs +1 -0
  267. package/website/dist/_worker.js/chunks/terraform_DT9JSFpC.mjs +1 -0
  268. package/website/dist/_worker.js/chunks/tex_5PKu2yA0.mjs +1 -0
  269. package/website/dist/_worker.js/chunks/tokyo-night_Buo8OK7-.mjs +1 -0
  270. package/website/dist/_worker.js/chunks/toml_CPuXX3oc.mjs +1 -0
  271. package/website/dist/_worker.js/chunks/ts-tags_D0M_1VSH.mjs +1 -0
  272. package/website/dist/_worker.js/chunks/tsv_CuivVNot.mjs +1 -0
  273. package/website/dist/_worker.js/chunks/tsx_MkuGr8MY.mjs +1 -0
  274. package/website/dist/_worker.js/chunks/turtle_BqgEPK7f.mjs +1 -0
  275. package/website/dist/_worker.js/chunks/twig_r1G9rpYJ.mjs +1 -0
  276. package/website/dist/_worker.js/chunks/typescript_Au5buqzM.mjs +1 -0
  277. package/website/dist/_worker.js/chunks/typespec_47rhBK_z.mjs +1 -0
  278. package/website/dist/_worker.js/chunks/typst_BAtuQLh-.mjs +1 -0
  279. package/website/dist/_worker.js/chunks/v_BIvWImHg.mjs +1 -0
  280. package/website/dist/_worker.js/chunks/vala_DYEacj30.mjs +1 -0
  281. package/website/dist/_worker.js/chunks/vb_CikQuqGJ.mjs +1 -0
  282. package/website/dist/_worker.js/chunks/verilog_BQRENwI-.mjs +1 -0
  283. package/website/dist/_worker.js/chunks/vesper_DA0kvTmj.mjs +1 -0
  284. package/website/dist/_worker.js/chunks/vhdl_DHscJIyg.mjs +1 -0
  285. package/website/dist/_worker.js/chunks/viml_F2pvMwvG.mjs +1 -0
  286. package/website/dist/_worker.js/chunks/vitesse-black_D9tjNzd0.mjs +1 -0
  287. package/website/dist/_worker.js/chunks/vitesse-dark_Bnm5d0hd.mjs +1 -0
  288. package/website/dist/_worker.js/chunks/vitesse-light_CHwbyjNR.mjs +1 -0
  289. package/website/dist/_worker.js/chunks/vue-html_DyYtbbMK.mjs +1 -0
  290. package/website/dist/_worker.js/chunks/vue_DofN6juy.mjs +1 -0
  291. package/website/dist/_worker.js/chunks/vyper_CiR0m-OV.mjs +1 -0
  292. package/website/dist/_worker.js/chunks/wasm_CwIGgRGf.mjs +1 -0
  293. package/website/dist/_worker.js/chunks/wasm_jKWhg0J0.mjs +1 -0
  294. package/website/dist/_worker.js/chunks/wenyan_DKvVZKXW.mjs +1 -0
  295. package/website/dist/_worker.js/chunks/wgsl_BOWZY7yw.mjs +1 -0
  296. package/website/dist/_worker.js/chunks/wikitext_CXDhhHPy.mjs +1 -0
  297. package/website/dist/_worker.js/chunks/wolfram_ChkmGnW0.mjs +1 -0
  298. package/website/dist/_worker.js/chunks/xml_DXH3hHIu.mjs +1 -0
  299. package/website/dist/_worker.js/chunks/xsl_DuP2mFjg.mjs +1 -0
  300. package/website/dist/_worker.js/chunks/yaml_IGiEkTge.mjs +1 -0
  301. package/website/dist/_worker.js/chunks/zenscript_59iXGyNw.mjs +1 -0
  302. package/website/dist/_worker.js/chunks/zig_DKzb0zdT.mjs +1 -0
  303. package/website/dist/_worker.js/index.js +53 -0
  304. package/website/dist/_worker.js/manifest_BAAoOzaU.mjs +98 -0
  305. package/website/dist/_worker.js/pages/_image.astro.mjs +24 -0
  306. package/website/dist/_worker.js/pages/agents.astro.mjs +1 -0
  307. package/website/dist/_worker.js/pages/animation.astro.mjs +1 -0
  308. package/website/dist/_worker.js/pages/api/raw-markdown/_---path_.astro.mjs +44 -0
  309. package/website/dist/_worker.js/pages/config.astro.mjs +1 -0
  310. package/website/dist/_worker.js/pages/fonts.astro.mjs +1 -0
  311. package/website/dist/_worker.js/pages/getting-started.astro.mjs +1 -0
  312. package/website/dist/_worker.js/pages/helpers.astro.mjs +1 -0
  313. package/website/dist/_worker.js/pages/images.astro.mjs +1 -0
  314. package/website/dist/_worker.js/pages/index.astro.mjs +1 -0
  315. package/website/dist/_worker.js/pages/llm.txt.astro.mjs +1 -0
  316. package/website/dist/_worker.js/pages/preview.astro.mjs +1 -0
  317. package/website/dist/_worker.js/pages/sdk.astro.mjs +1 -0
  318. package/website/dist/_worker.js/pages/sitemap.xml.astro.mjs +1 -0
  319. package/website/dist/_worker.js/pages/styling.astro.mjs +1 -0
  320. package/website/dist/_worker.js/pages/templates.astro.mjs +1 -0
  321. package/website/dist/_worker.js/pages/video.astro.mjs +1 -0
  322. package/website/dist/_worker.js/renderers.mjs +57 -0
  323. package/website/dist/agents/index.html +3 -2
  324. package/website/dist/animation/index.html +402 -128
  325. package/website/dist/config/index.html +184 -0
  326. package/website/dist/fonts/index.html +56 -52
  327. package/website/dist/getting-started/index.html +107 -0
  328. package/website/dist/helpers/index.html +8 -8
  329. package/website/dist/images/index.html +50 -23
  330. package/website/dist/index.html +114 -148
  331. package/website/dist/llm.txt +751 -1055
  332. package/website/dist/preview/index.html +111 -0
  333. package/website/dist/robots.txt +35 -0
  334. package/website/dist/sdk/index.html +390 -52
  335. package/website/dist/sitemap.xml +76 -0
  336. package/website/dist/styling/index.html +10 -7
  337. package/website/dist/templates/index.html +11 -59
  338. package/website/dist/video/index.html +106 -537
  339. package/website/package-lock.json +1077 -17
  340. package/website/templates/og-image.tsx +61 -0
  341. package/website/dist/_astro/agents.I1-fN38o.css +0 -1
@@ -1,7 +1,7 @@
1
1
  # loopwind - Complete Documentation for LLMs
2
2
 
3
3
  This is a comprehensive, single-file documentation for loopwind, optimized for LLM consumption.
4
- Generated: 2025-11-18T13:58:54.278Z
4
+ Generated: 2025-11-20T10:57:21.487Z
5
5
 
6
6
  ---
7
7
 
@@ -13,224 +13,213 @@ FILE: index.mdx
13
13
 
14
14
  import CodeVideoDemo from '../components/CodeVideoDemo.astro';
15
15
 
16
- export const videoCode = `export default function VideoIntro({ tw, title, subtitle }) {
16
+ export const videoCode = `// _loopwind/templates/video-intro.tsx
17
+ export const meta = {
18
+ name: 'video-intro',
19
+ type: 'video',
20
+ size: { width: 1200, height: 676 },
21
+ video: { fps: 30, duration: 3 },
22
+ props: {
23
+ title: 'string',
24
+ subtitle: 'string'
25
+ }
26
+ };
27
+
28
+ export default function VideoIntro({ tw, title, subtitle }) {
17
29
  return (
18
30
  <div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700 gap-4')}>
19
- <h1 style={tw('text-8xl font-bold text-white ease-out animate-bounce-in/0/0.4')}>
31
+ <div style={tw('relative flex flex-row items-center gap-2 ease-out')}>
32
+ {/* loop-ping/500: scale up + fade out every 500ms (radar effect) */}
33
+ <div style={tw('w-3 h-3 bg-white rounded-full loop-ping/500')} />
34
+ <div style={tw('text-white')}>Label</div>
35
+ </div>
36
+ {/* enter-fade-in/0/1200: fade in starting at 0ms, lasting 1200ms */}
37
+ <h1 style={tw('text-7xl font-bold text-white enter-fade-in/0/1200')}>
20
38
  {title}
21
39
  </h1>
22
- <p style={tw('text-2xl text-white/80 ease-out animate-fade-in-up/0.3/0.7')}>
40
+ {/* enter-fade-in/300/1200: fade in starting at 300ms, lasting 1200ms */}
41
+ <p style={tw('text-2xl text-white/80 ease-out enter-fade-in/300/1200')}>
23
42
  {subtitle}
24
43
  </p>
25
44
  </div>
26
45
  );
27
- }`;
28
-
29
- ## Video Templates
30
-
31
- <CodeVideoDemo
32
- code={videoCode}
33
- videoSrc="/demo-intro.mp4"
34
- title="Animated Intro Example"
35
- />
36
-
37
- **Render it:**
38
- ```bash
39
- loopwind render video-intro '{"title":"Welcome!","subtitle":"Built with loopwind"}' --out intro.mp4
40
- ```
41
-
42
- **Output:** `_loopwind/outputs/intro.mp4` (1920×1080px, 3 seconds @ 30fps)
43
-
44
- **Perfect for:** Social media intros, animated logos, tutorial overlays, and product showcases.
46
+ }
47
+ `;
45
48
 
46
- ## Image Templates
49
+ export const imageCode = `// _loopwind/templates/og-image.tsx
50
+ export const meta = {
51
+ name: 'og-image',
52
+ type: 'image',
53
+ size: { width: 1200, height: 630 },
54
+ props: {
55
+ title: 'string',
56
+ description: 'string'
57
+ }
58
+ };
47
59
 
48
- ```tsx
49
- // Social media card template
50
- export default function OGImage({ tw, image, title, description, logo }) {
60
+ export default function OGImage({ tw, title, description }) {
51
61
  return (
52
- <div style={tw('flex w-full h-full bg-white')}>
53
- <div style={tw('flex-1 flex flex-col justify-between p-12')}>
54
- <img src={image(logo)} style={tw('h-12 w-auto')} />
55
-
62
+ <div style={tw('flex w-full h-full bg-card p-16')}>
63
+ <div style={tw('flex flex-col justify-between flex-1')}>
64
+ <div style={tw('text-sm text-muted-foreground')}>
65
+ loopwind.dev
66
+ </div>
67
+
56
68
  <div>
57
- <h1 style={tw('text-5xl font-bold text-gray-900 mb-4')}>
69
+ <h1 style={tw('text-6xl font-bold text-foreground mb-6')}>
58
70
  {title}
59
71
  </h1>
60
- <p style={tw('text-xl text-gray-600')}>
72
+ <p style={tw('text-2xl text-muted-foreground')}>
61
73
  {description}
62
74
  </p>
63
75
  </div>
64
76
 
65
- <p style={tw('text-gray-400')}>yoursite.com</p>
66
- </div>
67
-
68
- <div style={tw('flex-1')}>
69
- <img
70
- src={image(featuredImage)}
71
- style={tw('w-full h-full object-cover')}
72
- />
77
+ <div style={tw('flex items-center gap-3')}>
78
+ <span style={tw('text-5xl')}>↫</span>
79
+ <span style={tw('text-3xl font-semibold bg-linear-to-r from-brand-from to-brand-to bg-clip-text text-transparent')}>
80
+ loopwind
81
+ </span>
82
+ </div>
73
83
  </div>
74
84
  </div>
75
85
  );
76
86
  }
77
- ```
78
-
79
- **Render it:**
80
- ```bash
81
- loopwind render og-image '{"title":"Hello World","description":"My awesome blog post"}'
82
- ```
83
-
84
- **Output:** `_loopwind/outputs/og-image.png` (1200×630px)
85
-
86
- **Perfect for:** Open Graph images, Twitter cards, blog headers, product cards, and quote graphics.
87
+ `;
87
88
 
88
- ## Features
89
+ ## Generate Images and Videos with Code
89
90
 
90
- - 🎨 **shadcn/ui Design System**: Beautiful, semantic colors out of the box (`text-primary`, `bg-card`, etc.)
91
- - ✨ **Template-based**: Install design templates like you install UI components
92
- - 🖼️ **Serverless Image Rendering**: Pure JavaScript rendering with Satori - works on Vercel, Netlify, Cloudflare Workers
93
- - 🎬 **Serverless Video Rendering**: WASM-based MP4 encoding - 12x faster than traditional approaches
94
- - 🎨 **Tailwind CSS Support**: Style templates with Tailwind utility classes + opacity modifiers (`bg-primary/50`)
95
- - 📱 **Built-in Helpers**: QR codes, image embedding, video backgrounds, template composition
96
- - ✅ **Smart Validation**: Automatic prop and template validation with helpful error messages
97
- - 🚀 **Framework-agnostic**: Works with Node, Bun, Deno, Laravel, Python, and more
98
- - 🤖 **Agent-friendly**: Machine-readable metadata for LLMs
99
- - 📦 **Easy installation**: `npx loopwind add template-name`
100
- - 🌐 **Pure JavaScript/WASM**: No native dependencies, works everywhere
91
+ Write React components, render them as images or videos. No design tools needed.
101
92
 
102
- ## Quick Start
93
+ <CodeVideoDemo
94
+ code={videoCode}
95
+ videoSrc="/demo-intro.mp4"
96
+ title="Video Templates"
97
+ />
103
98
 
104
- ### Installation
99
+ **Render it in one command:**
105
100
 
106
101
  ```bash
107
- npm install -g loopwind
102
+ loopwind render video-intro '{"title":"Welcome!","subtitle":"Built with loopwind"}'
108
103
  ```
109
104
 
110
- Or use with npx:
105
+ **Perfect for:** Social media intros, animated logos, product demos, tutorial overlays.
111
106
 
112
- ```bash
113
- npx loopwind --help
114
- ```
107
+ ---
115
108
 
116
- ### Initialize in Your Project
109
+ ## Image Templates
117
110
 
118
- Navigate to any project folder and run:
111
+ Generate Open Graph images, Twitter cards, and more:
119
112
 
120
- ```bash
121
- loopwind init
113
+ ```tsx
114
+ {imageCode}
122
115
  ```
123
116
 
124
- This creates a `_loopwind/` folder where templates and outputs will be located.
125
-
126
- ### Install a Template
127
-
128
- #### 1. Official Templates
117
+ **Render it:**
129
118
 
130
119
  ```bash
131
- loopwind add image-template
132
- loopwind add video-template
120
+ loopwind render og-image '{"title":"Hello World","description":"My awesome post"}'
133
121
  ```
134
122
 
135
- Templates are installed to: `_loopwind/templates/<template>/` (customizable in `loopwind.json`)
136
-
137
- **Benefits:**
138
- - Templates are local to your project
139
- - Version controlled with your project
140
- - Easy to share within your team
123
+ **Output:** `_loopwind/outputs/og-image.png` (1200×630px)
141
124
 
142
- ### Output a Template
125
+ ---
143
126
 
144
- ```bash
145
- loopwind render template-name '{"title":"Hello World","subtitle":"Built with loopwind"}'
146
- ```
127
+ ## Why loopwind?
147
128
 
148
- ## Commands
129
+ ### 🎨 Beautiful by Default
149
130
 
150
- ### `loopwind add <source>`
131
+ Built on shadcn/ui design system with semantic colors (`text-primary`, `bg-card`) and Tailwind CSS utilities. Your templates look professional out of the box.
151
132
 
152
- Install a template from various sources:
133
+ ### Blazing Fast Rendering
153
134
 
154
- ```bash
155
- # From registry
156
- loopwind add banner-hero
135
+ - **Serverless-ready**: Pure JavaScript rendering with Satori - works on Vercel, Netlify, Cloudflare Workers
136
+ - **WASM-powered video**: MP4 encoding that's 12x faster than traditional approaches
137
+ - **No dependencies**: No FFmpeg, no Chrome, no Docker. Just JavaScript.
157
138
 
158
- # From GitHub
159
- loopwind add github:username/repo/path/to/template
139
+ ### 📦 Template Marketplace (coming soon)
160
140
 
161
- # From URL
162
- loopwind add https://example.com/my-template.json
141
+ Install templates like UI components. Browse, add, and customize in seconds:
163
142
 
164
- # From local path
165
- loopwind add ./my-templates/custom-banner
143
+ ```bash
144
+ loopwind add banner-hero
145
+ loopwind add video-intro
146
+ loopwind add product-card
166
147
  ```
167
148
 
168
- ### `loopwind list`
149
+ ### 🤖 Built for AI Agents
169
150
 
170
- List all installed templates:
151
+ Machine-readable metadata and simple CLI make it perfect for automation:
171
152
 
172
153
  ```bash
173
- loopwind list
154
+ # Generate social media content programmatically
155
+ loopwind render og-image '{"title":"'$TITLE'","description":"'$DESC'"}'
174
156
  ```
175
157
 
176
- ### `loopwind render <template> <props> [options]`
158
+ ### 🚀 Framework Agnostic
177
159
 
178
- Render an image or video:
160
+ Works everywhere: Node.js, Bun, Deno, Python, PHP, Go. If it can run a CLI command, it can use loopwind.
179
161
 
180
- ```bash
181
- # Image with inline props
182
- loopwind render banner-hero '{"title":"Hello World"}'
162
+ ---
183
163
 
184
- # Video with inline props
185
- loopwind render video-intro '{"title":"Welcome"}' --out intro.mp4
164
+ ## Key Features
186
165
 
187
- # Using a props file
188
- loopwind render banner-hero props.json
166
+ - **Template-based workflow** - Install and customize design templates
167
+ - 🎬 **Animation classes** - `enter-fade-in/0/500`, `loop-spin/1000` - no manual calculations
168
+ - 📱 **Built-in helpers** - QR codes, image embedding, video backgrounds, template composition
169
+ - ✅ **Smart validation** - Automatic prop and template validation with helpful errors
170
+ - 🎨 **Full Tailwind support** - Including opacity modifiers (`bg-primary/50`)
171
+ - 🔧 **Zero config** - Works out of the box, customize when you need to
172
+ - 📝 **Type-safe** - Full TypeScript support with prop validation
173
+ - 🌐 **Edge-ready** - Runs anywhere JavaScript runs
189
174
 
190
- # Custom output
191
- loopwind render banner-hero '{"title":"Hello"}' --out custom-name.png
175
+ ---
192
176
 
193
- # Different format
194
- loopwind render banner-hero '{"title":"Hello"}' --format jpeg
195
- ```
177
+ ## Use Cases
196
178
 
197
- Options:
198
- - `--out, -o` - Output filename (default: `_loopwind/outputs/<template>.<ext>`)
199
- - `--format` - Output format: `png`, `jpeg`, `svg` (images only)
200
- - `--quality` - JPEG quality 1-100 (default: 92)
179
+ **Marketing Automation**
180
+ - Generate personalized email headers
181
+ - Create dynamic social media posts
182
+ - Automate Open Graph images
201
183
 
202
- ### `loopwind validate <template>`
184
+ **Content Creation**
185
+ - Produce video intros and outros
186
+ - Generate thumbnail variations
187
+ - Create branded graphics at scale
203
188
 
204
- Validate a template:
189
+ **Developer Tools**
190
+ - Build dynamic README badges
191
+ - Generate documentation images
192
+ - Create automated reports
205
193
 
206
- ```bash
207
- loopwind validate banner-hero
208
- ```
209
-
210
- Checks:
211
- - Template file exists and is valid React
212
- - `export const meta` exists and is valid
213
- - Required props are defined
214
- - Fonts exist (if specified)
194
+ **E-commerce**
195
+ - Product card generation
196
+ - Sale announcement videos
197
+ - Dynamic pricing graphics
215
198
 
216
- ### `loopwind init`
199
+ ---
217
200
 
218
- Initialize loopwind in a project:
201
+ ## Get Started
219
202
 
220
203
  ```bash
204
+ npm install -g loopwind
221
205
  loopwind init
206
+ loopwind add video-intro
207
+ loopwind render video-intro '{"title":"Hello World"}'
222
208
  ```
223
209
 
224
- Creates `loopwind.json` configuration file.
210
+ [View Full Documentation](/getting-started) · [Browse Templates](/templates) · [See Examples](/images)
225
211
 
226
- ## Next Steps
212
+ ---
227
213
 
228
- - [Learn about Templates](/templates)
229
- - [Image Rendering](/images)
230
- - [Video Rendering](/video)
231
- - [Helpers (QR, Template Composition)](/helpers)
232
- - [Styling with Tailwind & shadcn/ui](/styling)
233
- - [Custom Fonts](/fonts)
214
+ ## Learn More
215
+
216
+ - [Getting Started Guide](/getting-started) - Installation and first steps
217
+ - [Templates](/templates) - Create and customize templates
218
+ - [Images](/images) - Static image generation
219
+ - [Video](/video) - Animated video rendering
220
+ - [Styling](/styling) - Tailwind & shadcn/ui integration
221
+ - [SDK](/sdk) - Programmatic usage
222
+ - [AI Agents](/agents) - Integration guide for LLMs
234
223
 
235
224
 
236
225
 
@@ -240,42 +229,22 @@ Creates `loopwind.json` configuration file.
240
229
  FILE: templates.mdx
241
230
  ================================================================================
242
231
 
243
- # Installing Templates from Different Sources
232
+ # Installing Templates
244
233
 
245
234
  ## Overview
246
-
247
235
  loopwind supports installing templates from multiple sources:
248
236
 
249
- 1. **Official Registry** (default)
237
+ 1. **Official templates**
250
238
  2. **Direct URLs**
251
- 3. **GitHub Repositories**
252
- 4. **Local Filesystem**
253
-
254
- ## 1. Official Registry (Default)
255
-
256
- Install templates from the official loopwind registry at `https://loopwind.dev/r`
257
-
258
- ```bash
259
- loopwind add banner-hero
260
- loopwind add product-card
261
- loopwind add social-og-image
262
- ```
263
-
264
- **How it works:**
265
- - Fetches from: `https://loopwind.dev/r/banner-hero`
266
- - Returns JSON with template files
267
- - Installs to: `_loopwind/templates/banner-hero/`
268
239
 
269
- ### Custom Registry
270
-
271
- Use a different registry:
240
+ ## 1. Default templates
272
241
 
273
242
  ```bash
274
- loopwind add banner-hero --registry https://my-registry.com/templates
243
+ loopwind add image-template
244
+ loopwind add video-template
275
245
  ```
276
246
 
277
247
  ## 2. Direct URLs
278
-
279
248
  Install a template from any publicly accessible URL:
280
249
 
281
250
  ```bash
@@ -296,57 +265,14 @@ loopwind add https://cdn.example.com/templates/awesome-banner.json
296
265
  "description": "My custom template",
297
266
  "files": [
298
267
  {
299
- "path": "my-template.tsx",
300
- "content": "import React from 'react';\n\nexport default function..."
301
- },
302
- {
303
- "path": "meta.json",
304
- "content": "{\"name\":\"my-template\"...}"
268
+ "path": "template.tsx",
269
+ "content": "export const meta = {...};\n\nexport default function..."
305
270
  }
306
271
  ]
307
272
  }
308
273
  ```
309
274
 
310
- ## 3. GitHub Repositories
311
-
312
- Install templates directly from GitHub repos:
313
-
314
- ```bash
315
- # From a GitHub repo (looks for template.json in repo root)
316
- loopwind add github:username/repo
317
-
318
- # From a specific path in the repo
319
- loopwind add github:username/repo/templates/banner-hero
320
-
321
- # From an organization
322
- loopwind add github:myorg/design-templates/social-media/og-image
323
- ```
324
-
325
- **How it works:**
326
- 1. Detects `github:` prefix
327
- 2. Converts to raw GitHub URL
328
- 3. Fetches `template.json` (or `meta.json` + template file)
329
- 4. Downloads all referenced files
330
- 5. Installs locally
331
-
332
- **Template structure on GitHub:**
333
-
334
- Option A: Single template.json (self-contained)
335
- ```
336
- username/repo/
337
- └── template.json # Contains all files inline
338
- ```
339
-
340
- Option B: Separate files
341
- ```
342
- username/repo/
343
- ├── meta.json
344
- ├── banner-hero.tsx
345
- └── fonts/
346
- └── Inter-Bold.woff
347
- ```
348
-
349
- ## 4. Local Filesystem
275
+ ## 3. Local Filesystem
350
276
 
351
277
  Install templates from your local filesystem:
352
278
 
@@ -369,9 +295,9 @@ loopwind add /Users/you/templates/social-card
369
295
 
370
296
  Templates are installed to `_loopwind/templates/<template-name>/` by default.
371
297
 
372
- Customize this in `loopwind.json`:
298
+ Customize this in `_loopwind/loopwind.json`:
373
299
 
374
- ```json
300
+ ```json title="_loopwind/loopwind.json"
375
301
  {
376
302
  "templatesDir": "my-custom-templates"
377
303
  }
@@ -502,20 +428,25 @@ loopwind render my-template '{"title":"Hello"}' --format svg
502
428
 
503
429
  ## Embedding Images
504
430
 
505
- Use the `image()` helper to embed external images in your templates.
431
+ Use the `image()` helper to embed images in your templates. It supports two modes:
506
432
 
507
- ### Basic Usage
433
+ 1. **Prop-based** - Load from a prop value: `image('logo')`
434
+ 2. **Direct file** - Load from template directory: `image('icon.svg')`
435
+
436
+ ### Prop-based Images
437
+
438
+ Pass the prop name to load an image path from props:
508
439
 
509
440
  ```tsx
510
441
  export default function ProductCard({ tw, image, title, background }) {
511
442
  return (
512
443
  <div style={tw('relative w-full h-full')}>
513
- {/* Background image */}
514
- <img
515
- src={image(background)}
444
+ {/* Load image from 'background' prop */}
445
+ <img
446
+ src={image('background')}
516
447
  style={tw('absolute inset-0 w-full h-full object-cover')}
517
448
  />
518
-
449
+
519
450
  {/* Content overlay */}
520
451
  <div style={tw('relative z-10 p-12')}>
521
452
  <h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
@@ -533,6 +464,55 @@ export default function ProductCard({ tw, image, title, background }) {
533
464
  }
534
465
  ```
535
466
 
467
+ ### Direct File Images
468
+
469
+ Load images directly from your template directory by including the file extension:
470
+
471
+ ```tsx
472
+ export default function ChangelogItem({ tw, image, text }) {
473
+ return (
474
+ <div style={tw('flex items-center gap-4')}>
475
+ {/* Load check.svg from template directory */}
476
+ <img
477
+ src={image('check.svg')}
478
+ style={tw('w-6 h-6')}
479
+ />
480
+ <span style={tw('text-lg')}>{text}</span>
481
+ </div>
482
+ );
483
+ }
484
+ ```
485
+
486
+ This loads `check.svg` from the same directory as your template file. You can also use subdirectories:
487
+
488
+ ```tsx
489
+ // Load from assets subdirectory
490
+ <img src={image('assets/icons/star.svg')} />
491
+
492
+ // Load from shared folder
493
+ <img src={image('shared/logo.png')} />
494
+ ```
495
+
496
+ **Template directory structure:**
497
+ ```
498
+ _loopwind/templates/my-template/
499
+ ├── template.tsx
500
+ ├── check.svg ← image('check.svg')
501
+ └── assets/
502
+ └── icons/
503
+ └── star.svg ← image('assets/icons/star.svg')
504
+ ```
505
+
506
+ ### URLs
507
+
508
+ The `image()` helper also supports loading images from URLs:
509
+
510
+ ```json
511
+ {
512
+ "background": "https://example.com/image.jpg"
513
+ }
514
+ ```
515
+
536
516
  ### Supported Image Formats
537
517
 
538
518
  - ✅ **JPEG** (`.jpg`, `.jpeg`)
@@ -730,25 +710,6 @@ Templates are cached after first load for faster subsequent renders.
730
710
 
731
711
  ## Troubleshooting
732
712
 
733
- ### Fonts Not Rendering
734
-
735
- Make sure fonts are properly defined in your template's meta:
736
-
737
- ```tsx
738
- export const meta = {
739
- name: "my-template",
740
- // ...
741
- fonts: [
742
- {
743
- name: "Inter",
744
- path: "fonts/Inter-Bold.woff",
745
- weight: 700,
746
- style: "normal"
747
- }
748
- ]
749
- };
750
- ```
751
-
752
713
  ### Images Not Loading
753
714
 
754
715
  Check file paths are relative to the props file:
@@ -805,31 +766,19 @@ loopwind render video-intro props.json --out intro.mp4
805
766
  export const meta = {
806
767
  name: "video-intro",
807
768
  type: "video",
808
- description: "Animated intro with fade-in title",
769
+ description: "Animated intro with bounce-in title",
809
770
  size: { width: 1920, height: 1080 },
810
771
  video: { fps: 30, duration: 3 },
811
772
  props: { title: "string" }
812
773
  };
813
774
 
814
- export default function VideoIntro({ tw, title, frame, progress }) {
815
- // Animate opacity based on progress
816
- const titleOpacity = progress < 0.3 ? progress / 0.3 : 1;
817
-
775
+ export default function VideoIntro({ tw, title }) {
818
776
  return (
819
777
  <div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
820
- <h1
821
- style={{
822
- ...tw('text-8xl font-bold text-white'),
823
- opacity: titleOpacity
824
- }}
825
- >
778
+ {/* Bounce in from below at start */}
779
+ <h1 style={tw('text-8xl font-bold text-white ease-out enter-bounce-in-up/0/600')}>
826
780
  {title}
827
781
  </h1>
828
-
829
- {/* Show frame counter */}
830
- <div style={tw('absolute bottom-10 right-10 text-white text-sm')}>
831
- Frame: {frame}
832
- </div>
833
782
  </div>
834
783
  );
835
784
  }
@@ -843,572 +792,90 @@ export default function VideoIntro({ tw, title, frame, progress }) {
843
792
  }
844
793
  ```
845
794
 
846
- ## Video-Specific Props
847
-
848
- Every video template receives these additional props:
849
-
850
- ### `frame`
851
- Current frame number (0 to totalFrames - 1)
852
-
853
- ```tsx
854
- export default function MyVideo({ frame }) {
855
- // Frame 0, 1, 2, 3, ... 89 (for 3s @ 30fps)
856
- return <div>Frame: {frame}</div>;
857
- }
858
- ```
859
-
860
- ### `progress`
861
- Animation progress from 0 to 1
862
-
863
- ```tsx
864
- export default function MyVideo({ progress }) {
865
- // 0.0 at start, 0.5 at middle, 1.0 at end
866
- const x = progress * 100; // Move from 0 to 100
867
-
868
- return (
869
- <div style={{ transform: `translateX(${x}px)` }}>
870
- Moving element
871
- </div>
872
- );
873
- }
874
- ```
875
-
876
- ## Animation Patterns
795
+ ## Animation
877
796
 
878
- ### Pulse Animation
879
-
880
- ```tsx
881
- export default function PulseVideo({ tw, title }) {
882
- return (
883
- <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
884
- <h1 style={tw('text-8xl font-bold text-white animate-pulse')}>
885
- {title}
886
- </h1>
887
- </div>
888
- );
889
- }
890
- ```
891
-
892
- ### Spin Animation
893
-
894
- ```tsx
895
- export default function SpinVideo({ tw, title }) {
896
- return (
897
- <div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-purple-600 to-pink-600 gap-8')}>
898
- <div style={tw('w-32 h-32 border-8 border-white border-t-transparent rounded-full animate-spin')} />
899
- <h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
900
- </div>
901
- );
902
- }
903
- ```
797
+ loopwind provides **Tailwind-style animation classes** for creating smooth video animations without writing custom code.
904
798
 
905
- ### Bounce Animation
799
+ Use declarative animation classes with millisecond-based timing:
906
800
 
907
801
  ```tsx
908
- export default function BounceVideo({ tw, title }) {
802
+ export default function AnimatedIntro({ tw, title, subtitle }) {
909
803
  return (
910
- <div style={tw('flex items-center justify-center w-full h-full bg-blue-600')}>
911
- <h1 style={tw('text-8xl font-bold text-white animate-bounce')}>
804
+ <div style={tw('flex flex-col items-center justify-center w-full h-full bg-gradient-to-br from-blue-600 to-purple-700')}>
805
+ {/* Bounce in from below: starts at 0ms, lasts 400ms */}
806
+ <h1 style={tw('text-8xl font-bold text-white ease-out enter-bounce-in-up/0/400')}>
912
807
  {title}
913
808
  </h1>
914
- </div>
915
- );
916
- }
917
- ```
918
809
 
919
- ### Fade with Progress
920
-
921
- ```tsx
922
- export default function FadeVideo({ progress, title, tw }) {
923
- // Fade in first 20%, stay visible, fade out last 20%
924
- let opacity = 1;
925
- if (progress < 0.2) {
926
- opacity = progress / 0.2;
927
- } else if (progress > 0.8) {
928
- opacity = (1 - progress) / 0.2;
929
- }
930
-
931
- return (
932
- <h1 style={{ ...tw('text-6xl font-bold'), opacity }}>
933
- {title}
934
- </h1>
935
- );
936
- }
937
- ```
938
-
939
- ### Slide In
940
-
941
- ```tsx
942
- export default function SlideVideo({ progress, tw }) {
943
- // Slide in from left
944
- const x = -100 + (progress * 100); // -100 to 0
945
-
946
- return (
947
- <div style={{
948
- ...tw('text-4xl font-bold'),
949
- transform: `translateX(${x}%)`
950
- }}>
951
- Sliding Text
952
- </div>
953
- );
954
- }
955
- ```
956
-
957
- ### Scale Animation
958
-
959
- ```tsx
960
- export default function ScaleVideo({ progress, tw }) {
961
- // Scale from 0 to 1
962
- const scale = progress;
963
-
964
- return (
965
- <div style={{
966
- ...tw('text-6xl font-bold'),
967
- transform: `scale(${scale})`
968
- }}>
969
- Growing Text
970
- </div>
971
- );
972
- }
973
- ```
974
-
975
- ### Progress-Based Rotation
976
-
977
- ```tsx
978
- export default function RotateVideo({ progress, tw }) {
979
- // Rotate 360 degrees based on progress
980
- const rotation = progress * 360;
981
-
982
- return (
983
- <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
984
- <div style={{
985
- ...tw('w-32 h-32 bg-blue-500 rounded-lg'),
986
- transform: `rotate(${rotation}deg)`
987
- }}>
988
- </div>
989
- </div>
990
- );
991
- }
992
- ```
993
-
994
- ### Using Tailwind Animations
995
-
996
- Combine Tailwind's built-in animations with progress-based animations:
997
-
998
- ```tsx
999
- export default function PulsingLogo({ tw, progress, image, logo }) {
1000
- // Fade in based on progress
1001
- const opacity = Math.min(progress / 0.3, 1);
1002
-
1003
- return (
1004
- <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
1005
- {/* Tailwind's animate-pulse + custom opacity */}
1006
- <div style={{ ...tw('animate-pulse'), opacity }}>
1007
- <img src={image(logo)} style={tw('w-64 h-64')} />
1008
- </div>
1009
- </div>
1010
- );
1011
- }
1012
- ```
1013
-
1014
- **Available Tailwind animations:**
1015
- - `animate-spin` - Continuous rotation
1016
- - `animate-pulse` - Subtle fade in/out
1017
- - `animate-bounce` - Bouncing motion
1018
- - `animate-ping` - Ripple effect (great for badges)
1019
-
1020
- **Example with multiple animations:**
1021
-
1022
- ```tsx
1023
- export default function NotificationVideo({ tw, progress, title }) {
1024
- const slideX = -100 + (progress * 100); // Slide in from left
1025
-
1026
- return (
1027
- <div style={tw('flex items-center justify-center w-full h-full bg-gradient-to-br from-purple-600 to-blue-600')}>
1028
- <div
1029
- style={{
1030
- ...tw('flex items-center gap-4 bg-white rounded-2xl p-6 shadow-2xl'),
1031
- transform: `translateX(${slideX}%)`
1032
- }}
1033
- >
1034
- {/* Pinging indicator */}
1035
- <div style={tw('relative')}>
1036
- <div style={tw('w-3 h-3 bg-green-500 rounded-full animate-ping absolute')} />
1037
- <div style={tw('w-3 h-3 bg-green-500 rounded-full')} />
1038
- </div>
1039
-
1040
- <div>
1041
- <h2 style={tw('text-2xl font-bold text-gray-900')}>{title}</h2>
1042
- <p style={tw('text-gray-600')}>New notification</p>
1043
- </div>
1044
- </div>
1045
- </div>
1046
- );
1047
- }
1048
- ```
1049
-
1050
- ### Easing Functions
1051
-
1052
- Create smooth animations with easing:
1053
-
1054
- ```tsx
1055
- function easeOutCubic(t) {
1056
- return 1 - Math.pow(1 - t, 3);
1057
- }
1058
-
1059
- export default function EasedVideo({ progress, tw }) {
1060
- const easedProgress = easeOutCubic(progress);
1061
- const x = easedProgress * 100;
1062
-
1063
- return (
1064
- <div style={{ transform: `translateX(${x}px)` }}>
1065
- Smooth animation
810
+ {/* Fade in with upward motion: starts at 300ms, lasts 400ms */}
811
+ <p style={tw('text-2xl text-white/80 mt-4 ease-out enter-fade-in-up/300/400')}>
812
+ {subtitle}
813
+ </p>
1066
814
  </div>
1067
815
  );
1068
816
  }
1069
817
  ```
1070
818
 
1071
- ## Embedding Videos
1072
-
1073
- Use the `video()` helper to embed video backgrounds that auto-sync with your animation.
1074
-
1075
- ### Basic Usage
819
+ ### Common Animation Patterns
1076
820
 
1077
821
  ```tsx
1078
- export default function VideoOverlay({ tw, video, title, background }) {
1079
- return (
1080
- <div style={tw('relative w-full h-full')}>
1081
- {/* Background video - automatically synced to current frame */}
1082
- <img
1083
- src={video(background)}
1084
- style={tw('absolute inset-0 w-full h-full object-cover')}
1085
- />
1086
-
1087
- {/* Text overlay */}
1088
- <div style={tw('relative z-10 flex items-center justify-center w-full h-full')}>
1089
- <h1 style={tw('text-7xl font-bold text-white drop-shadow-lg')}>
1090
- {title}
1091
- </h1>
1092
- </div>
1093
- </div>
1094
- );
1095
- }
1096
- ```
1097
-
1098
- **Props:**
1099
- ```json
1100
- {
1101
- "title": "Amazing Video",
1102
- "background": "./footage/background.mp4"
1103
- }
1104
- ```
1105
-
1106
- ### How Video Sync Works
822
+ // Fade animations
823
+ <div style={tw('enter-fade-in/0/500')}>Fade in</div>
824
+ <div style={tw('enter-fade-in-up/0/600')}>Fade in with slide</div>
1107
825
 
1108
- 1. **First pass**: Template calls `video()` to register needed videos
1109
- 2. **Pre-processing**: loopwind extracts all frames at template's FPS
1110
- 3. **Rendering**: Each frame uses the corresponding video frame
1111
- 4. **Caching**: Frames are cached in memory for fast access
826
+ // Slide animations
827
+ <div style={tw('enter-slide-left/0/500')}>Slide from left</div>
828
+ <div style={tw('exit-slide-up/2500/500')}>Slide out up</div>
1112
829
 
1113
- ### Animated Overlay Example
830
+ // Bounce and scale
831
+ <div style={tw('enter-bounce-in/0/500')}>Bouncy entrance</div>
832
+ <div style={tw('enter-scale-in/0/400')}>Scale up</div>
1114
833
 
1115
- ```tsx
1116
- export default function VideoWithText({ tw, video, frame, progress, title, clip }) {
1117
- // Fade in title
1118
- const titleOpacity = progress < 0.2 ? progress / 0.2 : 1;
1119
-
1120
- return (
1121
- <div style={tw('relative w-full h-full')}>
1122
- {/* Synced video background */}
1123
- <img
1124
- src={video(clip)}
1125
- style={tw('absolute inset-0 w-full h-full object-cover')}
1126
- />
1127
-
1128
- {/* Dark overlay */}
1129
- <div style={tw('absolute inset-0 bg-black/40')} />
1130
-
1131
- {/* Animated title */}
1132
- <div style={tw('relative z-10 flex items-center justify-center w-full h-full')}>
1133
- <h1
1134
- style={{
1135
- ...tw('text-8xl font-bold text-white text-center'),
1136
- opacity: titleOpacity
1137
- }}
1138
- >
1139
- {title}
1140
- </h1>
1141
- </div>
1142
-
1143
- {/* Frame counter */}
1144
- <div style={tw('absolute bottom-4 right-4 text-white text-sm bg-black/50 px-3 py-1 rounded')}>
1145
- {frame} / {Math.floor(3 * 30)}
1146
- </div>
1147
- </div>
1148
- );
1149
- }
834
+ // Loop animations (continuous)
835
+ <div style={tw('loop-float/1000')}>Continuous floating</div>
836
+ <div style={tw('loop-pulse/800')}>Pulsing effect</div>
1150
837
  ```
1151
838
 
1152
- ## Combining Videos and Images
839
+ ### Utility-Based Animations
1153
840
 
1154
- Use both `video()` and `image()` helpers together to create rich, branded video content.
1155
-
1156
- ### Video Background with Logo
841
+ Animate any transform or opacity property directly:
1157
842
 
1158
843
  ```tsx
1159
- export default function BrandedVideo({ tw, video, image, progress, background, logo, title }) {
1160
- // Logo slides in from top
1161
- const logoY = progress < 0.3 ? -100 + (progress / 0.3) * 100 : 0;
1162
- // Title fades in after logo
1163
- const titleOpacity = progress > 0.3 ? Math.min((progress - 0.3) / 0.3, 1) : 0;
1164
-
1165
- return (
1166
- <div style={tw('relative w-full h-full')}>
1167
- {/* Video background */}
1168
- <img
1169
- src={video(background)}
1170
- style={tw('absolute inset-0 w-full h-full object-cover')}
1171
- />
1172
-
1173
- {/* Gradient overlay */}
1174
- <div style={tw('absolute inset-0 bg-gradient-to-t from-black/80 to-transparent')} />
1175
-
1176
- {/* Animated logo */}
1177
- <div
1178
- style={{
1179
- ...tw('absolute top-12 left-12'),
1180
- transform: `translateY(${logoY}px)`
1181
- }}
1182
- >
1183
- <img
1184
- src={image(logo)}
1185
- style={tw('h-16 w-auto')}
1186
- />
1187
- </div>
1188
-
1189
- {/* Animated title */}
1190
- <div style={tw('absolute bottom-20 left-12 right-12')}>
1191
- <h1
1192
- style={{
1193
- ...tw('text-7xl font-bold text-white'),
1194
- opacity: titleOpacity
1195
- }}
1196
- >
1197
- {title}
1198
- </h1>
1199
- </div>
1200
- </div>
1201
- );
1202
- }
1203
- ```
844
+ // Translate, rotate, scale
845
+ <div style={tw('enter-translate-x-5/0/500')}>Slide 20px from left</div>
846
+ <div style={tw('enter-rotate-90/0/800')}>Rotate 90 degrees</div>
847
+ <div style={tw('enter-scale-100/0/500')}>Scale from 0 to 100%</div>
1204
848
 
1205
- **Props:**
1206
- ```json
1207
- {
1208
- "title": "Introducing Our New Product",
1209
- "background": "./footage/product-demo.mp4",
1210
- "logo": "./images/company-logo.png"
1211
- }
849
+ // Combine multiple animations
850
+ <div style={tw('enter-translate-y-10/0/500 enter-rotate-45/0/500')}>
851
+ Fly and spin
852
+ </div>
1212
853
  ```
1213
854
 
1214
- ### Multi-Element Video Composition
1215
-
1216
- ```tsx
1217
- export default function ProductShowcase({
1218
- tw,
1219
- video,
1220
- image,
1221
- progress,
1222
- backgroundVideo,
1223
- productImage,
1224
- badge,
1225
- title,
1226
- price
1227
- }) {
1228
- // Product image zooms in
1229
- const scale = 0.8 + (Math.min(progress / 0.5, 1) * 0.2); // 0.8 to 1.0
1230
-
1231
- // Badge rotates in
1232
- const badgeRotation = progress < 0.4 ? (progress / 0.4) * 360 : 360;
1233
- const badgeOpacity = Math.min(progress / 0.4, 1);
1234
-
1235
- // Price slides up
1236
- const priceY = progress > 0.6 ? (1 - Math.min((progress - 0.6) / 0.4, 1)) * 50 : 50;
1237
-
1238
- return (
1239
- <div style={tw('relative w-full h-full bg-white')}>
1240
- {/* Video background (blurred) */}
1241
- <div style={tw('absolute inset-0 overflow-hidden')}>
1242
- <img
1243
- src={video(backgroundVideo)}
1244
- style={{
1245
- ...tw('w-full h-full object-cover'),
1246
- filter: 'blur(20px)',
1247
- transform: 'scale(1.1)' // Prevent blur edge artifacts
1248
- }}
1249
- />
1250
- <div style={tw('absolute inset-0 bg-white/50')} />
1251
- </div>
1252
-
1253
- {/* Main product image */}
1254
- <div style={tw('relative z-10 flex items-center justify-center w-full h-full p-20')}>
1255
- <div style={{ transform: `scale(${scale})` }}>
1256
- <img
1257
- src={image(productImage)}
1258
- style={tw('w-96 h-96 object-contain drop-shadow-2xl')}
1259
- />
1260
- </div>
1261
- </div>
1262
-
1263
- {/* Animated badge */}
1264
- <div
1265
- style={{
1266
- ...tw('absolute top-20 right-20'),
1267
- opacity: badgeOpacity,
1268
- transform: `rotate(${badgeRotation}deg)`
1269
- }}
1270
- >
1271
- <img
1272
- src={image(badge)}
1273
- style={tw('w-32 h-32')}
1274
- />
1275
- </div>
1276
-
1277
- {/* Title */}
1278
- <div style={tw('absolute top-20 left-20')}>
1279
- <h2 style={tw('text-5xl font-bold text-gray-900')}>
1280
- {title}
1281
- </h2>
1282
- </div>
1283
-
1284
- {/* Animated price */}
1285
- <div
1286
- style={{
1287
- ...tw('absolute bottom-20 left-20'),
1288
- transform: `translateY(${priceY}px)`
1289
- }}
1290
- >
1291
- <div style={tw('text-6xl font-black text-blue-600')}>
1292
- ${price}
1293
- </div>
1294
- </div>
1295
- </div>
1296
- );
1297
- }
1298
- ```
855
+ **See the full [Animation documentation](/animation)** for all animation classes, easing functions, staggered animations, and advanced patterns.
1299
856
 
1300
- **Props:**
1301
- ```json
1302
- {
1303
- "title": "Premium Headphones",
1304
- "price": "299",
1305
- "backgroundVideo": "./footage/studio-background.mp4",
1306
- "productImage": "./images/headphones.png",
1307
- "badge": "./images/new-badge.png"
1308
- }
1309
- ```
857
+ ### Programmatic Animations
1310
858
 
1311
- ### Video with Multiple Image Overlays
859
+ For complex custom animations, use the `progress` prop directly:
1312
860
 
1313
861
  ```tsx
1314
- export default function TutorialVideo({
1315
- tw,
1316
- video,
1317
- image,
1318
- progress,
1319
- frame,
1320
- screencast,
1321
- avatar,
1322
- logo,
1323
- instructor,
1324
- lesson
1325
- }) {
1326
- // Avatar pulses
1327
- const avatarScale = 1 + Math.sin(frame * 0.2) * 0.05;
1328
-
1329
- // Logo stays visible
1330
- const logoOpacity = 1;
862
+ export default function CustomAnimation({ progress, title, tw }) {
863
+ const opacity = progress < 0.3 ? progress / 0.3 : 1;
864
+ const scale = 0.8 + (progress * 0.2);
1331
865
 
1332
866
  return (
1333
- <div style={tw('relative w-full h-full bg-gray-900')}>
1334
- {/* Main screencast video */}
1335
- <img
1336
- src={video(screencast)}
1337
- style={tw('absolute inset-0 w-full h-full object-contain')}
1338
- />
1339
-
1340
- {/* Company logo (top left) */}
1341
- <div style={{
1342
- ...tw('absolute top-8 left-8'),
1343
- opacity: logoOpacity
1344
- }}>
1345
- <img
1346
- src={image(logo)}
1347
- style={tw('h-12 w-auto')}
1348
- />
1349
- </div>
1350
-
1351
- {/* Instructor avatar (bottom right) */}
1352
- <div style={tw('absolute bottom-8 right-8')}>
1353
- <div
1354
- style={{
1355
- ...tw('relative'),
1356
- transform: `scale(${avatarScale})`
1357
- }}
1358
- >
1359
- <img
1360
- src={image(avatar)}
1361
- style={tw('w-24 h-24 rounded-full border-4 border-blue-500')}
1362
- />
1363
- {/* Red recording dot */}
1364
- <div style={tw('absolute -top-1 -right-1 w-6 h-6 bg-red-500 rounded-full animate-pulse')} />
1365
- </div>
1366
- </div>
1367
-
1368
- {/* Lesson info overlay */}
1369
- <div style={tw('absolute bottom-8 left-8 bg-black/80 backdrop-blur px-6 py-4 rounded-lg')}>
1370
- <p style={tw('text-sm text-gray-400')}>Instructor</p>
1371
- <p style={tw('text-xl font-bold text-white mb-2')}>{instructor}</p>
1372
- <p style={tw('text-sm text-blue-400')}>{lesson}</p>
1373
- </div>
1374
- </div>
867
+ <h1 style={{
868
+ ...tw('text-8xl font-bold text-white'),
869
+ opacity,
870
+ transform: `scale(${scale})`
871
+ }}>
872
+ {title}
873
+ </h1>
1375
874
  );
1376
875
  }
1377
876
  ```
1378
877
 
1379
- **Props:**
1380
- ```json
1381
- {
1382
- "instructor": "Sarah Johnson",
1383
- "lesson": "Lesson 3: Advanced Techniques",
1384
- "screencast": "./footage/screen-recording.mp4",
1385
- "avatar": "./images/instructor.jpg",
1386
- "logo": "./images/course-logo.png"
1387
- }
1388
- ```
1389
-
1390
- ### Supported Formats
1391
-
1392
- **Video formats** (via `video()` helper):
1393
- - ✅ MP4 (`.mp4`)
1394
- - ✅ MOV (`.mov`)
1395
- - ✅ WebM (`.webm`)
1396
- - ✅ AVI (`.avi`)
1397
-
1398
- **Image formats** (via `image()` helper):
1399
- - ✅ JPEG (`.jpg`, `.jpeg`)
1400
- - ✅ PNG (`.png`) - with transparency
1401
- - ✅ GIF (`.gif`)
1402
- - ✅ WebP (`.webp`)
1403
- - ✅ SVG (`.svg`)
1404
-
1405
- ### Best Practices
1406
-
1407
- 1. **Pre-optimize assets**: Resize images and videos to match your output resolution
1408
- 2. **Use PNG for logos**: Transparency works perfectly with video backgrounds
1409
- 3. **Match frame rates**: If possible, use videos with the same FPS as your template
1410
- 4. **Consider file sizes**: Large videos take longer to process
1411
- 5. **Test opacity**: Ensure overlays are visible against video backgrounds
878
+ Most use cases are better served by animation classes, but `progress` and `frame` props give you complete control when needed.
1412
879
 
1413
880
  ## FPS and Duration
1414
881
 
@@ -1509,24 +976,16 @@ For long videos, consider rendering in segments.
1509
976
  ### Loading Spinner
1510
977
 
1511
978
  ```tsx
1512
- export default function LoadingVideo({ tw, progress, title }) {
1513
- // Show spinner for first 60%, then fade to title
1514
- const spinnerOpacity = progress < 0.6 ? 1 : (1 - (progress - 0.6) / 0.4);
1515
- const titleOpacity = progress > 0.6 ? (progress - 0.6) / 0.4 : 0;
1516
-
979
+ export default function LoadingVideo({ tw, title }) {
1517
980
  return (
1518
981
  <div style={tw('flex flex-col items-center justify-center w-full h-full bg-gray-900')}>
1519
- {/* Tailwind's animate-spin */}
1520
- <div style={{ opacity: spinnerOpacity }}>
1521
- <div style={tw('w-32 h-32 border-8 border-blue-500 border-t-transparent rounded-full animate-spin')} />
982
+ {/* Spinner fades out at 60% (1800ms for 3s video) */}
983
+ <div style={tw('exit-fade-out/1800/400')}>
984
+ <div style={tw('w-32 h-32 border-8 border-blue-500 border-t-transparent rounded-full loop-spin/1000')} />
1522
985
  </div>
1523
986
 
1524
- <h1
1525
- style={{
1526
- ...tw('text-6xl font-bold text-white mt-12'),
1527
- opacity: titleOpacity
1528
- }}
1529
- >
987
+ {/* Title fades in after spinner */}
988
+ <h1 style={tw('text-6xl font-bold text-white mt-12 ease-out enter-fade-in-up/1800/400')}>
1530
989
  {title}
1531
990
  </h1>
1532
991
  </div>
@@ -1534,27 +993,24 @@ export default function LoadingVideo({ tw, progress, title }) {
1534
993
  }
1535
994
  ```
1536
995
 
1537
- ### Progress Bar with Pulse
996
+ ### Progress Bar
1538
997
 
1539
998
  ```tsx
1540
- export default function ProgressVideo({ tw, progress, title, subtitle }) {
999
+ export default function ProgressVideo({ tw, title, subtitle }) {
1541
1000
  return (
1542
1001
  <div style={tw('flex flex-col items-center justify-center w-full h-full bg-gray-900 p-12')}>
1543
- <h2 style={tw('text-5xl font-bold text-white mb-12 animate-pulse')}>
1002
+ {/* Title bounces in */}
1003
+ <h2 style={tw('text-5xl font-bold text-white mb-12 ease-out enter-bounce-in/0/500')}>
1544
1004
  {title}
1545
1005
  </h2>
1546
1006
 
1547
- {/* Progress bar */}
1548
- <div style={tw('w-full max-w-2xl h-4 bg-gray-700 rounded-full overflow-hidden')}>
1549
- <div
1550
- style={{
1551
- ...tw('h-full bg-gradient-to-r from-blue-500 to-green-500'),
1552
- width: `${progress * 100}%`
1553
- }}
1554
- />
1007
+ {/* Progress bar container fades in */}
1008
+ <div style={tw('w-full max-w-2xl h-4 bg-gray-700 rounded-full overflow-hidden ease-out enter-fade-in/300/400')}>
1009
+ <div style={tw('h-full bg-gradient-to-r from-blue-500 to-green-500 w-full')} />
1555
1010
  </div>
1556
1011
 
1557
- <p style={tw('text-2xl text-gray-400 mt-8')}>
1012
+ {/* Subtitle fades in last */}
1013
+ <p style={tw('text-2xl text-gray-400 mt-8 ease-out enter-fade-in/600/400')}>
1558
1014
  {subtitle}
1559
1015
  </p>
1560
1016
  </div>
@@ -1562,7 +1018,7 @@ export default function ProgressVideo({ tw, progress, title, subtitle }) {
1562
1018
  }
1563
1019
  ```
1564
1020
 
1565
- ### Countdown Timer with Pulse
1021
+ ### Countdown Timer
1566
1022
 
1567
1023
  ```tsx
1568
1024
  export default function CountdownVideo({ tw, frame, message }) {
@@ -1571,11 +1027,13 @@ export default function CountdownVideo({ tw, frame, message }) {
1571
1027
 
1572
1028
  return (
1573
1029
  <div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
1574
- {/* Add pulse animation when counting down */}
1575
- <div style={tw('text-[200px] font-black text-white animate-pulse')}>
1030
+ {/* Number scales in and pulses continuously */}
1031
+ <div style={tw('text-[200px] font-black text-white ease-out enter-scale-in/0/400 loop-pulse/1000')}>
1576
1032
  {secondsRemaining}
1577
1033
  </div>
1578
- <p style={tw('text-3xl text-gray-400 mt-8')}>
1034
+
1035
+ {/* Message fades in */}
1036
+ <p style={tw('text-3xl text-gray-400 mt-8 ease-out enter-fade-in/400/500')}>
1579
1037
  {message}
1580
1038
  </p>
1581
1039
  </div>
@@ -1624,20 +1082,51 @@ loopwind render video-intro '{"title":"Welcome"}' --out intro.gif
1624
1082
  - Larger file sizes than MP4
1625
1083
  - Some color banding with gradients
1626
1084
 
1627
- **When to use GIF:**
1628
- - Short loops (< 5 seconds)
1629
- - Simple animations with solid colors
1630
- - Platforms that don't support video (email, GitHub)
1085
+ **When to use GIF:**
1086
+ - Short loops (< 5 seconds)
1087
+ - Simple animations with solid colors
1088
+ - Platforms that don't support video (email, GitHub)
1089
+
1090
+ **When to use MP4:**
1091
+ - Longer videos
1092
+ - Complex gradients or photos
1093
+ - Best quality and smallest size
1094
+
1095
+ ## Video-Specific Props
1096
+
1097
+ Every video template receives these additional props:
1098
+
1099
+ ### `frame`
1100
+ Current frame number (0 to totalFrames - 1)
1101
+
1102
+ ```tsx
1103
+ export default function MyVideo({ frame }) {
1104
+ // Frame 0, 1, 2, 3, ... 89 (for 3s @ 30fps)
1105
+ return <div>Frame: {frame}</div>;
1106
+ }
1107
+ ```
1108
+
1109
+ ### `progress`
1110
+ Animation progress from 0 to 1
1631
1111
 
1632
- **When to use MP4:**
1633
- - Longer videos
1634
- - Complex gradients or photos
1635
- - Best quality and smallest size
1112
+ ```tsx
1113
+ export default function MyVideo({ progress }) {
1114
+ // 0.0 at start, 0.5 at middle, 1.0 at end
1115
+ const x = progress * 100; // Move from 0 to 100
1116
+
1117
+ return (
1118
+ <div style={{ transform: `translateX(${x}px)` }}>
1119
+ Moving element
1120
+ </div>
1121
+ );
1122
+ }
1123
+ ```
1636
1124
 
1637
1125
  ## Next Steps
1638
1126
 
1639
- - [Learn about Image Rendering](/images)
1640
- - [QR Codes and Template Composition](/helpers)
1127
+ - [Animation - Complete animation guide](/animation)
1128
+ - [Image Rendering](/images)
1129
+ - [Helpers - QR Codes and Template Composition](/helpers)
1641
1130
  - [Styling with Tailwind & shadcn/ui](/styling)
1642
1131
  - [Custom Fonts in Videos](/fonts)
1643
1132
 
@@ -1651,9 +1140,9 @@ FILE: animation.mdx
1651
1140
 
1652
1141
  # Animation
1653
1142
 
1654
- loopwind provides **Tailwind-style animation classes** that work with `progress` and `frame` to create smooth video animations without writing custom code.
1143
+ loopwind provides **Tailwind-style animation classes** that work with time to create smooth video animations without writing custom code.
1655
1144
 
1656
- > **Note:** Animation classes only work with **video templates** and **GIFs**. For static images, animations will have no effect since there's no `progress` or `frame` context.
1145
+ > **Note:** Animation classes only work with **video templates** and **GIFs**. For static images, animations will have no effect since there's no time context.
1657
1146
 
1658
1147
  ## Quick Start
1659
1148
 
@@ -1661,71 +1150,248 @@ loopwind provides **Tailwind-style animation classes** that work with `progress`
1661
1150
  export default function MyVideo({ tw, title, subtitle }) {
1662
1151
  return (
1663
1152
  <div style={tw('flex flex-col items-center justify-center w-full h-full bg-black')}>
1664
- {/* Bounce in from below during first 40% */}
1665
- <h1 style={tw('text-8xl font-bold text-white ease-out animate-bounce-in-up/0/0.4')}>
1153
+ {/* Bounce in from below: starts at 0, lasts 400ms */}
1154
+ <h1 style={tw('text-8xl font-bold text-white ease-out enter-bounce-in-up/0/400')}>
1666
1155
  {title}
1667
1156
  </h1>
1668
1157
 
1669
- {/* Fade in with upward motion from 30% to 70% */}
1670
- <p style={tw('text-2xl text-white/80 mt-4 ease-out animate-fade-in-up/0.3/0.7')}>
1158
+ {/* Fade in with upward motion: starts at 300ms, lasts 400ms */}
1159
+ <p style={tw('text-2xl text-white/80 mt-4 ease-out enter-fade-in-up/300/400')}>
1671
1160
  {subtitle}
1672
1161
  </p>
1162
+
1163
+ {/* Continuous floating animation: repeats every 1s (1000ms) */}
1164
+ <div style={tw('mt-8 text-4xl loop-float/1000')}>
1165
+ ⬇️
1166
+ </div>
1673
1167
  </div>
1674
1168
  );
1675
1169
  }
1676
1170
  ```
1677
1171
 
1678
- ## Transition Animations
1172
+ ## Animation Format
1173
+
1174
+ loopwind uses three types of animations with **millisecond timing**:
1175
+
1176
+ | Type | Format | Description |
1177
+ |------|--------|-------------|
1178
+ | Enter | `enter-{type}/{start}/{duration}` | Animations that play when entering |
1179
+ | Exit | `exit-{type}/{start}/{duration}` | Animations that play when exiting |
1180
+ | Loop | `loop-{type}/{duration}` | Continuous looping animations |
1181
+
1182
+ All timing values are in **milliseconds** (1000ms = 1 second).
1183
+
1184
+ ## Utility-Based Animations
1185
+
1186
+ In addition to predefined animations, loopwind supports **Tailwind utility-based animations** that let you animate any transform or opacity property directly:
1187
+
1188
+ ```tsx
1189
+ // Slide in 20px from the left
1190
+ <div style={tw('enter-translate-x-5/0/1000')}>Content</div>
1191
+
1192
+ // Rotate 90 degrees on entrance
1193
+ <div style={tw('enter-rotate-90/0/500')}>Spinning</div>
1194
+
1195
+ // Fade to 50% opacity in a loop
1196
+ <div style={tw('loop-opacity-50/1000')}>Pulsing</div>
1197
+
1198
+ // Scale down with negative value
1199
+ <div style={tw('enter--scale-50/0/800')}>Shrinking</div>
1200
+ ```
1201
+
1202
+ ### Supported Utilities
1203
+
1204
+ | Utility | Format | Description | Example |
1205
+ |---------|--------|-------------|---------|
1206
+ | **translate-x** | `enter-translate-x-{value}` | Translate horizontally | `enter-translate-x-5` = 20px<br/>`enter-translate-x-full` = 100%<br/>`enter-translate-x-[20px]` = 20px |
1207
+ | **translate-y** | `enter-translate-y-{value}` | Translate vertically | `loop-translate-y-10` = 40px<br/>`enter-translate-y-1/2` = 50%<br/>`enter-translate-y-[5rem]` = 80px |
1208
+ | **opacity** | `enter-opacity-{n}` | Set opacity (0-100) | `enter-opacity-50` = 50% |
1209
+ | **scale** | `enter-scale-{n}` | Scale element (0-200) | `enter-scale-100` = 1.0x |
1210
+ | **rotate** | `enter-rotate-{n}` | Rotate in degrees | `enter-rotate-45` = 45° |
1211
+ | **skew-x** | `enter-skew-x-{n}` | Skew on X axis in degrees | `enter-skew-x-12` = 12° |
1212
+ | **skew-y** | `enter-skew-y-{n}` | Skew on Y axis in degrees | `exit-skew-y-6` = 6° |
1213
+
1214
+ **Translate value formats:**
1215
+ - **Numeric**: `5` = 20px (Tailwind spacing scale: 1 unit = 4px)
1216
+ - **Keywords**: `full` = 100%
1217
+ - **Fractions**: `1/2` = 50%, `1/3` = 33.333%, `2/3` = 66.666%, etc.
1218
+ - **Arbitrary values**: `[20px]`, `[5rem]`, `[10%]` (rem converts to px: 1rem = 16px)
1219
+
1220
+ All utilities work with:
1221
+ - **All prefixes**: `enter-`, `exit-`, `loop-`, `animate-`
1222
+ - **Negative values**: Prefix with `-` (e.g., `-translate-x-5`, `-rotate-45`)
1223
+ - **Timing syntax**: Add `/start/duration` (e.g., `enter-translate-x-5/0/800`)
1224
+
1225
+ ### Translate Animations
1226
+
1227
+ ```tsx
1228
+ // Numeric (Tailwind spacing): 20px (5 * 4px)
1229
+ <div style={tw('enter-translate-x-5/0/500')}>Content</div>
1230
+
1231
+ // Keyword: Full width (100%)
1232
+ <div style={tw('enter-translate-y-full/0/800')}>Dropping full height</div>
1233
+
1234
+ // Fraction: Half width (50%)
1235
+ <div style={tw('enter-translate-x-1/2/0/600')}>Slide in halfway</div>
1236
+
1237
+ // Arbitrary values: Exact px or rem
1238
+ <div style={tw('enter-translate-y-[20px]/0/500')}>Slide 20px</div>
1239
+ <div style={tw('enter-translate-x-[5rem]/0/800')}>Slide 5rem (80px)</div>
1240
+
1241
+ // Loop with fractions
1242
+ <div style={tw('loop-translate-y-1/4/1000')}>Oscillate 25%</div>
1243
+
1244
+ // Negative values
1245
+ <div style={tw('exit--translate-y-8/2000/500')}>Rising</div>
1246
+ ```
1247
+
1248
+ ### Opacity Animations
1249
+
1250
+ ```tsx
1251
+ // Fade to 100% opacity
1252
+ <div style={tw('enter-opacity-100/0/500')}>Fading In</div>
1253
+
1254
+ // Fade to 50% opacity
1255
+ <div style={tw('enter-opacity-50/0/800')}>Half Opacity</div>
1256
+
1257
+ // Pulse between 50% and 100%
1258
+ <div style={tw('loop-opacity-50/1000')}>Pulsing</div>
1259
+
1260
+ // Fade out to 0%
1261
+ <div style={tw('exit-opacity-0/2500/500')}>Vanishing</div>
1262
+ ```
1263
+
1264
+ ### Scale Animations
1265
+
1266
+ ```tsx
1267
+ // Scale from 0 to 100% (1.0x)
1268
+ <div style={tw('enter-scale-100/0/500')}>Growing</div>
1269
+
1270
+ // Scale to 150% (1.5x)
1271
+ <div style={tw('enter-scale-150/0/800')}>Enlarging</div>
1272
+
1273
+ // Pulse scale in a loop
1274
+ <div style={tw('loop-scale-110/1000')}>Breathing</div>
1275
+
1276
+ // Scale down to 50%
1277
+ <div style={tw('exit-scale-50/2000/500')}>Shrinking</div>
1278
+ ```
1279
+
1280
+ ### Rotate Animations
1281
+
1282
+ ```tsx
1283
+ // Rotate 90 degrees
1284
+ <div style={tw('enter-rotate-90/0/500')}>Quarter Turn</div>
1285
+
1286
+ // Rotate 180 degrees
1287
+ <div style={tw('enter-rotate-180/0/1000')}>Half Turn</div>
1288
+
1289
+ // Continuous rotation in loop (360 degrees per cycle)
1290
+ <div style={tw('loop-rotate-360/2000')}>Spinning</div>
1291
+
1292
+ // Rotate backwards with negative value
1293
+ <div style={tw('enter--rotate-45/0/500')}>Counter Rotation</div>
1294
+ ```
1295
+
1296
+ ### Skew Animations
1297
+
1298
+ ```tsx
1299
+ // Skew on X axis
1300
+ <div style={tw('enter-skew-x-12/0/500')}>Slanted</div>
1301
+
1302
+ // Skew on Y axis
1303
+ <div style={tw('enter-skew-y-6/0/800')}>Tilted</div>
1304
+
1305
+ // Oscillating skew in loop
1306
+ <div style={tw('loop-skew-x-6/1000')}>Wobbling</div>
1307
+
1308
+ // Negative skew
1309
+ <div style={tw('exit--skew-x-12/2000/500')}>Reverse Slant</div>
1310
+ ```
1311
+
1312
+ ### Combining Utilities
1313
+
1314
+ You can combine multiple utility animations on the same element:
1315
+
1316
+ ```tsx
1317
+ // Translate and rotate together
1318
+ <div style={tw('enter-translate-y-10/0/500 enter-rotate-45/0/500')}>
1319
+ Flying In
1320
+ </div>
1321
+
1322
+ // Fade and scale
1323
+ <div style={tw('enter-opacity-100/0/800 enter-scale-100/0/800')}>
1324
+ Appearing
1325
+ </div>
1326
+
1327
+ // Enter with translate, exit with rotation
1328
+ <div style={tw('enter-translate-x-5/0/500 exit-rotate-180/2500/500')}>
1329
+ Slide and Spin
1330
+ </div>
1331
+ ```
1332
+
1333
+ ### Bracket Notation
1334
+
1335
+ For more CSS-like syntax, you can use brackets with units:
1336
+
1337
+ ```tsx
1338
+ // Using bracket notation with seconds
1339
+ <h1 style={tw('enter-slide-up/[0.6s]/[1.5s]')}>Hello</h1>
1340
+
1341
+ // Using bracket notation with milliseconds
1342
+ <h1 style={tw('enter-fade-in/[300ms]/[800ms]')}>World</h1>
1343
+
1344
+ // Mix and match - plain numbers are milliseconds
1345
+ <h1 style={tw('enter-bounce-in/0/[1.2s]')}>Mixed</h1>
1346
+ ```
1347
+
1348
+ ## Enter Animations
1349
+
1350
+ Format: `enter-{type}/{startMs}/{durationMs}`
1679
1351
 
1680
- Format: `animate-{type}/{start}/{end}`
1352
+ - `startMs` - when the animation begins (milliseconds from start)
1353
+ - `durationMs` - how long the animation lasts
1681
1354
 
1682
- - `{type}` - The animation type (fade-in, bounce-in-up, etc.)
1683
- - `{start}` - Progress value to start (0-1)
1684
- - `{end}` - Progress value to end (0-1)
1355
+ When values are omitted (`enter-fade-in`), it uses the full video duration.
1685
1356
 
1686
1357
  ### Fade Animations
1687
1358
 
1688
1359
  Simple opacity transitions with optional direction.
1689
1360
 
1690
1361
  ```tsx
1691
- // Fade in from 0% to 50% of the video
1692
- <h1 style={tw('animate-fade-in/0/0.5')}>Hello</h1>
1362
+ // Fade in from 0ms to 500ms
1363
+ <h1 style={tw('enter-fade-in/0/500')}>Hello</h1>
1693
1364
 
1694
- // Fade out from 50% to 100%
1695
- <h1 style={tw('animate-fade-out/0.5/1')}>Goodbye</h1>
1365
+ // Fade in with upward motion
1366
+ <h1 style={tw('enter-fade-in-up/0/600')}>Hello</h1>
1696
1367
  ```
1697
1368
 
1698
1369
  | Class | Description |
1699
1370
  |-------|-------------|
1700
- | `animate-fade-in/0/1` | Fade in (opacity 0 → 1) |
1701
- | `animate-fade-out/0/1` | Fade out (opacity 1 0) |
1702
- | `animate-fade-in-up/0/1` | Fade in + slide up (30px) |
1703
- | `animate-fade-in-down/0/1` | Fade in + slide down (30px) |
1704
- | `animate-fade-in-left/0/1` | Fade in + slide from left (30px) |
1705
- | `animate-fade-in-right/0/1` | Fade in + slide from right (30px) |
1706
- | `animate-fade-out-up/0/1` | Fade out + slide up |
1707
- | `animate-fade-out-down/0/1` | Fade out + slide down |
1708
- | `animate-fade-out-left/0/1` | Fade out + slide left |
1709
- | `animate-fade-out-right/0/1` | Fade out + slide right |
1371
+ | `enter-fade-in/0/500` | Fade in (opacity 0 → 1) |
1372
+ | `enter-fade-in-up/0/500` | Fade in + slide up (30px) |
1373
+ | `enter-fade-in-down/0/500` | Fade in + slide down (30px) |
1374
+ | `enter-fade-in-left/0/500` | Fade in + slide from left (30px) |
1375
+ | `enter-fade-in-right/0/500` | Fade in + slide from right (30px) |
1710
1376
 
1711
1377
  ### Slide Animations
1712
1378
 
1713
1379
  Larger movement (100px) with fade.
1714
1380
 
1715
1381
  ```tsx
1716
- // Slide in from left
1717
- <div style={tw('animate-slide-left/0/0.5')}>Content</div>
1382
+ // Slide in from left: starts at 0, lasts 500ms
1383
+ <div style={tw('enter-slide-left/0/500')}>Content</div>
1718
1384
 
1719
- // Slide up from bottom
1720
- <div style={tw('animate-slide-up/0.2/0.8')}>Content</div>
1385
+ // Slide up from bottom: starts at 200ms, lasts 600ms
1386
+ <div style={tw('enter-slide-up/200/600')}>Content</div>
1721
1387
  ```
1722
1388
 
1723
1389
  | Class | Description |
1724
1390
  |-------|-------------|
1725
- | `animate-slide-left/0/1` | Slide in from left (100px) |
1726
- | `animate-slide-right/0/1` | Slide in from right (100px) |
1727
- | `animate-slide-up/0/1` | Slide in from bottom (100px) |
1728
- | `animate-slide-down/0/1` | Slide in from top (100px) |
1391
+ | `enter-slide-left/0/500` | Slide in from left (100px) |
1392
+ | `enter-slide-right/0/500` | Slide in from right (100px) |
1393
+ | `enter-slide-up/0/500` | Slide in from bottom (100px) |
1394
+ | `enter-slide-down/0/500` | Slide in from top (100px) |
1729
1395
 
1730
1396
  ### Bounce Animations
1731
1397
 
@@ -1733,19 +1399,19 @@ Playful entrance with overshoot effect.
1733
1399
 
1734
1400
  ```tsx
1735
1401
  // Bounce in with scale overshoot
1736
- <h1 style={tw('animate-bounce-in/0/0.5')}>Bouncy!</h1>
1402
+ <h1 style={tw('enter-bounce-in/0/500')}>Bouncy!</h1>
1737
1403
 
1738
1404
  // Bounce in from below
1739
- <div style={tw('animate-bounce-in-up/0/0.6')}>Pop!</div>
1405
+ <div style={tw('enter-bounce-in-up/0/600')}>Pop!</div>
1740
1406
  ```
1741
1407
 
1742
1408
  | Class | Description |
1743
1409
  |-------|-------------|
1744
- | `animate-bounce-in/0/1` | Bounce in with scale overshoot |
1745
- | `animate-bounce-in-up/0/1` | Bounce in from below |
1746
- | `animate-bounce-in-down/0/1` | Bounce in from above |
1747
- | `animate-bounce-in-left/0/1` | Bounce in from left |
1748
- | `animate-bounce-in-right/0/1` | Bounce in from right |
1410
+ | `enter-bounce-in/0/500` | Bounce in with scale overshoot |
1411
+ | `enter-bounce-in-up/0/500` | Bounce in from below |
1412
+ | `enter-bounce-in-down/0/500` | Bounce in from above |
1413
+ | `enter-bounce-in-left/0/500` | Bounce in from left |
1414
+ | `enter-bounce-in-right/0/500` | Bounce in from right |
1749
1415
 
1750
1416
  ### Scale & Zoom Animations
1751
1417
 
@@ -1753,18 +1419,16 @@ Size-based transitions.
1753
1419
 
1754
1420
  ```tsx
1755
1421
  // Scale in from 50%
1756
- <div style={tw('animate-scale-in/0/0.5')}>Growing</div>
1422
+ <div style={tw('enter-scale-in/0/500')}>Growing</div>
1757
1423
 
1758
1424
  // Zoom in from 0%
1759
- <div style={tw('animate-zoom-in/0/1')}>Zooming</div>
1425
+ <div style={tw('enter-zoom-in/0/1000')}>Zooming</div>
1760
1426
  ```
1761
1427
 
1762
1428
  | Class | Description |
1763
1429
  |-------|-------------|
1764
- | `animate-scale-in/0/1` | Scale up from 50% to 100% |
1765
- | `animate-scale-out/0/1` | Scale up to 150% + fade out |
1766
- | `animate-zoom-in/0/1` | Zoom in from 0% to 100% |
1767
- | `animate-zoom-out/0/1` | Zoom out from 100% to 200% + fade |
1430
+ | `enter-scale-in/0/500` | Scale up from 50% to 100% |
1431
+ | `enter-zoom-in/0/500` | Zoom in from 0% to 100% |
1768
1432
 
1769
1433
  ### Rotate & Flip Animations
1770
1434
 
@@ -1772,47 +1436,89 @@ Rotation-based transitions.
1772
1436
 
1773
1437
  ```tsx
1774
1438
  // Rotate in 180 degrees
1775
- <div style={tw('animate-rotate-in/0/0.5')}>Spinning</div>
1439
+ <div style={tw('enter-rotate-in/0/500')}>Spinning</div>
1776
1440
 
1777
1441
  // 3D flip on X axis
1778
- <div style={tw('animate-flip-in-x/0/0.5')}>Flipping</div>
1442
+ <div style={tw('enter-flip-in-x/0/500')}>Flipping</div>
1443
+ ```
1444
+
1445
+ | Class | Description |
1446
+ |-------|-------------|
1447
+ | `enter-rotate-in/0/500` | Rotate in from -180° |
1448
+ | `enter-flip-in-x/0/500` | 3D flip on horizontal axis |
1449
+ | `enter-flip-in-y/0/500` | 3D flip on vertical axis |
1450
+
1451
+ ## Exit Animations
1452
+
1453
+ Format: `exit-{type}/{startMs}/{durationMs}`
1454
+
1455
+ - `startMs` - when the exit animation begins
1456
+ - `durationMs` - how long the exit animation lasts
1457
+
1458
+ Exit animations use the same timing system but animate elements out.
1459
+
1460
+ ```tsx
1461
+ // Fade out starting at 2500ms, lasting 500ms (ends at 3000ms)
1462
+ <h1 style={tw('exit-fade-out/2500/500')}>Goodbye</h1>
1463
+
1464
+ // Combined enter and exit on same element
1465
+ <h1 style={tw('enter-fade-in/0/500 exit-fade-out/2500/500')}>
1466
+ Hello and Goodbye
1467
+ </h1>
1779
1468
  ```
1780
1469
 
1781
1470
  | Class | Description |
1782
1471
  |-------|-------------|
1783
- | `animate-rotate-in/0/1` | Rotate in from -180° |
1784
- | `animate-rotate-out/0/1` | Rotate out to 180° |
1785
- | `animate-flip-in-x/0/1` | 3D flip on horizontal axis |
1786
- | `animate-flip-in-y/0/1` | 3D flip on vertical axis |
1472
+ | `exit-fade-out/2500/500` | Fade out (opacity 1 → 0) |
1473
+ | `exit-fade-out-up/2500/500` | Fade out + slide up |
1474
+ | `exit-fade-out-down/2500/500` | Fade out + slide down |
1475
+ | `exit-fade-out-left/2500/500` | Fade out + slide left |
1476
+ | `exit-fade-out-right/2500/500` | Fade out + slide right |
1477
+ | `exit-slide-up/2500/500` | Slide out upward (100px) |
1478
+ | `exit-slide-down/2500/500` | Slide out downward (100px) |
1479
+ | `exit-slide-left/2500/500` | Slide out to left (100px) |
1480
+ | `exit-slide-right/2500/500` | Slide out to right (100px) |
1481
+ | `exit-scale-out/2500/500` | Scale out to 150% |
1482
+ | `exit-zoom-out/2500/500` | Zoom out to 200% |
1483
+ | `exit-rotate-out/2500/500` | Rotate out to 180° |
1484
+ | `exit-bounce-out/2500/500` | Bounce out with scale |
1485
+ | `exit-bounce-out-up/2500/500` | Bounce out upward |
1486
+ | `exit-bounce-out-down/2500/500` | Bounce out downward |
1487
+ | `exit-bounce-out-left/2500/500` | Bounce out to left |
1488
+ | `exit-bounce-out-right/2500/500` | Bounce out to right |
1787
1489
 
1788
1490
  ## Loop Animations
1789
1491
 
1790
- Format: `animate-{type}/{frameLength}`
1492
+ Format: `loop-{type}/{durationMs}`
1791
1493
 
1792
- Loop animations repeat every `{frameLength}` frames. At 30fps:
1793
- - `/30` = 1 second loop
1794
- - `/15` = 0.5 second loop
1795
- - `/60` = 2 second loop
1494
+ Loop animations repeat every `{durationMs}` milliseconds:
1495
+ - `/1000` = 1 second loop
1496
+ - `/500` = 0.5 second loop
1497
+ - `/2000` = 2 second loop
1498
+
1499
+ When duration is omitted (`loop-bounce`), it defaults to 1000ms (1 second).
1796
1500
 
1797
1501
  ```tsx
1798
- // Pulse opacity every 15 frames (0.5s at 30fps)
1799
- <div style={tw('animate-pulse/15')}>Pulsing</div>
1502
+ // Pulse opacity every 500ms
1503
+ <div style={tw('loop-fade/500')}>Pulsing</div>
1800
1504
 
1801
- // Bounce every 20 frames
1802
- <div style={tw('animate-bounce/20')}>Bouncing</div>
1505
+ // Bounce every 800ms
1506
+ <div style={tw('loop-bounce/800')}>Bouncing</div>
1803
1507
 
1804
- // Full rotation every 60 frames (2s)
1805
- <div style={tw('animate-spin/60')}>Spinning</div>
1508
+ // Full rotation every 2000ms
1509
+ <div style={tw('loop-spin/2000')}>Spinning</div>
1806
1510
  ```
1807
1511
 
1808
1512
  | Class | Description |
1809
1513
  |-------|-------------|
1810
- | `animate-pulse/{n}` | Opacity pulse (0.5 → 1 → 0.5) |
1811
- | `animate-bounce/{n}` | Bounce up and down |
1812
- | `animate-spin/{n}` | Full 360° rotation |
1813
- | `animate-ping/{n}` | Scale up + fade out (radar effect) |
1814
- | `animate-wiggle/{n}` | Side to side wiggle |
1815
- | `animate-float/{n}` | Gentle up and down floating |
1514
+ | `loop-fade/{ms}` | Opacity pulse (0.5 → 1 → 0.5) |
1515
+ | `loop-bounce/{ms}` | Bounce up and down |
1516
+ | `loop-spin/{ms}` | Full 360° rotation |
1517
+ | `loop-ping/{ms}` | Scale up + fade out (radar effect) |
1518
+ | `loop-wiggle/{ms}` | Side to side wiggle |
1519
+ | `loop-float/{ms}` | Gentle up and down floating |
1520
+ | `loop-pulse/{ms}` | Scale pulse (1.0 → 1.05 → 1.0) |
1521
+ | `loop-shake/{ms}` | Shake side to side |
1816
1522
 
1817
1523
  ## Easing Functions
1818
1524
 
@@ -1820,16 +1526,16 @@ Add an easing class **before** the animation class to control the timing curve.
1820
1526
 
1821
1527
  ```tsx
1822
1528
  // Ease in (accelerate)
1823
- <h1 style={tw('ease-in animate-fade-in/0/1')}>Accelerating</h1>
1529
+ <h1 style={tw('ease-in enter-fade-in/0/1000')}>Accelerating</h1>
1824
1530
 
1825
1531
  // Ease out (decelerate) - default
1826
- <h1 style={tw('ease-out animate-fade-in/0/1')}>Decelerating</h1>
1532
+ <h1 style={tw('ease-out enter-fade-in/0/1000')}>Decelerating</h1>
1827
1533
 
1828
1534
  // Ease in-out (smooth)
1829
- <h1 style={tw('ease-in-out animate-fade-in/0/1')}>Smooth</h1>
1535
+ <h1 style={tw('ease-in-out enter-fade-in/0/1000')}>Smooth</h1>
1830
1536
 
1831
1537
  // Strong cubic easing
1832
- <h1 style={tw('ease-out-cubic animate-bounce-in/0/0.5')}>Dramatic</h1>
1538
+ <h1 style={tw('ease-out-cubic enter-bounce-in/0/500')}>Dramatic</h1>
1833
1539
  ```
1834
1540
 
1835
1541
  | Class | Description | Best For |
@@ -1845,26 +1551,45 @@ Add an easing class **before** the animation class to control the timing curve.
1845
1551
  | `ease-out-quart` | Very strong fast start | Punchy entrances |
1846
1552
  | `ease-in-out-quart` | Very strong both ends | Maximum drama |
1847
1553
 
1554
+ ## Combining Enter and Exit
1555
+
1556
+ You can use both enter and exit animations on the same element:
1557
+
1558
+ ```tsx
1559
+ export default function EnterExit({ tw, title }) {
1560
+ return (
1561
+ <div style={tw('flex items-center justify-center w-full h-full bg-black')}>
1562
+ {/* Fade in during first 500ms, fade out during last 500ms (assuming 3s video) */}
1563
+ <h1 style={tw('text-8xl font-bold text-white enter-fade-in/0/500 exit-fade-out/2500/500')}>
1564
+ {title}
1565
+ </h1>
1566
+ </div>
1567
+ );
1568
+ }
1569
+ ```
1570
+
1571
+ The opacities from multiple animations are **multiplied together**, so you get smooth transitions that combine properly.
1572
+
1848
1573
  ## Staggered Animations
1849
1574
 
1850
- Create sequenced animations by offsetting the start/end times:
1575
+ Create sequenced animations by offsetting start times:
1851
1576
 
1852
1577
  ```tsx
1853
1578
  export default function StaggeredList({ tw, items }) {
1854
1579
  return (
1855
1580
  <div style={tw('flex flex-col gap-4')}>
1856
- {/* First item: 0% to 30% */}
1857
- <div style={tw('ease-out animate-fade-in-left/0/0.3')}>
1581
+ {/* First item: starts at 0ms, lasts 300ms */}
1582
+ <div style={tw('ease-out enter-fade-in-left/0/300')}>
1858
1583
  {items[0]}
1859
1584
  </div>
1860
1585
 
1861
- {/* Second item: 10% to 40% */}
1862
- <div style={tw('ease-out animate-fade-in-left/0.1/0.4')}>
1586
+ {/* Second item: starts at 100ms, lasts 300ms */}
1587
+ <div style={tw('ease-out enter-fade-in-left/100/300')}>
1863
1588
  {items[1]}
1864
1589
  </div>
1865
1590
 
1866
- {/* Third item: 20% to 50% */}
1867
- <div style={tw('ease-out animate-fade-in-left/0.2/0.5')}>
1591
+ {/* Third item: starts at 200ms, lasts 300ms */}
1592
+ <div style={tw('ease-out enter-fade-in-left/200/300')}>
1868
1593
  {items[2]}
1869
1594
  </div>
1870
1595
  </div>
@@ -1881,13 +1606,13 @@ export default function DynamicStagger({ tw, items }) {
1881
1606
  return (
1882
1607
  <div style={tw('flex flex-col gap-4')}>
1883
1608
  {items.map((item, i) => {
1884
- const start = i * 0.1; // Each item starts 10% later
1885
- const end = start + 0.3; // Each animation lasts 30%
1609
+ const start = i * 100; // Each item starts 100ms later
1610
+ const duration = 300; // Each animation lasts 300ms
1886
1611
 
1887
1612
  return (
1888
1613
  <div
1889
1614
  key={i}
1890
- style={tw(`ease-out animate-fade-in-up/${start}/${end}`)}
1615
+ style={tw(`ease-out enter-fade-in-up/${start}/${duration}`)}
1891
1616
  >
1892
1617
  {item}
1893
1618
  </div>
@@ -1898,40 +1623,6 @@ export default function DynamicStagger({ tw, items }) {
1898
1623
  }
1899
1624
  ```
1900
1625
 
1901
- ## Combining Animations
1902
-
1903
- You can combine multiple animation classes. They will be applied together:
1904
-
1905
- ```tsx
1906
- // Note: Multiple transforms will be combined
1907
- <h1 style={tw('animate-fade-in/0/0.5 animate-scale-in/0/0.5')}>
1908
- Fade + Scale
1909
- </h1>
1910
- ```
1911
-
1912
- For more complex combinations, use manual animation with `progress`:
1913
-
1914
- ```tsx
1915
- export default function ComplexAnimation({ tw, progress, title }) {
1916
- // Custom compound animation
1917
- const opacity = Math.min(1, progress * 3);
1918
- const scale = 0.8 + progress * 0.2;
1919
- const rotation = (1 - progress) * -10;
1920
-
1921
- return (
1922
- <div style={tw('flex items-center justify-center w-full h-full')}>
1923
- <h1 style={{
1924
- ...tw('text-8xl font-bold'),
1925
- opacity,
1926
- transform: `scale(${scale}) rotate(${rotation}deg)`
1927
- }}>
1928
- {title}
1929
- </h1>
1930
- </div>
1931
- );
1932
- }
1933
- ```
1934
-
1935
1626
  ## Common Patterns
1936
1627
 
1937
1628
  ### Intro Sequence
@@ -1943,16 +1634,16 @@ export default function IntroVideo({ tw, title, subtitle, logo }) {
1943
1634
  {/* Logo appears first */}
1944
1635
  <img
1945
1636
  src={logo}
1946
- style={tw('h-20 mb-8 ease-out animate-scale-in/0/0.3')}
1637
+ style={tw('h-20 mb-8 ease-out enter-scale-in/0/300')}
1947
1638
  />
1948
1639
 
1949
1640
  {/* Title bounces in */}
1950
- <h1 style={tw('text-7xl font-bold text-white ease-out animate-bounce-in-up/0.2/0.5')}>
1641
+ <h1 style={tw('text-7xl font-bold text-white ease-out enter-bounce-in-up/200/500')}>
1951
1642
  {title}
1952
1643
  </h1>
1953
1644
 
1954
1645
  {/* Subtitle fades in last */}
1955
- <p style={tw('text-2xl text-white/80 mt-4 ease-out animate-fade-in-up/0.4/0.7')}>
1646
+ <p style={tw('text-2xl text-white/80 mt-4 ease-out enter-fade-in-up/400/700')}>
1956
1647
  {subtitle}
1957
1648
  </p>
1958
1649
  </div>
@@ -1969,7 +1660,7 @@ export default function TextReveal({ tw, words }) {
1969
1660
  {words.split(' ').map((word, i) => (
1970
1661
  <span
1971
1662
  key={i}
1972
- style={tw(`text-4xl font-bold ease-out animate-fade-in-up/${i * 0.1}/${i * 0.1 + 0.2}`)}
1663
+ style={tw(`text-4xl font-bold ease-out enter-fade-in-up/${i * 100}/200`)}
1973
1664
  >
1974
1665
  {word}
1975
1666
  </span>
@@ -1986,8 +1677,8 @@ export default function AnimatedBackground({ tw, children }) {
1986
1677
  return (
1987
1678
  <div style={tw('relative w-full h-full')}>
1988
1679
  {/* Floating background circles */}
1989
- <div style={tw('absolute top-10 left-10 w-20 h-20 rounded-full bg-white/10 animate-float/60')} />
1990
- <div style={tw('absolute bottom-20 right-20 w-32 h-32 rounded-full bg-white/10 animate-pulse/45')} />
1680
+ <div style={tw('absolute top-10 left-10 w-20 h-20 rounded-full bg-white/10 loop-float/2000')} />
1681
+ <div style={tw('absolute bottom-20 right-20 w-32 h-32 rounded-full bg-white/10 loop-fade/1500')} />
1991
1682
 
1992
1683
  {/* Main content */}
1993
1684
  <div style={tw('relative z-10')}>
@@ -1998,14 +1689,14 @@ export default function AnimatedBackground({ tw, children }) {
1998
1689
  }
1999
1690
  ```
2000
1691
 
2001
- ### Exit Animation
1692
+ ### Full Enter/Exit Animation
2002
1693
 
2003
1694
  ```tsx
2004
- export default function ExitAnimation({ tw, title }) {
1695
+ export default function FullAnimation({ tw, title }) {
2005
1696
  return (
2006
1697
  <div style={tw('flex items-center justify-center w-full h-full bg-black')}>
2007
- {/* Visible from 0-70%, then exits */}
2008
- <h1 style={tw('text-8xl font-bold text-white ease-in animate-fade-out-up/0.7/1')}>
1698
+ {/* Enter: starts at 0, lasts 400ms. Exit: starts at 2600ms, lasts 400ms */}
1699
+ <h1 style={tw('text-8xl font-bold text-white ease-out enter-bounce-in-up/0/400 exit-fade-out-up/2600/400')}>
2009
1700
  {title}
2010
1701
  </h1>
2011
1702
  </div>
@@ -2013,7 +1704,7 @@ export default function ExitAnimation({ tw, title }) {
2013
1704
  }
2014
1705
  ```
2015
1706
 
2016
- ## Manual Animation with `progress` and `frame`
1707
+ ## Programmatic Animations
2017
1708
 
2018
1709
  For complete control beyond animation classes, use `progress` and `frame` directly.
2019
1710
 
@@ -2026,22 +1717,23 @@ For complete control beyond animation classes, use `progress` and `frame` direct
2026
1717
 
2027
1718
  These are **only available in video templates**. Use them when animation classes aren't flexible enough.
2028
1719
 
2029
- ### Using `progress`
1720
+ ### Using `frame`
2030
1721
 
2031
1722
  ```tsx
2032
- export default function ProgressAnimation({ tw, progress, title }) {
2033
- // Custom fade based on progress
2034
- const opacity = progress < 0.3 ? progress / 0.3 : 1;
2035
-
2036
- // Custom scale based on progress
2037
- const scale = 0.8 + progress * 0.2; // 0.8 to 1.0
1723
+ export default function FrameAnimation({ tw, frame, title }) {
1724
+ // Color cycling using frame number
1725
+ const hue = (frame * 5) % 360; // Cycle through colors
1726
+
1727
+ // Pulsing based on frame
1728
+ const fps = 30;
1729
+ const pulse = Math.sin(frame / fps * Math.PI * 2) * 0.2 + 0.8; // 0.6 to 1.0
2038
1730
 
2039
1731
  return (
2040
- <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
1732
+ <div style={tw('flex items-center justify-center w-full h-full bg-black')}>
2041
1733
  <h1 style={{
2042
- ...tw('text-8xl font-bold text-white'),
2043
- opacity,
2044
- transform: `scale(${scale})`
1734
+ ...tw('text-8xl font-bold'),
1735
+ color: `hsl(${hue}, 70%, 60%)`,
1736
+ transform: `scale(${pulse})`
2045
1737
  }}>
2046
1738
  {title}
2047
1739
  </h1>
@@ -2050,23 +1742,22 @@ export default function ProgressAnimation({ tw, progress, title }) {
2050
1742
  }
2051
1743
  ```
2052
1744
 
2053
- ### Using `frame`
1745
+ ### Using `progress`
2054
1746
 
2055
1747
  ```tsx
2056
- export default function FrameAnimation({ tw, frame, title }) {
2057
- // Color cycling using frame number
2058
- const hue = (frame * 5) % 360; // Cycle through colors
2059
-
2060
- // Pulsing based on frame
2061
- const fps = 30;
2062
- const pulse = Math.sin(frame / fps * Math.PI * 2) * 0.2 + 0.8; // 0.6 to 1.0
1748
+ export default function ProgressAnimation({ tw, progress, title }) {
1749
+ // Custom fade based on progress
1750
+ const opacity = progress < 0.3 ? progress / 0.3 : 1;
1751
+
1752
+ // Custom scale based on progress
1753
+ const scale = 0.8 + progress * 0.2; // 0.8 to 1.0
2063
1754
 
2064
1755
  return (
2065
- <div style={tw('flex items-center justify-center w-full h-full bg-black')}>
1756
+ <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
2066
1757
  <h1 style={{
2067
- ...tw('text-8xl font-bold'),
2068
- color: `hsl(${hue}, 70%, 60%)`,
2069
- transform: `scale(${pulse})`
1758
+ ...tw('text-8xl font-bold text-white'),
1759
+ opacity,
1760
+ transform: `scale(${scale})`
2070
1761
  }}>
2071
1762
  {title}
2072
1763
  </h1>
@@ -2081,7 +1772,7 @@ export default function FrameAnimation({ tw, frame, title }) {
2081
1772
  export default function CustomEasing({ tw, progress, title }) {
2082
1773
  // Smoothstep easing
2083
1774
  const eased = progress * progress * (3 - 2 * progress);
2084
-
1775
+
2085
1776
  // Elastic easing
2086
1777
  const elastic = Math.pow(2, -10 * progress) * Math.sin((progress - 0.075) * (2 * Math.PI) / 0.3) + 1;
2087
1778
 
@@ -2099,7 +1790,7 @@ export default function CustomEasing({ tw, progress, title }) {
2099
1790
  }
2100
1791
  ```
2101
1792
 
2102
- ### When to Use Manual Animation
1793
+ ### When to Use Programmatic Animations
2103
1794
 
2104
1795
  Use `progress`/`frame` instead of animation classes when you need:
2105
1796
  - **Custom easing functions** (elastic, spring, bounce with specific curves)
@@ -2141,7 +1832,7 @@ Beyond the basics, loopwind provides:
2141
1832
  - `qr()` - Generate QR codes on the fly
2142
1833
  - `config` - Access user configuration
2143
1834
 
2144
- For image and video embedding, see the [Images](/images) and [Video](/video) pages.
1835
+ For image embedding, see the [Images](/images) page.
2145
1836
 
2146
1837
  ## Template Composition
2147
1838
 
@@ -2261,7 +1952,7 @@ You can customize QR code appearance:
2261
1952
 
2262
1953
  ## User Configuration
2263
1954
 
2264
- Access user settings from `loopwind.json` using the `config` prop:
1955
+ Access user settings from `_loopwind/loopwind.json` using the `config` prop:
2265
1956
 
2266
1957
  ```tsx
2267
1958
  export default function BrandedTemplate({ tw, config, title }) {
@@ -2281,8 +1972,8 @@ export default function BrandedTemplate({ tw, config, title }) {
2281
1972
  }
2282
1973
  ```
2283
1974
 
2284
- **User's loopwind.json:**
2285
- ```json
1975
+ **User's `_loopwind/loopwind.json`:**
1976
+ ```json title="_loopwind/loopwind.json"
2286
1977
  {
2287
1978
  "colors": {
2288
1979
  "brand": "#ff6b6b"
@@ -2309,8 +2000,7 @@ export default function MyTemplate({
2309
2000
 
2310
2001
  // Media helpers (see dedicated pages)
2311
2002
  image, // Image embedder → see /images
2312
- video, // Video frame getter → see /video
2313
-
2003
+
2314
2004
  // Video-specific (only in video templates)
2315
2005
  frame, // Current frame number → see /video
2316
2006
  progress, // Animation progress 0-1 → see /video
@@ -2325,7 +2015,7 @@ export default function MyTemplate({
2325
2015
  ## Next Steps
2326
2016
 
2327
2017
  - [Image Rendering and Embedding](/images)
2328
- - [Video Rendering and Animation](/video)
2018
+ - [Video Animation](/video)
2329
2019
  - [Styling with Tailwind & shadcn/ui](/styling)
2330
2020
  - [Custom Fonts](/fonts)
2331
2021
 
@@ -2415,7 +2105,7 @@ loopwind uses **shadcn/ui's design system** by default, providing semantic color
2415
2105
 
2416
2106
  ### Default Color Palette
2417
2107
 
2418
- All templates automatically have access to these semantic colors:
2108
+ All templates automatically have access to these semantic colors defined in `_loopwind/loopwind.json`:
2419
2109
 
2420
2110
  ```typescript
2421
2111
  colors: {
@@ -2650,9 +2340,9 @@ export default function GradientCard({ title, tw }) {
2650
2340
 
2651
2341
  ## Custom Theme Colors
2652
2342
 
2653
- Override default colors in your `loopwind.json`:
2343
+ You can override the default shadcn colors or add your own custom colors in `_loopwind/loopwind.json`:
2654
2344
 
2655
- ```json
2345
+ ```json title="_loopwind/loopwind.json"
2656
2346
  {
2657
2347
  "theme": {
2658
2348
  "colors": {
@@ -2665,11 +2355,12 @@ Override default colors in your `loopwind.json`:
2665
2355
  }
2666
2356
  ```
2667
2357
 
2668
- Then use in templates:
2358
+ Then use these custom colors in your templates:
2669
2359
 
2670
2360
  ```tsx
2671
2361
  tw('text-brand') // Uses your custom brand color
2672
2362
  tw('bg-primary') // Uses your custom primary color
2363
+ tw('bg-accent') // Uses your custom accent color
2673
2364
  ```
2674
2365
 
2675
2366
  ## Auto-Detection from tailwind.config.js
@@ -2679,8 +2370,9 @@ loopwind automatically detects and loads your project's Tailwind configuration:
2679
2370
  ```
2680
2371
  your-project/
2681
2372
  ├── tailwind.config.js ← Automatically detected
2682
- ├── loopwind.json
2683
- └── _loopwind/templates/
2373
+ └── _loopwind/
2374
+ ├── loopwind.json
2375
+ └── templates/
2684
2376
  ```
2685
2377
 
2686
2378
  This includes:
@@ -2775,13 +2467,13 @@ The recommended way to use fonts is through `loopwind.json` - configure fonts on
2775
2467
 
2776
2468
  ## Using Fonts from loopwind.json (Recommended)
2777
2469
 
2778
- Configure fonts in your `loopwind.json` and use Tailwind classes in templates.
2470
+ Configure fonts in your `_loopwind/loopwind.json` and use Tailwind classes in templates.
2779
2471
 
2780
2472
  ### Simple Setup
2781
2473
 
2782
- Define font families without loading custom fonts (uses system fonts):
2474
+ Define font families in `_loopwind/loopwind.json` without loading custom fonts (uses system fonts):
2783
2475
 
2784
- ```json
2476
+ ```json title="_loopwind/loopwind.json"
2785
2477
  {
2786
2478
  "fonts": {
2787
2479
  "sans": ["Inter", "system-ui", "-apple-system", "sans-serif"],
@@ -2810,13 +2502,13 @@ export default function({ title, tw }) {
2810
2502
  }
2811
2503
  ```
2812
2504
 
2813
- **Result:** Uses system fonts, falls back to Noto Sans for rendering.
2505
+ **Result:** Uses system fonts, falls back to Inter for rendering.
2814
2506
 
2815
2507
  ### Complete Setup (With Font Files)
2816
2508
 
2817
- Load custom font files for brand-specific typography:
2509
+ Load custom font files for brand-specific typography in `_loopwind/loopwind.json`:
2818
2510
 
2819
- ```json
2511
+ ```json title="_loopwind/loopwind.json"
2820
2512
  {
2821
2513
  "fonts": {
2822
2514
  "sans": {
@@ -2839,13 +2531,13 @@ Load custom font files for brand-specific typography:
2839
2531
  **Project structure:**
2840
2532
  ```
2841
2533
  your-project/
2842
- ├── loopwind.json
2843
- ├── fonts/
2844
- ├── Inter-Regular.woff
2845
- │ ├── Inter-Bold.woff
2846
- │ └── JetBrainsMono-Regular.woff
2847
- └── _loopwind/
2848
- └── templates/
2534
+ ├── _loopwind/
2535
+ ├── loopwind.json
2536
+ └── templates/
2537
+ └── fonts/
2538
+ ├── Inter-Regular.woff
2539
+ ├── Inter-Bold.woff
2540
+ └── JetBrainsMono-Regular.woff
2849
2541
  ```
2850
2542
 
2851
2543
  **Template usage (same as before):**
@@ -2862,62 +2554,23 @@ your-project/
2862
2554
  - `font-mono` - Uses `fonts.mono` from loopwind.json
2863
2555
 
2864
2556
  **Supported formats:**
2865
- - ✅ **WOFF** (`.woff`) - Recommended
2866
- - ✅ **WOFF2** (`.woff2`) - Best compression
2557
+ - ✅ **WOFF** (`.woff`) - Recommended for best compatibility
2867
2558
  - ✅ **TTF** (`.ttf`) - Also supported
2868
2559
  - ✅ **OTF** (`.otf`) - Also supported
2869
-
2870
- ## Template-Specific Fonts (Advanced)
2871
-
2872
- For templates that need unique fonts not shared across the project:
2873
-
2874
- **Template structure:**
2875
- ```
2876
- _loopwind/templates/my-template/
2877
- ├── template.tsx
2878
- └── fonts/
2879
- └── SpecialFont.woff
2880
- ```
2881
-
2882
- **template.tsx:**
2883
- ```tsx
2884
- export const meta = {
2885
- name: "my-template",
2886
- type: "image",
2887
- size: { width: 1200, height: 630 },
2888
- props: { title: "string" },
2889
- fonts: [
2890
- {
2891
- name: "Special Font",
2892
- path: "fonts/SpecialFont.woff",
2893
- weight: 400,
2894
- style: "normal"
2895
- }
2896
- ]
2897
- };
2898
-
2899
- export default function Template({ title, tw }) {
2900
- return (
2901
- <h1 style={{ fontFamily: 'Special Font', fontWeight: 400 }}>
2902
- {title}
2903
- </h1>
2904
- );
2905
- }
2906
- ```
2560
+ - ❌ **WOFF2** (`.woff2`) - Not supported by renderer
2907
2561
 
2908
2562
  ## Font Loading Priority
2909
2563
 
2910
2564
  loopwind loads fonts in this order:
2911
2565
 
2912
2566
  1. **loopwind.json fonts** (if configured with `files`)
2913
- 2. **Template meta fonts** (if specified in `export const meta`)
2914
- 3. **Default Noto Sans** (from CDN)
2567
+ 2. **Bundled Inter fonts** (included with CLI)
2915
2568
 
2916
- This means loopwind.json fonts override template fonts, ensuring consistency.
2569
+ This ensures fonts work out of the box with no configuration.
2917
2570
 
2918
2571
  ## Default Fonts
2919
2572
 
2920
- If no fonts are configured, loopwind automatically fetches **Noto Sans** from jsDelivr CDN.
2573
+ If no fonts are configured, loopwind uses **Inter** (Regular 400, Bold 700) which is bundled with the CLI. This means fonts work offline with no configuration required.
2921
2574
 
2922
2575
  ## Best Practices
2923
2576
 
@@ -2926,12 +2579,11 @@ If no fonts are configured, loopwind automatically fetches **Noto Sans** from js
2926
2579
  3. ✅ **Include fallbacks** - Always add system fonts: `["Inter", "system-ui", "sans-serif"]`
2927
2580
  4. ✅ **Match names** - First font in `family` array is used as the loaded font name
2928
2581
  5. ✅ **Relative paths** - Font paths are relative to `loopwind.json` location
2929
- 6. ⚠️ **Template fonts for special cases** - Only use template meta fonts for template-specific typography
2930
2582
 
2931
2583
  ## Examples
2932
2584
 
2933
2585
  ### Minimal Setup (System Fonts)
2934
- ```json
2586
+ ```json title="_loopwind/loopwind.json"
2935
2587
  {
2936
2588
  "fonts": {
2937
2589
  "sans": ["Inter", "-apple-system", "sans-serif"]
@@ -2941,7 +2593,7 @@ If no fonts are configured, loopwind automatically fetches **Noto Sans** from js
2941
2593
  Uses system Inter if available, falls back to Noto Sans for rendering.
2942
2594
 
2943
2595
  ### Brand Fonts Setup
2944
- ```json
2596
+ ```json title="_loopwind/loopwind.json"
2945
2597
  {
2946
2598
  "fonts": {
2947
2599
  "sans": {
@@ -2957,7 +2609,7 @@ Uses system Inter if available, falls back to Noto Sans for rendering.
2957
2609
  Loads and uses Montserrat for all templates.
2958
2610
 
2959
2611
  ### Multi-Font Setup
2960
- ```json
2612
+ ```json title="_loopwind/loopwind.json"
2961
2613
  {
2962
2614
  "fonts": {
2963
2615
  "sans": {
@@ -2984,6 +2636,50 @@ Loads and uses Montserrat for all templates.
2984
2636
  ```
2985
2637
  Loads different fonts for each style class.
2986
2638
 
2639
+ ### External Font URLs
2640
+ Load fonts directly from CDNs without downloading files:
2641
+
2642
+ ```json title="_loopwind/loopwind.json"
2643
+ {
2644
+ "fonts": {
2645
+ "sans": {
2646
+ "family": ["Inter", "sans-serif"],
2647
+ "files": [
2648
+ {
2649
+ "path": "https://unpkg.com/@fontsource/inter@5.0.18/files/inter-latin-400-normal.woff",
2650
+ "weight": 400
2651
+ },
2652
+ {
2653
+ "path": "https://unpkg.com/@fontsource/inter@5.0.18/files/inter-latin-700-normal.woff",
2654
+ "weight": 700
2655
+ }
2656
+ ]
2657
+ }
2658
+ }
2659
+ }
2660
+ ```
2661
+
2662
+ You can also mix local and external fonts:
2663
+
2664
+ ```json title="_loopwind/loopwind.json"
2665
+ {
2666
+ "fonts": {
2667
+ "sans": {
2668
+ "family": ["Inter", "sans-serif"],
2669
+ "files": [
2670
+ { "path": "./fonts/Inter-Regular.woff", "weight": 400 },
2671
+ {
2672
+ "path": "https://unpkg.com/@fontsource/inter@5.0.18/files/inter-latin-700-normal.woff",
2673
+ "weight": 700
2674
+ }
2675
+ ]
2676
+ }
2677
+ }
2678
+ }
2679
+ ```
2680
+
2681
+ **Note:** Use WOFF format (`.woff`) for best compatibility. WOFF2 is not supported by the underlying renderer.
2682
+
2987
2683
  ## Performance
2988
2684
 
2989
2685
  - ✅ **Font caching** - Fonts load once and are cached for all renders