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.
- package/dist/sdk/index.d.ts +2 -2
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/template.d.ts +97 -0
- package/dist/sdk/template.d.ts.map +1 -1
- package/dist/sdk/template.js +217 -0
- package/dist/sdk/template.js.map +1 -1
- package/examples/code-editor-templates.ts +173 -0
- package/examples/nextjs-template-import.ts +58 -0
- package/examples/visual-builder-templates.ts +336 -0
- package/package.json +3 -1
- package/test-jsx-support.mjs +120 -0
- package/test-sdk-user-templates.mjs +469 -0
- package/website/.astro/integrations/_inox-tools_astro-when/types.d.ts +1 -0
- package/website/.astro/types.d.ts +1 -0
- package/website/DEPLOYMENT.md +142 -0
- package/website/OG_IMAGES.md +141 -0
- package/website/astro.config.mjs +9 -1
- package/website/dist/.gitkeep +1 -0
- package/website/dist/_astro/agents.Yx-L_igG.css +1 -0
- package/website/dist/_routes.json +30 -0
- package/website/dist/_worker.js/_@astrojs-ssr-adapter.mjs +1033 -0
- package/website/dist/_worker.js/_astro-internal_middleware.mjs +40 -0
- package/website/dist/_worker.js/chunks/abap_BTmsHiP5.mjs +1 -0
- package/website/dist/_worker.js/chunks/actionscript-3_DmBelb1E.mjs +1 -0
- package/website/dist/_worker.js/chunks/ada_8-E0ahCN.mjs +1 -0
- package/website/dist/_worker.js/chunks/andromeeda_XI-CXx50.mjs +1 -0
- package/website/dist/_worker.js/chunks/angular-html_DKGh3gGH.mjs +1 -0
- package/website/dist/_worker.js/chunks/angular-ts_-qZGsJoA.mjs +1 -0
- package/website/dist/_worker.js/chunks/apache_ijTUt0Ee.mjs +1 -0
- package/website/dist/_worker.js/chunks/apex_agu1c6Sh.mjs +1 -0
- package/website/dist/_worker.js/chunks/apl_Bj2f7Art.mjs +1 -0
- package/website/dist/_worker.js/chunks/applescript_B_vXrOh3.mjs +1 -0
- package/website/dist/_worker.js/chunks/ara_DCEQ2rnh.mjs +1 -0
- package/website/dist/_worker.js/chunks/asciidoc_CGN_EkYS.mjs +1 -0
- package/website/dist/_worker.js/chunks/asm_BBWZgnDp.mjs +1 -0
- package/website/dist/_worker.js/chunks/astro/assets-service_j52rQLzU.mjs +721 -0
- package/website/dist/_worker.js/chunks/astro/server_Y5_QHO8v.mjs +2401 -0
- package/website/dist/_worker.js/chunks/astro-designed-error-pages_BNTLO-TA.mjs +542 -0
- package/website/dist/_worker.js/chunks/astro_Dr_hht3h.mjs +1 -0
- package/website/dist/_worker.js/chunks/aurora-x_9GHG8nSq.mjs +1 -0
- package/website/dist/_worker.js/chunks/awk_DHRvhXot.mjs +1 -0
- package/website/dist/_worker.js/chunks/ayu-dark_CcvqmEHE.mjs +1 -0
- package/website/dist/_worker.js/chunks/ballerina_C7SdeSZb.mjs +1 -0
- package/website/dist/_worker.js/chunks/bat_Dv4A3u45.mjs +1 -0
- package/website/dist/_worker.js/chunks/beancount_BfPf9Luv.mjs +1 -0
- package/website/dist/_worker.js/chunks/berry_B8rfM3lL.mjs +1 -0
- package/website/dist/_worker.js/chunks/bibtex_TcjYgtJM.mjs +1 -0
- package/website/dist/_worker.js/chunks/bicep_CrlFWCdN.mjs +1 -0
- package/website/dist/_worker.js/chunks/blade_lanKVYID.mjs +1 -0
- package/website/dist/_worker.js/chunks/bsl_BhppzXMB.mjs +1 -0
- package/website/dist/_worker.js/chunks/c_6FBALJTK.mjs +1 -0
- package/website/dist/_worker.js/chunks/cadence_2txU9LVE.mjs +1 -0
- package/website/dist/_worker.js/chunks/cairo_BkrFAIlP.mjs +1 -0
- package/website/dist/_worker.js/chunks/catppuccin-frappe_CkEqIYhU.mjs +1 -0
- package/website/dist/_worker.js/chunks/catppuccin-latte_DG4Gx_-v.mjs +1 -0
- package/website/dist/_worker.js/chunks/catppuccin-macchiato_Cwi3vCXf.mjs +1 -0
- package/website/dist/_worker.js/chunks/catppuccin-mocha_L9_OPlFX.mjs +1 -0
- package/website/dist/_worker.js/chunks/clarity_BEAe4Ulu.mjs +1 -0
- package/website/dist/_worker.js/chunks/clojure_VnUX6p2g.mjs +1 -0
- package/website/dist/_worker.js/chunks/cmake_0-SGkZEj.mjs +1 -0
- package/website/dist/_worker.js/chunks/cobol_92M_KGaE.mjs +1 -0
- package/website/dist/_worker.js/chunks/codeowners_CzMwskBv.mjs +1 -0
- package/website/dist/_worker.js/chunks/codeql_DWJZNHv1.mjs +1 -0
- package/website/dist/_worker.js/chunks/coffee_CQjKU2fh.mjs +1 -0
- package/website/dist/_worker.js/chunks/common-lisp_BBLWDpS5.mjs +1 -0
- package/website/dist/_worker.js/chunks/coq_hedRFV3D.mjs +1 -0
- package/website/dist/_worker.js/chunks/cpp_DlS1i6Zs.mjs +1 -0
- package/website/dist/_worker.js/chunks/crystal_D6n65fKV.mjs +1 -0
- package/website/dist/_worker.js/chunks/csharp_C6FCVFzc.mjs +1 -0
- package/website/dist/_worker.js/chunks/css_C5uJEgmJ.mjs +1 -0
- package/website/dist/_worker.js/chunks/csv_CtMYuuJl.mjs +1 -0
- package/website/dist/_worker.js/chunks/cue_BsPexqx6.mjs +1 -0
- package/website/dist/_worker.js/chunks/cypher_apzf6OBi.mjs +1 -0
- package/website/dist/_worker.js/chunks/d_DcvIRcgm.mjs +1 -0
- package/website/dist/_worker.js/chunks/dark-plus_C01ONtzj.mjs +1 -0
- package/website/dist/_worker.js/chunks/dart_WkzM5WrV.mjs +1 -0
- package/website/dist/_worker.js/chunks/dax_DjXAO5V4.mjs +1 -0
- package/website/dist/_worker.js/chunks/desktop_C92LCxdc.mjs +1 -0
- package/website/dist/_worker.js/chunks/diff_CVwM_9XJ.mjs +1 -0
- package/website/dist/_worker.js/chunks/docker_DPzgJf6Z.mjs +1 -0
- package/website/dist/_worker.js/chunks/dotenv_D_vgANvA.mjs +1 -0
- package/website/dist/_worker.js/chunks/dracula-soft_CLnUBwFm.mjs +1 -0
- package/website/dist/_worker.js/chunks/dracula_lBVpb6Lb.mjs +1 -0
- package/website/dist/_worker.js/chunks/dream-maker_DTLbzd_J.mjs +1 -0
- package/website/dist/_worker.js/chunks/edge_i54JYm3_.mjs +1 -0
- package/website/dist/_worker.js/chunks/elixir_BJCIjTu4.mjs +1 -0
- package/website/dist/_worker.js/chunks/elm_BbXD39-_.mjs +1 -0
- package/website/dist/_worker.js/chunks/emacs-lisp_pxa5cXaN.mjs +1 -0
- package/website/dist/_worker.js/chunks/erb_Ccjijeee.mjs +1 -0
- package/website/dist/_worker.js/chunks/erlang_B2VM_hi7.mjs +1 -0
- package/website/dist/_worker.js/chunks/everforest-dark_BxvIPBim.mjs +1 -0
- package/website/dist/_worker.js/chunks/everforest-light_B7VoyaJM.mjs +1 -0
- package/website/dist/_worker.js/chunks/fennel_D-uo7X6c.mjs +1 -0
- package/website/dist/_worker.js/chunks/fish_BjePoK3m.mjs +1 -0
- package/website/dist/_worker.js/chunks/fluent_C8fgkzLX.mjs +1 -0
- package/website/dist/_worker.js/chunks/fortran-fixed-form_D1pu5zrc.mjs +1 -0
- package/website/dist/_worker.js/chunks/fortran-free-form_CSGOhJD6.mjs +1 -0
- package/website/dist/_worker.js/chunks/fsharp_B0xy-A4Y.mjs +1 -0
- package/website/dist/_worker.js/chunks/gdresource_CWppjlHq.mjs +1 -0
- package/website/dist/_worker.js/chunks/gdscript_eQCHchcS.mjs +1 -0
- package/website/dist/_worker.js/chunks/gdshader_C4kxepX7.mjs +1 -0
- package/website/dist/_worker.js/chunks/genie_ACtQLcDW.mjs +1 -0
- package/website/dist/_worker.js/chunks/gherkin_BFp2uKUd.mjs +1 -0
- package/website/dist/_worker.js/chunks/git-commit_CLg9ZwMV.mjs +1 -0
- package/website/dist/_worker.js/chunks/git-rebase_DG8A80Nt.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-dark-default_BI0EP2Kv.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-dark-dimmed_a_NIC0Xb.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-dark-high-contrast_jZGqT7hk.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-dark_CHCDNd2O.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-light-default_DRbOW5RG.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-light-high-contrast_tn_kWutM.mjs +1 -0
- package/website/dist/_worker.js/chunks/github-light_D9brYzot.mjs +1 -0
- package/website/dist/_worker.js/chunks/gleam_Dmhu1oxW.mjs +1 -0
- package/website/dist/_worker.js/chunks/glimmer-js_BfZbXy8A.mjs +1 -0
- package/website/dist/_worker.js/chunks/glimmer-ts_B9QVICrD.mjs +1 -0
- package/website/dist/_worker.js/chunks/glsl_DD2PPwOs.mjs +1 -0
- package/website/dist/_worker.js/chunks/gnuplot_D2OYChUX.mjs +1 -0
- package/website/dist/_worker.js/chunks/go_DYGFTe3h.mjs +1 -0
- package/website/dist/_worker.js/chunks/graphql_B7XsT3nH.mjs +1 -0
- package/website/dist/_worker.js/chunks/groovy_BO12Uwkl.mjs +1 -0
- package/website/dist/_worker.js/chunks/hack_CB2_ztCP.mjs +1 -0
- package/website/dist/_worker.js/chunks/haml_CyfDcDD3.mjs +1 -0
- package/website/dist/_worker.js/chunks/handlebars_CfpxpWm2.mjs +1 -0
- package/website/dist/_worker.js/chunks/haskell_jUeC5uN5.mjs +1 -0
- package/website/dist/_worker.js/chunks/haxe_B6GxP1WB.mjs +1 -0
- package/website/dist/_worker.js/chunks/hcl_DwoHV2oh.mjs +1 -0
- package/website/dist/_worker.js/chunks/hjson_DV7cJRk4.mjs +1 -0
- package/website/dist/_worker.js/chunks/hlsl_BlFCscPI.mjs +1 -0
- package/website/dist/_worker.js/chunks/houston_COBFG1Mx.mjs +1 -0
- package/website/dist/_worker.js/chunks/html-derivative_C9pJ337h.mjs +1 -0
- package/website/dist/_worker.js/chunks/html_D1OkrZS5.mjs +1 -0
- package/website/dist/_worker.js/chunks/http_DIGXRqvJ.mjs +1 -0
- package/website/dist/_worker.js/chunks/hxml_DEwh9i-c.mjs +1 -0
- package/website/dist/_worker.js/chunks/hy_DDoIgW1K.mjs +1 -0
- package/website/dist/_worker.js/chunks/imba_B00zbHo4.mjs +1 -0
- package/website/dist/_worker.js/chunks/index_C1UTDwYg.mjs +1861 -0
- package/website/dist/_worker.js/chunks/ini_D7XQA_p8.mjs +1 -0
- package/website/dist/_worker.js/chunks/java_B9wdFd8K.mjs +1 -0
- package/website/dist/_worker.js/chunks/javascript_CLsPGOON.mjs +1 -0
- package/website/dist/_worker.js/chunks/jinja_jarBCAN1.mjs +1 -0
- package/website/dist/_worker.js/chunks/jison_oGg3J708.mjs +1 -0
- package/website/dist/_worker.js/chunks/json5_DlZ1Kyaa.mjs +1 -0
- package/website/dist/_worker.js/chunks/json_DaYk_FMp.mjs +1 -0
- package/website/dist/_worker.js/chunks/jsonc_DlwgfSDs.mjs +1 -0
- package/website/dist/_worker.js/chunks/jsonl_BbCCVaZF.mjs +1 -0
- package/website/dist/_worker.js/chunks/jsonnet_Dt-G75xe.mjs +1 -0
- package/website/dist/_worker.js/chunks/jssm_BtKFTj2A.mjs +1 -0
- package/website/dist/_worker.js/chunks/jsx_DDx_xAZ8.mjs +1 -0
- package/website/dist/_worker.js/chunks/julia_CK0lv68l.mjs +1 -0
- package/website/dist/_worker.js/chunks/kanagawa-dragon_BldAK3Oo.mjs +1 -0
- package/website/dist/_worker.js/chunks/kanagawa-lotus_DVM8FX9_.mjs +1 -0
- package/website/dist/_worker.js/chunks/kanagawa-wave_Dpih0AKP.mjs +1 -0
- package/website/dist/_worker.js/chunks/kotlin_kWneB9V_.mjs +1 -0
- package/website/dist/_worker.js/chunks/kusto_BKVATd95.mjs +1 -0
- package/website/dist/_worker.js/chunks/laserwave_BqatxsVl.mjs +1 -0
- package/website/dist/_worker.js/chunks/latex_LVDcGBbc.mjs +1 -0
- package/website/dist/_worker.js/chunks/lean_W7qo-5M2.mjs +1 -0
- package/website/dist/_worker.js/chunks/less_DFNwJnBH.mjs +1 -0
- package/website/dist/_worker.js/chunks/light-plus_Dp0AoWsO.mjs +1 -0
- package/website/dist/_worker.js/chunks/liquid_D24qs0pc.mjs +1 -0
- package/website/dist/_worker.js/chunks/log_IPWMXriF.mjs +1 -0
- package/website/dist/_worker.js/chunks/logo_C6KaatrQ.mjs +1 -0
- package/website/dist/_worker.js/chunks/lua_CwnEf-T7.mjs +1 -0
- package/website/dist/_worker.js/chunks/luau_Br3-CXjS.mjs +1 -0
- package/website/dist/_worker.js/chunks/make_UBNG-kOo.mjs +1 -0
- package/website/dist/_worker.js/chunks/markdown_C7mhJFCm.mjs +1 -0
- package/website/dist/_worker.js/chunks/marko_4tchUvI7.mjs +1 -0
- package/website/dist/_worker.js/chunks/material-theme-darker_SKtaNEPn.mjs +1 -0
- package/website/dist/_worker.js/chunks/material-theme-lighter_zOX_DZCH.mjs +1 -0
- package/website/dist/_worker.js/chunks/material-theme-ocean_BN9WbhdC.mjs +1 -0
- package/website/dist/_worker.js/chunks/material-theme-palenight_DT_covjH.mjs +1 -0
- package/website/dist/_worker.js/chunks/material-theme_6RpeM3kc.mjs +1 -0
- package/website/dist/_worker.js/chunks/matlab_DCOXsPKR.mjs +1 -0
- package/website/dist/_worker.js/chunks/mdc_B9gb2UFP.mjs +1 -0
- package/website/dist/_worker.js/chunks/mdx_DGU7Nu9u.mjs +1 -0
- package/website/dist/_worker.js/chunks/mermaid_B69URzsZ.mjs +1 -0
- package/website/dist/_worker.js/chunks/min-dark_BgxifOMI.mjs +1 -0
- package/website/dist/_worker.js/chunks/min-light_BrPjXxUp.mjs +1 -0
- package/website/dist/_worker.js/chunks/mipsasm_9U-4_t7k.mjs +1 -0
- package/website/dist/_worker.js/chunks/mojo_B0wt7ug3.mjs +1 -0
- package/website/dist/_worker.js/chunks/monokai_B6Pxpoyi.mjs +1 -0
- package/website/dist/_worker.js/chunks/move_1eid4CyR.mjs +1 -0
- package/website/dist/_worker.js/chunks/narrat_Ds6-p5JZ.mjs +1 -0
- package/website/dist/_worker.js/chunks/nextflow_v2N1Qlqa.mjs +1 -0
- package/website/dist/_worker.js/chunks/nginx_Bp9Ab2NH.mjs +1 -0
- package/website/dist/_worker.js/chunks/night-owl_CdwOw_sc.mjs +1 -0
- package/website/dist/_worker.js/chunks/nim_BXGDUe53.mjs +1 -0
- package/website/dist/_worker.js/chunks/nix_CUig1nJH.mjs +1 -0
- package/website/dist/_worker.js/chunks/noop-middleware_DlWGj5t5.mjs +10 -0
- package/website/dist/_worker.js/chunks/nord_SPoG1iae.mjs +1 -0
- package/website/dist/_worker.js/chunks/nushell_DJw1Lca8.mjs +1 -0
- package/website/dist/_worker.js/chunks/objective-c_Bktzl_CO.mjs +1 -0
- package/website/dist/_worker.js/chunks/objective-cpp_CP4DWdDp.mjs +1 -0
- package/website/dist/_worker.js/chunks/ocaml_CeEAs7bZ.mjs +1 -0
- package/website/dist/_worker.js/chunks/one-dark-pro_-hIwCNMi.mjs +1 -0
- package/website/dist/_worker.js/chunks/one-light_DSmYvJ05.mjs +1 -0
- package/website/dist/_worker.js/chunks/pascal_C-S_Ms_o.mjs +1 -0
- package/website/dist/_worker.js/chunks/perl_CKamvo15.mjs +1 -0
- package/website/dist/_worker.js/chunks/php_BlmcX_F3.mjs +1 -0
- package/website/dist/_worker.js/chunks/plastic_Ryt8tVoA.mjs +1 -0
- package/website/dist/_worker.js/chunks/plsql_Cb3v7cBj.mjs +1 -0
- package/website/dist/_worker.js/chunks/po_DZbdNRlo.mjs +1 -0
- package/website/dist/_worker.js/chunks/poimandres_bYmE3_5d.mjs +1 -0
- package/website/dist/_worker.js/chunks/polar_pJkMGwoW.mjs +1 -0
- package/website/dist/_worker.js/chunks/postcss_BAXSOKgk.mjs +1 -0
- package/website/dist/_worker.js/chunks/powerquery_oITMGN4x.mjs +1 -0
- package/website/dist/_worker.js/chunks/powershell_6306-xIF.mjs +1 -0
- package/website/dist/_worker.js/chunks/prisma_DSDxnZGz.mjs +1 -0
- package/website/dist/_worker.js/chunks/prolog_CxG7tjZR.mjs +1 -0
- package/website/dist/_worker.js/chunks/proto_CS9ByXm1.mjs +1 -0
- package/website/dist/_worker.js/chunks/pug_BMtLJo6U.mjs +1 -0
- package/website/dist/_worker.js/chunks/puppet_BfeeSzee.mjs +1 -0
- package/website/dist/_worker.js/chunks/purescript_BFfueNaH.mjs +1 -0
- package/website/dist/_worker.js/chunks/python_Cc4Faapv.mjs +1 -0
- package/website/dist/_worker.js/chunks/qml_C1CTJTK8.mjs +1 -0
- package/website/dist/_worker.js/chunks/qmldir_nG1KaqKR.mjs +1 -0
- package/website/dist/_worker.js/chunks/qss_Cncxk263.mjs +1 -0
- package/website/dist/_worker.js/chunks/r_ChR54Ihi.mjs +1 -0
- package/website/dist/_worker.js/chunks/racket_BDrhptDs.mjs +1 -0
- package/website/dist/_worker.js/chunks/raku_07OUHa0P.mjs +1 -0
- package/website/dist/_worker.js/chunks/razor_DIP3INLa.mjs +1 -0
- package/website/dist/_worker.js/chunks/red_DOPXfj-6.mjs +1 -0
- package/website/dist/_worker.js/chunks/reg_B64SwEDj.mjs +1 -0
- package/website/dist/_worker.js/chunks/regexp_ButFGoB5.mjs +1 -0
- package/website/dist/_worker.js/chunks/rel_BWJAWqZD.mjs +1 -0
- package/website/dist/_worker.js/chunks/riscv_79gXlbsF.mjs +1 -0
- package/website/dist/_worker.js/chunks/rose-pine-dawn_DHIjVGd3.mjs +1 -0
- package/website/dist/_worker.js/chunks/rose-pine-moon_t86aEbs0.mjs +1 -0
- package/website/dist/_worker.js/chunks/rose-pine_BHgrcDCs.mjs +1 -0
- package/website/dist/_worker.js/chunks/rst_D3F4Fcpj.mjs +1 -0
- package/website/dist/_worker.js/chunks/ruby_Cs7vM9iv.mjs +1 -0
- package/website/dist/_worker.js/chunks/rust_DpyRVatH.mjs +1 -0
- package/website/dist/_worker.js/chunks/sas_DW45xZXN.mjs +1 -0
- package/website/dist/_worker.js/chunks/sass_C6SiMwN_.mjs +1 -0
- package/website/dist/_worker.js/chunks/scala_DlZOjNZk.mjs +1 -0
- package/website/dist/_worker.js/chunks/scheme_D2ezSJXu.mjs +1 -0
- package/website/dist/_worker.js/chunks/scss_DG5Spjqu.mjs +1 -0
- package/website/dist/_worker.js/chunks/sdbl_ZCYaj4VN.mjs +1 -0
- package/website/dist/_worker.js/chunks/shaderlab_CAcRkg1_.mjs +1 -0
- package/website/dist/_worker.js/chunks/shellscript_BWwhkDVh.mjs +1 -0
- package/website/dist/_worker.js/chunks/shellsession_BfEA3juK.mjs +1 -0
- package/website/dist/_worker.js/chunks/slack-dark_CL3lSpCc.mjs +1 -0
- package/website/dist/_worker.js/chunks/slack-ochin_DdZKOQVh.mjs +1 -0
- package/website/dist/_worker.js/chunks/smalltalk_DgilzSui.mjs +1 -0
- package/website/dist/_worker.js/chunks/snazzy-light_eJU08Pz_.mjs +1 -0
- package/website/dist/_worker.js/chunks/solarized-dark_Dg_YQywx.mjs +1 -0
- package/website/dist/_worker.js/chunks/solarized-light_BnIsrA6p.mjs +1 -0
- package/website/dist/_worker.js/chunks/solidity_DkseH8pQ.mjs +1 -0
- package/website/dist/_worker.js/chunks/soy_DU7bOYoG.mjs +1 -0
- package/website/dist/_worker.js/chunks/sparql_BuI1DBDH.mjs +1 -0
- package/website/dist/_worker.js/chunks/splunk_B8Ha9Pkg.mjs +1 -0
- package/website/dist/_worker.js/chunks/sql_BniHwea5.mjs +1 -0
- package/website/dist/_worker.js/chunks/ssh-config_CkE1GuVe.mjs +1 -0
- package/website/dist/_worker.js/chunks/stata_Dtqpbd_l.mjs +1 -0
- package/website/dist/_worker.js/chunks/stylus_CXTtglzO.mjs +1 -0
- package/website/dist/_worker.js/chunks/svelte_BjWYcUCN.mjs +1 -0
- package/website/dist/_worker.js/chunks/swift_BzHql_rM.mjs +1 -0
- package/website/dist/_worker.js/chunks/synthwave-84_DLRNhxNA.mjs +1 -0
- package/website/dist/_worker.js/chunks/system-verilog_ChyInPph.mjs +1 -0
- package/website/dist/_worker.js/chunks/systemd_Bi9Qa2qD.mjs +1 -0
- package/website/dist/_worker.js/chunks/talonscript_B3sH_Y-V.mjs +1 -0
- package/website/dist/_worker.js/chunks/tasl_BJ5yipRs.mjs +1 -0
- package/website/dist/_worker.js/chunks/tcl_CoJQjNoP.mjs +1 -0
- package/website/dist/_worker.js/chunks/templ_CrU7Ffil.mjs +1 -0
- package/website/dist/_worker.js/chunks/terraform_DT9JSFpC.mjs +1 -0
- package/website/dist/_worker.js/chunks/tex_5PKu2yA0.mjs +1 -0
- package/website/dist/_worker.js/chunks/tokyo-night_Buo8OK7-.mjs +1 -0
- package/website/dist/_worker.js/chunks/toml_CPuXX3oc.mjs +1 -0
- package/website/dist/_worker.js/chunks/ts-tags_D0M_1VSH.mjs +1 -0
- package/website/dist/_worker.js/chunks/tsv_CuivVNot.mjs +1 -0
- package/website/dist/_worker.js/chunks/tsx_MkuGr8MY.mjs +1 -0
- package/website/dist/_worker.js/chunks/turtle_BqgEPK7f.mjs +1 -0
- package/website/dist/_worker.js/chunks/twig_r1G9rpYJ.mjs +1 -0
- package/website/dist/_worker.js/chunks/typescript_Au5buqzM.mjs +1 -0
- package/website/dist/_worker.js/chunks/typespec_47rhBK_z.mjs +1 -0
- package/website/dist/_worker.js/chunks/typst_BAtuQLh-.mjs +1 -0
- package/website/dist/_worker.js/chunks/v_BIvWImHg.mjs +1 -0
- package/website/dist/_worker.js/chunks/vala_DYEacj30.mjs +1 -0
- package/website/dist/_worker.js/chunks/vb_CikQuqGJ.mjs +1 -0
- package/website/dist/_worker.js/chunks/verilog_BQRENwI-.mjs +1 -0
- package/website/dist/_worker.js/chunks/vesper_DA0kvTmj.mjs +1 -0
- package/website/dist/_worker.js/chunks/vhdl_DHscJIyg.mjs +1 -0
- package/website/dist/_worker.js/chunks/viml_F2pvMwvG.mjs +1 -0
- package/website/dist/_worker.js/chunks/vitesse-black_D9tjNzd0.mjs +1 -0
- package/website/dist/_worker.js/chunks/vitesse-dark_Bnm5d0hd.mjs +1 -0
- package/website/dist/_worker.js/chunks/vitesse-light_CHwbyjNR.mjs +1 -0
- package/website/dist/_worker.js/chunks/vue-html_DyYtbbMK.mjs +1 -0
- package/website/dist/_worker.js/chunks/vue_DofN6juy.mjs +1 -0
- package/website/dist/_worker.js/chunks/vyper_CiR0m-OV.mjs +1 -0
- package/website/dist/_worker.js/chunks/wasm_CwIGgRGf.mjs +1 -0
- package/website/dist/_worker.js/chunks/wasm_jKWhg0J0.mjs +1 -0
- package/website/dist/_worker.js/chunks/wenyan_DKvVZKXW.mjs +1 -0
- package/website/dist/_worker.js/chunks/wgsl_BOWZY7yw.mjs +1 -0
- package/website/dist/_worker.js/chunks/wikitext_CXDhhHPy.mjs +1 -0
- package/website/dist/_worker.js/chunks/wolfram_ChkmGnW0.mjs +1 -0
- package/website/dist/_worker.js/chunks/xml_DXH3hHIu.mjs +1 -0
- package/website/dist/_worker.js/chunks/xsl_DuP2mFjg.mjs +1 -0
- package/website/dist/_worker.js/chunks/yaml_IGiEkTge.mjs +1 -0
- package/website/dist/_worker.js/chunks/zenscript_59iXGyNw.mjs +1 -0
- package/website/dist/_worker.js/chunks/zig_DKzb0zdT.mjs +1 -0
- package/website/dist/_worker.js/index.js +53 -0
- package/website/dist/_worker.js/manifest_BAAoOzaU.mjs +98 -0
- package/website/dist/_worker.js/pages/_image.astro.mjs +24 -0
- package/website/dist/_worker.js/pages/agents.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/animation.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/api/raw-markdown/_---path_.astro.mjs +44 -0
- package/website/dist/_worker.js/pages/config.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/fonts.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/getting-started.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/helpers.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/images.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/index.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/llm.txt.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/preview.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/sdk.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/sitemap.xml.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/styling.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/templates.astro.mjs +1 -0
- package/website/dist/_worker.js/pages/video.astro.mjs +1 -0
- package/website/dist/_worker.js/renderers.mjs +57 -0
- package/website/dist/agents/index.html +3 -2
- package/website/dist/animation/index.html +402 -128
- package/website/dist/config/index.html +184 -0
- package/website/dist/fonts/index.html +56 -52
- package/website/dist/getting-started/index.html +107 -0
- package/website/dist/helpers/index.html +8 -8
- package/website/dist/images/index.html +50 -23
- package/website/dist/index.html +114 -148
- package/website/dist/llm.txt +751 -1055
- package/website/dist/preview/index.html +111 -0
- package/website/dist/robots.txt +35 -0
- package/website/dist/sdk/index.html +390 -52
- package/website/dist/sitemap.xml +76 -0
- package/website/dist/styling/index.html +10 -7
- package/website/dist/templates/index.html +11 -59
- package/website/dist/video/index.html +106 -537
- package/website/package-lock.json +1077 -17
- package/website/templates/og-image.tsx +61 -0
- package/website/dist/_astro/agents.I1-fN38o.css +0 -1
package/website/dist/llm.txt
CHANGED
|
@@ -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-
|
|
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 =
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
53
|
-
<div style={tw('flex
|
|
54
|
-
<
|
|
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-
|
|
69
|
+
<h1 style={tw('text-6xl font-bold text-foreground mb-6')}>
|
|
58
70
|
{title}
|
|
59
71
|
</h1>
|
|
60
|
-
<p style={tw('text-
|
|
72
|
+
<p style={tw('text-2xl text-muted-foreground')}>
|
|
61
73
|
{description}
|
|
62
74
|
</p>
|
|
63
75
|
</div>
|
|
64
76
|
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
##
|
|
89
|
+
## Generate Images and Videos with Code
|
|
89
90
|
|
|
90
|
-
|
|
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
|
-
|
|
93
|
+
<CodeVideoDemo
|
|
94
|
+
code={videoCode}
|
|
95
|
+
videoSrc="/demo-intro.mp4"
|
|
96
|
+
title="Video Templates"
|
|
97
|
+
/>
|
|
103
98
|
|
|
104
|
-
|
|
99
|
+
**Render it in one command:**
|
|
105
100
|
|
|
106
101
|
```bash
|
|
107
|
-
|
|
102
|
+
loopwind render video-intro '{"title":"Welcome!","subtitle":"Built with loopwind"}'
|
|
108
103
|
```
|
|
109
104
|
|
|
110
|
-
|
|
105
|
+
**Perfect for:** Social media intros, animated logos, product demos, tutorial overlays.
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
npx loopwind --help
|
|
114
|
-
```
|
|
107
|
+
---
|
|
115
108
|
|
|
116
|
-
|
|
109
|
+
## Image Templates
|
|
117
110
|
|
|
118
|
-
|
|
111
|
+
Generate Open Graph images, Twitter cards, and more:
|
|
119
112
|
|
|
120
|
-
```
|
|
121
|
-
|
|
113
|
+
```tsx
|
|
114
|
+
{imageCode}
|
|
122
115
|
```
|
|
123
116
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
### Install a Template
|
|
127
|
-
|
|
128
|
-
#### 1. Official Templates
|
|
117
|
+
**Render it:**
|
|
129
118
|
|
|
130
119
|
```bash
|
|
131
|
-
loopwind
|
|
132
|
-
loopwind add video-template
|
|
120
|
+
loopwind render og-image '{"title":"Hello World","description":"My awesome post"}'
|
|
133
121
|
```
|
|
134
122
|
|
|
135
|
-
|
|
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
|
-
|
|
125
|
+
---
|
|
143
126
|
|
|
144
|
-
|
|
145
|
-
loopwind render template-name '{"title":"Hello World","subtitle":"Built with loopwind"}'
|
|
146
|
-
```
|
|
127
|
+
## Why loopwind?
|
|
147
128
|
|
|
148
|
-
|
|
129
|
+
### 🎨 Beautiful by Default
|
|
149
130
|
|
|
150
|
-
|
|
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
|
-
|
|
133
|
+
### ⚡ Blazing Fast Rendering
|
|
153
134
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
159
|
-
loopwind add github:username/repo/path/to/template
|
|
139
|
+
### 📦 Template Marketplace (coming soon)
|
|
160
140
|
|
|
161
|
-
|
|
162
|
-
loopwind add https://example.com/my-template.json
|
|
141
|
+
Install templates like UI components. Browse, add, and customize in seconds:
|
|
163
142
|
|
|
164
|
-
|
|
165
|
-
loopwind add
|
|
143
|
+
```bash
|
|
144
|
+
loopwind add banner-hero
|
|
145
|
+
loopwind add video-intro
|
|
146
|
+
loopwind add product-card
|
|
166
147
|
```
|
|
167
148
|
|
|
168
|
-
###
|
|
149
|
+
### 🤖 Built for AI Agents
|
|
169
150
|
|
|
170
|
-
|
|
151
|
+
Machine-readable metadata and simple CLI make it perfect for automation:
|
|
171
152
|
|
|
172
153
|
```bash
|
|
173
|
-
|
|
154
|
+
# Generate social media content programmatically
|
|
155
|
+
loopwind render og-image '{"title":"'$TITLE'","description":"'$DESC'"}'
|
|
174
156
|
```
|
|
175
157
|
|
|
176
|
-
###
|
|
158
|
+
### 🚀 Framework Agnostic
|
|
177
159
|
|
|
178
|
-
|
|
160
|
+
Works everywhere: Node.js, Bun, Deno, Python, PHP, Go. If it can run a CLI command, it can use loopwind.
|
|
179
161
|
|
|
180
|
-
|
|
181
|
-
# Image with inline props
|
|
182
|
-
loopwind render banner-hero '{"title":"Hello World"}'
|
|
162
|
+
---
|
|
183
163
|
|
|
184
|
-
|
|
185
|
-
loopwind render video-intro '{"title":"Welcome"}' --out intro.mp4
|
|
164
|
+
## Key Features
|
|
186
165
|
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
191
|
-
loopwind render banner-hero '{"title":"Hello"}' --out custom-name.png
|
|
175
|
+
---
|
|
192
176
|
|
|
193
|
-
|
|
194
|
-
loopwind render banner-hero '{"title":"Hello"}' --format jpeg
|
|
195
|
-
```
|
|
177
|
+
## Use Cases
|
|
196
178
|
|
|
197
|
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
179
|
+
**Marketing Automation**
|
|
180
|
+
- Generate personalized email headers
|
|
181
|
+
- Create dynamic social media posts
|
|
182
|
+
- Automate Open Graph images
|
|
201
183
|
|
|
202
|
-
|
|
184
|
+
**Content Creation**
|
|
185
|
+
- Produce video intros and outros
|
|
186
|
+
- Generate thumbnail variations
|
|
187
|
+
- Create branded graphics at scale
|
|
203
188
|
|
|
204
|
-
|
|
189
|
+
**Developer Tools**
|
|
190
|
+
- Build dynamic README badges
|
|
191
|
+
- Generate documentation images
|
|
192
|
+
- Create automated reports
|
|
205
193
|
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
199
|
+
---
|
|
217
200
|
|
|
218
|
-
|
|
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
|
-
|
|
210
|
+
[View Full Documentation](/getting-started) · [Browse Templates](/templates) · [See Examples](/images)
|
|
225
211
|
|
|
226
|
-
|
|
212
|
+
---
|
|
227
213
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
- [
|
|
231
|
-
- [
|
|
232
|
-
- [
|
|
233
|
-
- [
|
|
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
|
|
232
|
+
# Installing Templates
|
|
244
233
|
|
|
245
234
|
## Overview
|
|
246
|
-
|
|
247
235
|
loopwind supports installing templates from multiple sources:
|
|
248
236
|
|
|
249
|
-
1. **Official
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
Use a different registry:
|
|
240
|
+
## 1. Default templates
|
|
272
241
|
|
|
273
242
|
```bash
|
|
274
|
-
loopwind add
|
|
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": "
|
|
300
|
-
"content": "
|
|
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.
|
|
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
|
|
431
|
+
Use the `image()` helper to embed images in your templates. It supports two modes:
|
|
506
432
|
|
|
507
|
-
|
|
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
|
-
{/*
|
|
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
|
|
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
|
|
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
|
-
|
|
821
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
799
|
+
Use declarative animation classes with millisecond-based timing:
|
|
906
800
|
|
|
907
801
|
```tsx
|
|
908
|
-
export default function
|
|
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
|
-
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
-
|
|
839
|
+
### Utility-Based Animations
|
|
1153
840
|
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
### Video Background with Logo
|
|
841
|
+
Animate any transform or opacity property directly:
|
|
1157
842
|
|
|
1158
843
|
```tsx
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
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
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
859
|
+
For complex custom animations, use the `progress` prop directly:
|
|
1312
860
|
|
|
1313
861
|
```tsx
|
|
1314
|
-
export default function
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
-
<
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
{/*
|
|
1520
|
-
<div style={
|
|
1521
|
-
<div style={tw('w-32 h-32 border-8 border-blue-500 border-t-transparent rounded-full
|
|
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
|
-
|
|
1525
|
-
|
|
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
|
|
996
|
+
### Progress Bar
|
|
1538
997
|
|
|
1539
998
|
```tsx
|
|
1540
|
-
export default function ProgressVideo({ tw,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
{/*
|
|
1575
|
-
<div style={tw('text-[200px] font-black text-white
|
|
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
|
-
|
|
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
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
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
|
-
- [
|
|
1640
|
-
- [
|
|
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
|
|
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
|
|
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
|
|
1665
|
-
<h1 style={tw('text-8xl font-bold text-white ease-out
|
|
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
|
|
1670
|
-
<p style={tw('text-2xl text-white/80 mt-4 ease-out
|
|
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
|
-
##
|
|
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
|
-
|
|
1352
|
+
- `startMs` - when the animation begins (milliseconds from start)
|
|
1353
|
+
- `durationMs` - how long the animation lasts
|
|
1681
1354
|
|
|
1682
|
-
|
|
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
|
|
1692
|
-
<h1 style={tw('
|
|
1362
|
+
// Fade in from 0ms to 500ms
|
|
1363
|
+
<h1 style={tw('enter-fade-in/0/500')}>Hello</h1>
|
|
1693
1364
|
|
|
1694
|
-
// Fade
|
|
1695
|
-
<h1 style={tw('
|
|
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
|
-
| `
|
|
1701
|
-
| `
|
|
1702
|
-
| `
|
|
1703
|
-
| `
|
|
1704
|
-
| `
|
|
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('
|
|
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('
|
|
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
|
-
| `
|
|
1726
|
-
| `
|
|
1727
|
-
| `
|
|
1728
|
-
| `
|
|
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('
|
|
1402
|
+
<h1 style={tw('enter-bounce-in/0/500')}>Bouncy!</h1>
|
|
1737
1403
|
|
|
1738
1404
|
// Bounce in from below
|
|
1739
|
-
<div style={tw('
|
|
1405
|
+
<div style={tw('enter-bounce-in-up/0/600')}>Pop!</div>
|
|
1740
1406
|
```
|
|
1741
1407
|
|
|
1742
1408
|
| Class | Description |
|
|
1743
1409
|
|-------|-------------|
|
|
1744
|
-
| `
|
|
1745
|
-
| `
|
|
1746
|
-
| `
|
|
1747
|
-
| `
|
|
1748
|
-
| `
|
|
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('
|
|
1422
|
+
<div style={tw('enter-scale-in/0/500')}>Growing</div>
|
|
1757
1423
|
|
|
1758
1424
|
// Zoom in from 0%
|
|
1759
|
-
<div style={tw('
|
|
1425
|
+
<div style={tw('enter-zoom-in/0/1000')}>Zooming</div>
|
|
1760
1426
|
```
|
|
1761
1427
|
|
|
1762
1428
|
| Class | Description |
|
|
1763
1429
|
|-------|-------------|
|
|
1764
|
-
| `
|
|
1765
|
-
| `
|
|
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('
|
|
1439
|
+
<div style={tw('enter-rotate-in/0/500')}>Spinning</div>
|
|
1776
1440
|
|
|
1777
1441
|
// 3D flip on X axis
|
|
1778
|
-
<div style={tw('
|
|
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
|
-
| `
|
|
1784
|
-
| `
|
|
1785
|
-
| `
|
|
1786
|
-
| `
|
|
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: `
|
|
1492
|
+
Format: `loop-{type}/{durationMs}`
|
|
1791
1493
|
|
|
1792
|
-
Loop animations repeat every `{
|
|
1793
|
-
- `/
|
|
1794
|
-
- `/
|
|
1795
|
-
- `/
|
|
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
|
|
1799
|
-
<div style={tw('
|
|
1502
|
+
// Pulse opacity every 500ms
|
|
1503
|
+
<div style={tw('loop-fade/500')}>Pulsing</div>
|
|
1800
1504
|
|
|
1801
|
-
// Bounce every
|
|
1802
|
-
<div style={tw('
|
|
1505
|
+
// Bounce every 800ms
|
|
1506
|
+
<div style={tw('loop-bounce/800')}>Bouncing</div>
|
|
1803
1507
|
|
|
1804
|
-
// Full rotation every
|
|
1805
|
-
<div style={tw('
|
|
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
|
-
| `
|
|
1811
|
-
| `
|
|
1812
|
-
| `
|
|
1813
|
-
| `
|
|
1814
|
-
| `
|
|
1815
|
-
| `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
1857
|
-
<div style={tw('ease-out
|
|
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:
|
|
1862
|
-
<div style={tw('ease-out
|
|
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:
|
|
1867
|
-
<div style={tw('ease-out
|
|
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 *
|
|
1885
|
-
const
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1990
|
-
<div style={tw('absolute bottom-20 right-20 w-32 h-32 rounded-full bg-white/10
|
|
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
|
|
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
|
-
{/*
|
|
2008
|
-
<h1 style={tw('text-8xl font-bold text-white ease-in
|
|
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
|
-
##
|
|
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 `
|
|
1720
|
+
### Using `frame`
|
|
2030
1721
|
|
|
2031
1722
|
```tsx
|
|
2032
|
-
export default function
|
|
2033
|
-
//
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
//
|
|
2037
|
-
const
|
|
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-
|
|
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
|
|
2043
|
-
|
|
2044
|
-
transform: `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 `
|
|
1745
|
+
### Using `progress`
|
|
2054
1746
|
|
|
2055
1747
|
```tsx
|
|
2056
|
-
export default function
|
|
2057
|
-
//
|
|
2058
|
-
const
|
|
2059
|
-
|
|
2060
|
-
//
|
|
2061
|
-
const
|
|
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-
|
|
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
|
-
|
|
2069
|
-
transform: `scale(${
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2683
|
-
|
|
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
|
|
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
|
-
├──
|
|
2843
|
-
├──
|
|
2844
|
-
│
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
└──
|
|
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. **
|
|
2914
|
-
3. **Default Noto Sans** (from CDN)
|
|
2567
|
+
2. **Bundled Inter fonts** (included with CLI)
|
|
2915
2568
|
|
|
2916
|
-
This
|
|
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
|
|
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
|