loopwind 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +9 -2
  2. package/dist/cli.js +5 -3
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/render.d.ts +1 -0
  5. package/dist/commands/render.d.ts.map +1 -1
  6. package/dist/commands/render.js +1 -0
  7. package/dist/commands/render.js.map +1 -1
  8. package/dist/lib/encode-worker.d.ts +2 -0
  9. package/dist/lib/encode-worker.d.ts.map +1 -0
  10. package/dist/lib/encode-worker.js +29 -0
  11. package/dist/lib/encode-worker.js.map +1 -0
  12. package/dist/lib/mjpeg-muxer.d.ts +46 -0
  13. package/dist/lib/mjpeg-muxer.d.ts.map +1 -0
  14. package/dist/lib/mjpeg-muxer.js +513 -0
  15. package/dist/lib/mjpeg-muxer.js.map +1 -0
  16. package/dist/lib/render-core.d.ts +63 -0
  17. package/dist/lib/render-core.d.ts.map +1 -0
  18. package/dist/lib/render-core.js +65 -0
  19. package/dist/lib/render-core.js.map +1 -0
  20. package/dist/lib/renderer.d.ts.map +1 -1
  21. package/dist/lib/renderer.js +10 -7
  22. package/dist/lib/renderer.js.map +1 -1
  23. package/dist/lib/resvg-init.d.ts +15 -0
  24. package/dist/lib/resvg-init.d.ts.map +1 -0
  25. package/dist/lib/resvg-init.js +55 -0
  26. package/dist/lib/resvg-init.js.map +1 -0
  27. package/dist/lib/tailwind/colors.d.ts +8 -0
  28. package/dist/lib/tailwind/colors.d.ts.map +1 -0
  29. package/dist/lib/tailwind/colors.js +102 -0
  30. package/dist/lib/tailwind/colors.js.map +1 -0
  31. package/dist/lib/tailwind/index.d.ts +10 -0
  32. package/dist/lib/tailwind/index.d.ts.map +1 -0
  33. package/dist/lib/tailwind/index.js +9 -0
  34. package/dist/lib/tailwind/index.js.map +1 -0
  35. package/dist/lib/tailwind/resolvers.d.ts +28 -0
  36. package/dist/lib/tailwind/resolvers.d.ts.map +1 -0
  37. package/dist/lib/tailwind/resolvers.js +94 -0
  38. package/dist/lib/tailwind/resolvers.js.map +1 -0
  39. package/dist/lib/tailwind/types.d.ts +29 -0
  40. package/dist/lib/tailwind/types.d.ts.map +1 -0
  41. package/dist/lib/tailwind/types.js +8 -0
  42. package/dist/lib/tailwind/types.js.map +1 -0
  43. package/dist/lib/tailwind-config-loader.d.ts +8 -45
  44. package/dist/lib/tailwind-config-loader.d.ts.map +1 -1
  45. package/dist/lib/tailwind-config-loader.js +6 -429
  46. package/dist/lib/tailwind-config-loader.js.map +1 -1
  47. package/dist/lib/tailwind.d.ts +1 -1
  48. package/dist/lib/tailwind.d.ts.map +1 -1
  49. package/dist/lib/tailwind.js +1 -1
  50. package/dist/lib/tailwind.js.map +1 -1
  51. package/dist/lib/video-preview.d.ts.map +1 -1
  52. package/dist/lib/video-preview.js +28 -16
  53. package/dist/lib/video-preview.js.map +1 -1
  54. package/dist/lib/video-renderer.d.ts +19 -1
  55. package/dist/lib/video-renderer.d.ts.map +1 -1
  56. package/dist/lib/video-renderer.js +194 -70
  57. package/dist/lib/video-renderer.js.map +1 -1
  58. package/dist/sdk/edge/index.d.ts +91 -0
  59. package/dist/sdk/edge/index.d.ts.map +1 -0
  60. package/dist/sdk/edge/index.js +187 -0
  61. package/dist/sdk/edge/index.js.map +1 -0
  62. package/dist/sdk/workers/index.d.ts +135 -0
  63. package/dist/sdk/workers/index.d.ts.map +1 -0
  64. package/dist/sdk/workers/index.js +271 -0
  65. package/dist/sdk/workers/index.js.map +1 -0
  66. package/dist/sdk/workers/tailwind-config.d.ts +48 -0
  67. package/dist/sdk/workers/tailwind-config.d.ts.map +1 -0
  68. package/dist/sdk/workers/tailwind-config.js +187 -0
  69. package/dist/sdk/workers/tailwind-config.js.map +1 -0
  70. package/dist/sdk/workers/tailwind.d.ts +9 -0
  71. package/dist/sdk/workers/tailwind.d.ts.map +1 -0
  72. package/dist/sdk/workers/tailwind.js +8 -0
  73. package/dist/sdk/workers/tailwind.js.map +1 -0
  74. package/package.json +6 -2
  75. package/test-cloudflare-worker/README.md +64 -0
  76. package/test-cloudflare-worker/dist/README.md +1 -0
  77. package/test-cloudflare-worker/dist/index.js +23743 -0
  78. package/test-cloudflare-worker/dist/index.js.map +8 -0
  79. package/test-cloudflare-worker/package-lock.json +1773 -0
  80. package/test-cloudflare-worker/package.json +25 -0
  81. package/test-cloudflare-worker/test-sdk.mjs +75 -0
  82. package/test-cloudflare-worker/wrangler.toml +14 -0
  83. package/test-video-720p.mjs +96 -0
  84. package/test-video-breakdown.mjs +98 -0
  85. package/test-video-perf-1080.mjs +67 -0
  86. package/test-video-perf.mjs +56 -0
  87. package/test-worker-1080p.mjs +103 -0
  88. package/test-worker-viability.mjs +140 -0
  89. package/website/astro.config.mjs +4 -9
  90. package/website/dist/_astro/PlaygroundEditor.DzFavsm8.js +26 -0
  91. package/website/dist/_astro/VideoPreviewClient.BrajhYmh.js +1 -0
  92. package/website/dist/_astro/agents.CZXv4DCM.css +1 -0
  93. package/website/dist/_astro/client.BHSq4mdQ.js +33 -0
  94. package/website/dist/_astro/index.CTbGshLK.js +9 -0
  95. package/website/dist/_astro/jsx-runtime.BjG_zV1W.js +9 -0
  96. package/website/dist/_routes.json +1 -0
  97. package/website/dist/_worker.js/_@astrojs-ssr-adapter.mjs +4 -4
  98. package/website/dist/_worker.js/_astro-internal_middleware.mjs +2 -2
  99. package/website/dist/_worker.js/chunks/Logo_Cud5QvBJ.mjs +22 -0
  100. package/website/dist/_worker.js/chunks/_@astro-renderers_-YVK7NHa.mjs +15015 -0
  101. package/website/dist/_worker.js/chunks/astro/{server_Y5_QHO8v.mjs → server_CsUrSZgd.mjs} +113 -2
  102. package/website/dist/_worker.js/chunks/{astro-designed-error-pages_BNTLO-TA.mjs → astro-designed-error-pages_1ELXm5Tt.mjs} +1 -1
  103. package/website/dist/_worker.js/chunks/{index_C1UTDwYg.mjs → index_BDWR1Q-q.mjs} +2 -2
  104. package/website/dist/_worker.js/chunks/{noop-middleware_DlWGj5t5.mjs → noop-middleware_B8fH5jha.mjs} +1 -1
  105. package/website/dist/_worker.js/index.js +38 -30
  106. package/website/dist/_worker.js/manifest_Bk6136-u.mjs +98 -0
  107. package/website/dist/_worker.js/pages/_image.astro.mjs +1 -1
  108. package/website/dist/_worker.js/pages/api/playground/render.astro.mjs +25562 -0
  109. package/website/dist/_worker.js/pages/api/playground/templates.astro.mjs +92 -0
  110. package/website/dist/_worker.js/pages/api/raw-markdown/_---path_.astro.mjs +1 -1
  111. package/website/dist/_worker.js/pages/playground/_example_.astro.mjs +95 -0
  112. package/website/dist/_worker.js/pages/playground.astro.mjs +1 -0
  113. package/website/dist/_worker.js/renderers.mjs +1 -56
  114. package/website/dist/agents/index.html +4 -2
  115. package/website/dist/animation/index.html +629 -3
  116. package/website/dist/config/index.html +4 -2
  117. package/website/dist/fonts/index.html +4 -2
  118. package/website/dist/getting-started/index.html +4 -2
  119. package/website/dist/helpers/index.html +196 -10
  120. package/website/dist/images/index.html +4 -2
  121. package/website/dist/index.html +4 -3
  122. package/website/dist/llm.txt +870 -20
  123. package/website/dist/playground/index.html +6 -0
  124. package/website/dist/preview/index.html +4 -2
  125. package/website/dist/sdk/index.html +639 -127
  126. package/website/dist/sitemap.xml +12 -12
  127. package/website/dist/styling/index.html +4 -2
  128. package/website/dist/templates/index.html +4 -2
  129. package/website/dist/video/index.html +47 -12
  130. package/website/package-lock.json +11 -1
  131. package/website/package.json +3 -1
  132. package/website/wrangler.toml +9 -0
  133. package/_dsgn/templates/dashed-stroke-test/template.tsx +0 -73
  134. package/_dsgn/templates/path-follow-test/template.tsx +0 -176
  135. package/_dsgn/templates/path-simple-test/template.tsx +0 -98
  136. package/_dsgn/templates/stroke-dash-test/meta.json +0 -12
  137. package/_dsgn/templates/stroke-dash-test/template.tsx +0 -53
  138. package/website/dist/_astro/agents.Yx-L_igG.css +0 -1
  139. package/website/dist/_worker.js/manifest_CT_D-YDe.mjs +0 -98
@@ -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-20T11:24:11.500Z
4
+ Generated: 2025-12-15T21:23:12.229Z
5
5
 
6
6
  ---
7
7
 
@@ -750,8 +750,8 @@ Create animated videos programmatically using React components. Perfect for auto
750
750
  # Render a video template with inline props
751
751
  loopwind render video-intro '{"title":"Welcome!"}' --out intro.mp4
752
752
 
753
- # With custom encoding
754
- loopwind render video-intro '{"title":"Welcome!"}' --preset ultrafast
753
+ # Faster encoding with FFmpeg (requires FFmpeg installed)
754
+ loopwind render video-intro '{"title":"Welcome!"}' --ffmpeg
755
755
 
756
756
  # Or use a props file
757
757
  loopwind render video-intro props.json --out intro.mp4
@@ -918,29 +918,52 @@ export const meta = {
918
918
 
919
919
  ## Encoding Options
920
920
 
921
- ### Default (Balanced)
921
+ loopwind supports two encoding backends:
922
+
923
+ ### WASM Encoder (Default)
922
924
 
923
925
  ```bash
924
926
  loopwind render video-intro '{"title":"Welcome"}'
925
927
  ```
926
928
 
927
- Uses H.264 codec with good quality/size balance.
929
+ Pure JavaScript/WASM H.264 encoder. **Serverless-compatible** - works on Vercel, Cloudflare Workers, AWS Lambda, and anywhere JavaScript runs.
928
930
 
929
- ### Fast Encoding
931
+ ### FFmpeg Encoder (Faster)
930
932
 
931
933
  ```bash
932
- loopwind render video-intro '{"title":"Welcome"}' --preset ultrafast
934
+ loopwind render video-intro '{"title":"Welcome"}' --ffmpeg
933
935
  ```
934
936
 
935
- Faster encoding, slightly larger files. Good for previews.
937
+ Uses FFmpeg for **2x faster encoding** and smaller file sizes. Requires FFmpeg installed on your system.
938
+
939
+ **Install FFmpeg:**
940
+ ```bash
941
+ # macOS
942
+ brew install ffmpeg
943
+
944
+ # Ubuntu/Debian
945
+ sudo apt install ffmpeg
946
+
947
+ # Windows
948
+ winget install ffmpeg
949
+ ```
936
950
 
937
- ### High Quality
951
+ ### Quality Settings
938
952
 
939
953
  ```bash
940
- loopwind render video-intro '{"title":"Welcome"}' --preset slow
954
+ # Higher quality (lower CRF = better quality, default: 23)
955
+ loopwind render video-intro '{"title":"Welcome"}' --crf 18
956
+
957
+ # Lower quality, smaller files
958
+ loopwind render video-intro '{"title":"Welcome"}' --crf 28
941
959
  ```
942
960
 
943
- Better compression, slower encoding. Good for final output.
961
+ ### When to Use Each
962
+
963
+ | Encoder | Use Case |
964
+ |---------|----------|
965
+ | **WASM (default)** | Serverless, CI/CD, no dependencies needed |
966
+ | **FFmpeg (`--ffmpeg`)** | Local development, faster iteration, smaller files |
944
967
 
945
968
  ## Performance
946
969
 
@@ -1551,6 +1574,122 @@ Add an easing class **before** the animation class to control the timing curve.
1551
1574
  | `ease-out-quart` | Very strong fast start | Punchy entrances |
1552
1575
  | `ease-in-out-quart` | Very strong both ends | Maximum drama |
1553
1576
 
1577
+ ### Per-Animation-Type Easing
1578
+
1579
+ You can apply **different easing functions** to enter, exit, and loop animations on the same element using `enter-ease-*`, `exit-ease-*`, and `loop-ease-*` classes.
1580
+
1581
+ ```tsx
1582
+ // Different easing for enter and exit
1583
+ <h1 style={tw('enter-ease-out-cubic enter-fade-in/0/500 exit-ease-in exit-fade-out/2500/500')}>
1584
+ Smooth entrance, sharp exit
1585
+ </h1>
1586
+
1587
+ // Loop with linear easing, enter with bounce
1588
+ <div style={tw('enter-ease-out enter-bounce-in/0/400 loop-ease-linear loop-fade/1000')}>
1589
+ Bouncy entrance, linear loop
1590
+ </div>
1591
+
1592
+ // Default easing still works (applies to all animations)
1593
+ <div style={tw('ease-in-out enter-fade-in/0/500 exit-fade-out/2500/500')}>
1594
+ Same easing for both
1595
+ </div>
1596
+
1597
+ // Mix default with specific overrides
1598
+ <div style={tw('ease-out enter-fade-in/0/500 exit-ease-in-cubic exit-fade-out/2500/500')}>
1599
+ Default ease-out for enter, cubic-in for exit
1600
+ </div>
1601
+ ```
1602
+
1603
+ **How it works:**
1604
+
1605
+ 1. **Default easing** (`ease-*`) applies to ALL animations if no specific override is set
1606
+ 2. **Specific easing** (`enter-ease-*`, `exit-ease-*`, `loop-ease-*`) overrides the default for that animation type
1607
+ 3. If both are present, specific easing takes priority for its animation type
1608
+
1609
+ **Available easing classes:**
1610
+
1611
+ | Default (all animations) | Enter only | Exit only | Loop only |
1612
+ |--------------------------|------------|-----------|-----------|
1613
+ | `ease-in` | `enter-ease-in` | `exit-ease-in` | `loop-ease-in` |
1614
+ | `ease-out` | `enter-ease-out` | `exit-ease-out` | `loop-ease-out` |
1615
+ | `ease-in-out` | `enter-ease-in-out` | `exit-ease-in-out` | `loop-ease-in-out` |
1616
+ | `ease-in-cubic` | `enter-ease-in-cubic` | `exit-ease-in-cubic` | `loop-ease-in-cubic` |
1617
+ | `ease-out-cubic` | `enter-ease-out-cubic` | `exit-ease-out-cubic` | `loop-ease-out-cubic` |
1618
+ | `ease-in-out-cubic` | `enter-ease-in-out-cubic` | `exit-ease-in-out-cubic` | `loop-ease-in-out-cubic` |
1619
+ | `ease-in-quart` | `enter-ease-in-quart` | `exit-ease-in-quart` | `loop-ease-in-quart` |
1620
+ | `ease-out-quart` | `enter-ease-out-quart` | `exit-ease-out-quart` | `loop-ease-out-quart` |
1621
+ | `ease-in-out-quart` | `enter-ease-in-out-quart` | `exit-ease-in-out-quart` | `loop-ease-in-out-quart` |
1622
+ | `linear` | `enter-ease-linear` | `exit-ease-linear` | `loop-ease-linear` |
1623
+ | `ease-spring` | `enter-ease-spring` | `exit-ease-spring` | `loop-ease-spring` |
1624
+
1625
+ ### Spring Easing
1626
+
1627
+ Spring easing creates natural, physics-based bouncy animations. Use the built-in `ease-spring` easing or create custom springs with configurable parameters.
1628
+
1629
+ ```tsx
1630
+ // Default spring easing
1631
+ <h1 style={tw('ease-spring enter-bounce-in/0/500')}>Bouncy spring!</h1>
1632
+
1633
+ // Per-animation-type spring
1634
+ <div style={tw('enter-ease-spring enter-fade-in/0/500 exit-ease-out exit-fade-out/2500/500')}>
1635
+ Spring entrance, smooth exit
1636
+ </div>
1637
+
1638
+ // Custom spring with parameters: ease-spring/mass/stiffness/damping
1639
+ <h1 style={tw('ease-spring/1/100/10 enter-scale-in/0/800')}>
1640
+ Custom spring (mass=1, stiffness=100, damping=10)
1641
+ </h1>
1642
+
1643
+ // More bouncy spring (lower damping)
1644
+ <div style={tw('ease-spring/1/170/8 enter-bounce-in-up/0/600')}>
1645
+ Extra bouncy!
1646
+ </div>
1647
+
1648
+ // Stiffer spring (higher stiffness, faster)
1649
+ <div style={tw('ease-spring/1/200/12 enter-fade-in-up/0/400')}>
1650
+ Snappy spring
1651
+ </div>
1652
+
1653
+ // Per-animation-type custom springs
1654
+ <div style={tw('enter-ease-spring/1/150/10 enter-fade-in/0/500 exit-ease-spring/1/100/15 exit-fade-out/2500/500')}>
1655
+ Different springs for enter and exit
1656
+ </div>
1657
+ ```
1658
+
1659
+ **Spring parameters:**
1660
+
1661
+ | Parameter | Description | Effect when increased | Default |
1662
+ |-----------|-------------|----------------------|---------|
1663
+ | **mass** | Mass of the spring | Slower, more inertia | 1 |
1664
+ | **stiffness** | Spring stiffness | Faster, snappier | 100 |
1665
+ | **damping** | Damping coefficient | Less bounce, smoother | 10 |
1666
+
1667
+ **Common spring presets:**
1668
+
1669
+ ```tsx
1670
+ // Gentle bounce (default)
1671
+ ease-spring/1/100/10
1672
+
1673
+ // Extra bouncy
1674
+ ease-spring/1/170/8
1675
+
1676
+ // Snappy (no bounce)
1677
+ ease-spring/1/200/15
1678
+
1679
+ // Slow and bouncy
1680
+ ease-spring/2/100/8
1681
+
1682
+ // Fast and tight
1683
+ ease-spring/0.5/300/20
1684
+ ```
1685
+
1686
+ **How spring works:**
1687
+
1688
+ 1. **Default `ease-spring`** - Uses a pre-calculated spring curve optimized for most use cases
1689
+ 2. **Custom `ease-spring/mass/stiffness/damping`** - Generates a physics-based spring curve using the [damped harmonic oscillator](https://www.kvin.me/css-springs) formula
1690
+ 3. The spring automatically calculates its ideal duration to reach the final state
1691
+ 4. Works with all animation types: `ease-spring`, `enter-ease-spring`, `exit-ease-spring`, `loop-ease-spring`
1692
+
1554
1693
  ## Combining Enter and Exit
1555
1694
 
1556
1695
  You can use both enter and exit animations on the same element:
@@ -1793,7 +1932,7 @@ export default function CustomEasing({ tw, progress, title }) {
1793
1932
  ### When to Use Programmatic Animations
1794
1933
 
1795
1934
  Use `progress`/`frame` instead of animation classes when you need:
1796
- - **Custom easing functions** (elastic, spring, bounce with specific curves)
1935
+ - **Custom easing functions** (elastic, bounce with specific curves beyond built-in ease-spring)
1797
1936
  - **Color cycling or gradients** based on time
1798
1937
  - **Mathematical animations** (sine waves, spirals, etc.)
1799
1938
  - **Complex multi-property animations** that need precise coordination
@@ -1801,6 +1940,498 @@ Use `progress`/`frame` instead of animation classes when you need:
1801
1940
 
1802
1941
  For everything else, prefer animation classes - they're simpler and more maintainable.
1803
1942
 
1943
+ ### Animating Along Paths
1944
+
1945
+ Animate elements along SVG paths with proper rotation using built-in **path helpers**:
1946
+
1947
+ ```tsx
1948
+ export default function PathFollowing({ tw, progress, path }) {
1949
+ // Follow a quadratic Bezier curve - one line!
1950
+ const rocket = path.followQuadratic(
1951
+ { x: 200, y: 400 }, // Start point
1952
+ { x: 960, y: 150 }, // Control point
1953
+ { x: 1720, y: 400 }, // End point
1954
+ progress
1955
+ );
1956
+
1957
+ return (
1958
+ <div style={{ display: 'flex', ...tw('relative w-full h-full bg-gray-900') }}>
1959
+ {/* Draw the path (optional) */}
1960
+ <svg width="1920" height="1080" style={{ position: 'absolute' }}>
1961
+ <path
1962
+ d="M 200 400 Q 960 150 1720 400"
1963
+ stroke="rgba(255,255,255,0.2)"
1964
+ strokeWidth={2}
1965
+ fill="none"
1966
+ />
1967
+ </svg>
1968
+
1969
+ {/* Element following the path */}
1970
+ <div
1971
+ style={{
1972
+ position: "absolute",
1973
+ left: rocket.x,
1974
+ top: rocket.y,
1975
+ transform: `translate(-50%, -50%) rotate(${rocket.angle}deg)`,
1976
+ fontSize: '48px'
1977
+ }}
1978
+ >
1979
+ 🚀
1980
+ </div>
1981
+ </div>
1982
+ );
1983
+ }
1984
+ ```
1985
+
1986
+ ### Text Path Animations
1987
+
1988
+ Combine `textPath` helpers with animation classes to create animated text along curves:
1989
+
1990
+ **Rotating text around a circle:**
1991
+ ```tsx
1992
+ export default function RotatingCircleText({ tw, textPath, progress }) {
1993
+ return (
1994
+ <div style={tw('relative w-full h-full bg-black')}>
1995
+ {/* Text rotates around circle using progress */}
1996
+ {textPath.onCircle(
1997
+ "SPINNING TEXT • AROUND • ",
1998
+ 960, // center x
1999
+ 540, // center y
2000
+ 400, // radius
2001
+ progress, // rotation offset (0-1 animates full rotation)
2002
+ {
2003
+ fontSize: "3xl",
2004
+ fontWeight: "bold",
2005
+ color: "yellow-300"
2006
+ }
2007
+ )}
2008
+ </div>
2009
+ );
2010
+ }
2011
+ ```
2012
+
2013
+ **Animated text reveal along a path:**
2014
+ ```tsx
2015
+ export default function PathTextReveal({ tw, textPath, progress }) {
2016
+ // Create custom path follower that animates position
2017
+ const pathFollower = (t) => {
2018
+ // Only show characters up to current progress
2019
+ const visibleProgress = progress * 1.5; // Extend range for smooth reveal
2020
+ const opacity = t < visibleProgress ? 1 : 0;
2021
+
2022
+ // Follow quadratic curve
2023
+ const pos = {
2024
+ x: (1 - t) * (1 - t) * 200 + 2 * (1 - t) * t * 960 + t * t * 1720,
2025
+ y: (1 - t) * (1 - t) * 400 + 2 * (1 - t) * t * 150 + t * t * 400,
2026
+ angle: 0
2027
+ };
2028
+
2029
+ return { ...pos, opacity };
2030
+ };
2031
+
2032
+ return (
2033
+ <div style={tw('relative w-full h-full bg-gray-900')}>
2034
+ {textPath.onPath(
2035
+ "REVEALING TEXT",
2036
+ pathFollower,
2037
+ {
2038
+ fontSize: "4xl",
2039
+ fontWeight: "bold",
2040
+ color: "blue-300"
2041
+ }
2042
+ ).map((char, i) => (
2043
+ <div key={i} style={{ ...char.props.style, opacity: char.props.style.opacity || 1 }}>
2044
+ {char}
2045
+ </div>
2046
+ ))}
2047
+ </div>
2048
+ );
2049
+ }
2050
+ ```
2051
+
2052
+ **Staggered character entrance:**
2053
+ ```tsx
2054
+ export default function StaggeredCircleText({ tw, textPath }) {
2055
+ const text = "HELLO WORLD";
2056
+
2057
+ return (
2058
+ <div style={tw('relative w-full h-full bg-slate-900')}>
2059
+ {textPath.onCircle(
2060
+ text,
2061
+ 960, 540, 400, 0,
2062
+ { fontSize: "4xl", fontWeight: "bold", color: "white" }
2063
+ ).map((char, i) => {
2064
+ // Stagger fade-in: each character starts 50ms later
2065
+ const staggerDelay = i * 50;
2066
+ return (
2067
+ <div
2068
+ key={i}
2069
+ style={{
2070
+ ...char.props.style,
2071
+ ...tw(`enter-fade-in/${staggerDelay}/300 enter-scale-100/${staggerDelay}/300`)
2072
+ }}
2073
+ >
2074
+ {char.props.children}
2075
+ </div>
2076
+ );
2077
+ })}
2078
+ </div>
2079
+ );
2080
+ }
2081
+ ```
2082
+
2083
+ **Text with bounce entrance along arc:**
2084
+ ```tsx
2085
+ export default function BouncyArcText({ tw, textPath }) {
2086
+ return (
2087
+ <div style={tw('relative w-full h-full bg-gradient-to-br from-purple-600 to-blue-500')}>
2088
+ {/* Draw the arc path */}
2089
+ <svg width="1920" height="1080" style={{ position: 'absolute' }}>
2090
+ <path
2091
+ d="M 300 900 A 600 600 0 0 1 1620 900"
2092
+ stroke="rgba(255,255,255,0.2)"
2093
+ strokeWidth={2}
2094
+ fill="none"
2095
+ strokeDasharray="5 5"
2096
+ />
2097
+ </svg>
2098
+
2099
+ {/* Text follows arc with staggered bounce */}
2100
+ {textPath.onArc(
2101
+ "BOUNCING ON ARC",
2102
+ 960, // cx
2103
+ 300, // cy
2104
+ 600, // radius
2105
+ 180, // start angle
2106
+ 360, // end angle
2107
+ { fontSize: "3xl", fontWeight: "bold", color: "white" }
2108
+ ).map((char, i) => (
2109
+ <div
2110
+ key={i}
2111
+ style={{
2112
+ ...char.props.style,
2113
+ ...tw(`ease-out enter-bounce-in-up/${i * 80}/500`)
2114
+ }}
2115
+ >
2116
+ {char.props.children}
2117
+ </div>
2118
+ ))}
2119
+ </div>
2120
+ );
2121
+ }
2122
+ ```
2123
+
2124
+ **Loop animation with text on curve:**
2125
+ ```tsx
2126
+ export default function LoopingCurveText({ tw, textPath, frame }) {
2127
+ // Calculate wave effect using frame
2128
+ const waveOffset = Math.sin(frame / 30 * Math.PI * 2) * 0.1;
2129
+
2130
+ return (
2131
+ <div style={tw('relative w-full h-full bg-black')}>
2132
+ {textPath.onQuadratic(
2133
+ "WAVY TEXT",
2134
+ { x: 200, y: 400 },
2135
+ { x: 960, y: 150 },
2136
+ { x: 1720, y: 400 },
2137
+ { fontSize: "4xl", fontWeight: "bold", color: "pink-300" }
2138
+ ).map((char, i) => (
2139
+ <div
2140
+ key={i}
2141
+ style={{
2142
+ ...char.props.style,
2143
+ transform: `${char.props.style.transform} translateY(${Math.sin((i + frame) / 5) * 10}px)`
2144
+ }}
2145
+ >
2146
+ {char.props.children}
2147
+ </div>
2148
+ ))}
2149
+ </div>
2150
+ );
2151
+ }
2152
+ ```
2153
+
2154
+ **Tips for animating text paths:**
2155
+ 1. **Use `progress` for smooth rotation** on circles and arcs
2156
+ 2. **Map over returned characters** to apply individual animations
2157
+ 3. **Combine with animation classes** like `enter-fade-in`, `enter-bounce-in`, etc.
2158
+ 4. **Stagger character animations** by calculating delays: `i * delayMs`
2159
+ 5. **Use `frame` for continuous effects** like waves or pulsing
2160
+ 6. **Preserve the original transform** when adding animations: `transform: '${char.props.style.transform} ...'`
2161
+
2162
+ **Common path types:**
2163
+
2164
+ **Quadratic Bezier** (Q command):
2165
+ ```tsx
2166
+ // Position: (1-t)²·P0 + 2(1-t)t·P1 + t²·P2
2167
+ function pointOnQuadraticBezier(p0, p1, p2, t) {
2168
+ const x = (1 - t) * (1 - t) * p0.x + 2 * (1 - t) * t * p1.x + t * t * p2.x;
2169
+ const y = (1 - t) * (1 - t) * p0.y + 2 * (1 - t) * t * p1.y + t * t * p2.y;
2170
+ return { x, y };
2171
+ }
2172
+
2173
+ // Tangent angle
2174
+ function angleOnQuadraticBezier(p0, p1, p2, t) {
2175
+ const dx = 2 * (1 - t) * (p1.x - p0.x) + 2 * t * (p2.x - p1.x);
2176
+ const dy = 2 * (1 - t) * (p1.y - p0.y) + 2 * t * (p2.y - p1.y);
2177
+ return Math.atan2(dy, dx) * (180 / Math.PI);
2178
+ }
2179
+ ```
2180
+
2181
+ **Cubic Bezier** (C command):
2182
+ ```tsx
2183
+ // Position: (1-t)³·P0 + 3(1-t)²t·P1 + 3(1-t)t²·P2 + t³·P3
2184
+ function pointOnCubicBezier(p0, p1, p2, p3, t) {
2185
+ const mt = 1 - t;
2186
+ const mt2 = mt * mt;
2187
+ const mt3 = mt2 * mt;
2188
+ const t2 = t * t;
2189
+ const t3 = t2 * t;
2190
+ const x = mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x;
2191
+ const y = mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y;
2192
+ return { x, y };
2193
+ }
2194
+
2195
+ // Tangent angle
2196
+ function angleOnCubicBezier(p0, p1, p2, p3, t) {
2197
+ const mt = 1 - t;
2198
+ const mt2 = mt * mt;
2199
+ const t2 = t * t;
2200
+ const dx = -3 * mt2 * p0.x + 3 * mt2 * p1.x - 6 * mt * t * p1.x - 3 * t2 * p2.x + 6 * mt * t * p2.x + 3 * t2 * p3.x;
2201
+ const dy = -3 * mt2 * p0.y + 3 * mt2 * p1.y - 6 * mt * t * p1.y - 3 * t2 * p2.y + 6 * mt * t * p2.y + 3 * t2 * p3.y;
2202
+ return Math.atan2(dy, dx) * (180 / Math.PI);
2203
+ }
2204
+ ```
2205
+
2206
+ **Circle**:
2207
+ ```tsx
2208
+ function pointOnCircle(cx, cy, radius, angleRadians) {
2209
+ return {
2210
+ x: cx + radius * Math.cos(angleRadians),
2211
+ y: cy + radius * Math.sin(angleRadians)
2212
+ };
2213
+ }
2214
+
2215
+ // Usage
2216
+ const angleRadians = progress * Math.PI * 2;
2217
+ const pos = pointOnCircle(300, 300, 100, angleRadians);
2218
+ const tangentAngle = (angleRadians * 180 / Math.PI) + 90; // Tangent is perpendicular
2219
+ ```
2220
+
2221
+ **Tips:**
2222
+ - Use `progress` (0-1) for smooth animation
2223
+ - The `translate(-50%, -50%)` centers the element on the path
2224
+ - Combine rotation with the translate: `translate(-50%, -50%) rotate(${angle}deg)`
2225
+ - For text following a path, you can animate individual characters at different progress values
2226
+
2227
+ ## SVG Stroke Animations
2228
+
2229
+ Animate SVG path strokes with the **stroke-dash** classes, perfect for drawing or erasing line art, icons, and illustrations.
2230
+
2231
+ ### How It Works
2232
+
2233
+ SVG stroke animations use `strokeDasharray` and `strokeDashoffset` CSS properties to create drawing effects:
2234
+
2235
+ 1. **Enter animations** - Draw the stroke from start to finish
2236
+ 2. **Exit animations** - Erase the stroke from finish to start
2237
+ 3. **Loop animations** - Continuously draw and erase
2238
+
2239
+ ### Format
2240
+
2241
+ All stroke-dash animations require the **path length** in brackets:
2242
+
2243
+ ```tsx
2244
+ enter-stroke-dash-[length]/start/duration
2245
+ exit-stroke-dash-[length]/start/duration
2246
+ loop-stroke-dash-[length]/duration
2247
+ ```
2248
+
2249
+ ### Basic Examples
2250
+
2251
+ ```tsx
2252
+ export default function SVGAnimation({ tw }) {
2253
+ return (
2254
+ <svg width="400" height="200" viewBox="0 0 400 200">
2255
+ {/* Draw a curve over 1 second */}
2256
+ <path
2257
+ d="M10 150 Q 95 10 180 150"
2258
+ stroke="black"
2259
+ strokeWidth={4}
2260
+ fill="none"
2261
+ style={tw('enter-stroke-dash-[300]/0/1000')}
2262
+ />
2263
+ </svg>
2264
+ );
2265
+ }
2266
+ ```
2267
+
2268
+ ### Enter Animations (Drawing)
2269
+
2270
+ Draw strokes from 0% to 100%:
2271
+
2272
+ ```tsx
2273
+ // Draw a 300px path over 1 second
2274
+ <path style={tw('enter-stroke-dash-[300]/0/1000')} />
2275
+
2276
+ // Draw with spring easing
2277
+ <path style={tw('ease-spring enter-stroke-dash-[500]/0/1500')} />
2278
+
2279
+ // Stagger multiple paths
2280
+ <path style={tw('enter-stroke-dash-[200]/0/600')} />
2281
+ <path style={tw('enter-stroke-dash-[200]/200/600')} />
2282
+ <path style={tw('enter-stroke-dash-[200]/400/600')} />
2283
+ ```
2284
+
2285
+ ### Exit Animations (Erasing)
2286
+
2287
+ Erase strokes from 100% to 0%:
2288
+
2289
+ ```tsx
2290
+ // Erase starting at 2000ms, lasting 500ms
2291
+ <path style={tw('exit-stroke-dash-[300]/2000/500')} />
2292
+
2293
+ // Draw then erase the same path
2294
+ <path style={tw('enter-stroke-dash-[400]/0/800 exit-stroke-dash-[400]/2200/800')} />
2295
+ ```
2296
+
2297
+ ### Loop Animations
2298
+
2299
+ Continuously draw and erase:
2300
+
2301
+ ```tsx
2302
+ // Loop every 2 seconds (draws in first half, erases in second half)
2303
+ <path style={tw('loop-stroke-dash-[300]/2000')} />
2304
+
2305
+ // Faster loop
2306
+ <path style={tw('loop-stroke-dash-[200]/1000')} />
2307
+ ```
2308
+
2309
+ ### Getting Path Length
2310
+
2311
+ To find the path length for your SVG:
2312
+
2313
+ ```tsx
2314
+ // In browser console or component:
2315
+ const path = document.querySelector('path');
2316
+ const length = path.getTotalLength();
2317
+ console.log(length); // e.g., 347.89
2318
+ ```
2319
+
2320
+ Then use that value:
2321
+
2322
+ ```tsx
2323
+ <path style={tw('enter-stroke-dash-[347.89]/0/1000')} />
2324
+ ```
2325
+
2326
+ ### Complete Example
2327
+
2328
+ ```tsx
2329
+ export default function DrawingEffect({ tw }) {
2330
+ return (
2331
+ <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
2332
+ <svg width="600" height="400" viewBox="0 0 600 400">
2333
+ {/* Checkmark icon drawn in sequence */}
2334
+ <path
2335
+ d="M100 200 L 200 300 L 400 100"
2336
+ stroke="#10b981"
2337
+ strokeWidth={8}
2338
+ fill="none"
2339
+ strokeLinecap="round"
2340
+ strokeLinejoin="round"
2341
+ style={tw('ease-out enter-stroke-dash-[600]/0/1200')}
2342
+ />
2343
+
2344
+ {/* Circle drawn after checkmark */}
2345
+ <circle
2346
+ cx="250"
2347
+ cy="200"
2348
+ r="150"
2349
+ stroke="#10b981"
2350
+ strokeWidth={6}
2351
+ fill="none"
2352
+ style={tw('ease-out enter-stroke-dash-[942]/1000/1000')}
2353
+ />
2354
+ </svg>
2355
+ </div>
2356
+ );
2357
+ }
2358
+ ```
2359
+
2360
+ ### Combining with Other Animations
2361
+
2362
+ Stroke animations work alongside other animation classes:
2363
+
2364
+ ```tsx
2365
+ // Fade in while drawing
2366
+ <path style={tw('enter-stroke-dash-[300]/0/1000 enter-fade-in/0/1000')} />
2367
+
2368
+ // Draw with pulsing color
2369
+ <svg>
2370
+ <path
2371
+ stroke="url(#gradient)"
2372
+ style={tw('enter-stroke-dash-[500]/0/1500')}
2373
+ />
2374
+ <defs>
2375
+ <linearGradient id="gradient">
2376
+ <stop offset="0%" stopColor="#8b5cf6" />
2377
+ <stop offset="100%" stopColor="#ec4899" />
2378
+ </linearGradient>
2379
+ </defs>
2380
+ </svg>
2381
+ ```
2382
+
2383
+ ### Animated Dashed Strokes (Marching Ants)
2384
+
2385
+ For **marching ants** or **animated dashed patterns**, use `frame` or `progress` directly instead of animation classes:
2386
+
2387
+ ```tsx
2388
+ export default function MarchingAnts({ tw, frame }) {
2389
+ // Calculate animated offset (loops every 30 frames)
2390
+ const dashOffset = -(frame % 30) * 2;
2391
+
2392
+ return (
2393
+ <div style={tw('flex items-center justify-center w-full h-full bg-gray-900')}>
2394
+ <svg width="600" height="400" viewBox="0 0 600 400">
2395
+ {/* Marching ants border */}
2396
+ <rect
2397
+ x="50"
2398
+ y="50"
2399
+ width="500"
2400
+ height="300"
2401
+ fill="none"
2402
+ stroke="#3b82f6"
2403
+ strokeWidth={3}
2404
+ strokeDasharray="10 5"
2405
+ strokeDashoffset={dashOffset}
2406
+ />
2407
+
2408
+ {/* Animated circle with different speed */}
2409
+ <circle
2410
+ cx="300"
2411
+ cy="200"
2412
+ r="80"
2413
+ fill="none"
2414
+ stroke="#10b981"
2415
+ strokeWidth={4}
2416
+ strokeDasharray="15 8"
2417
+ strokeDashoffset={dashOffset * 1.5}
2418
+ />
2419
+ </svg>
2420
+ </div>
2421
+ );
2422
+ }
2423
+ ```
2424
+
2425
+ **Tips:**
2426
+ - `strokeDasharray="10 5"` - 10px dash, 5px gap
2427
+ - `strokeDashoffset={dashOffset}` - animates the pattern position
2428
+ - Negative offset moves forward, positive moves backward
2429
+ - Different speeds: multiply by different values (e.g., `dashOffset * 2`)
2430
+
2431
+ This technique is different from `stroke-dash` classes:
2432
+ - **`stroke-dash` classes** - Draw/erase the stroke (reveal animation)
2433
+ - **Marching ants** - Move a dashed pattern along the stroke
2434
+
1804
2435
  ## Performance Tips
1805
2436
 
1806
2437
  1. **Use Tailwind classes** when possible - they're optimized for the renderer
@@ -1986,27 +2617,246 @@ export default function BrandedTemplate({ tw, config, title }) {
1986
2617
 
1987
2618
  This allows templates to adapt to user preferences and brand guidelines.
1988
2619
 
2620
+ ## Text on Path
2621
+
2622
+ Render text along curves, circles, and custom paths with automatic character positioning and rotation.
2623
+
2624
+ ### Usage
2625
+
2626
+ ```tsx
2627
+ export default function CircleText({ tw, textPath, message }) {
2628
+ return (
2629
+ <div style={tw('relative w-full h-full bg-slate-900')}>
2630
+ {textPath.onCircle(
2631
+ message,
2632
+ 960, // center x
2633
+ 540, // center y
2634
+ 400, // radius
2635
+ 0, // rotation offset (0-1)
2636
+ {
2637
+ fontSize: "4xl",
2638
+ fontWeight: "bold",
2639
+ color: "white",
2640
+ letterSpacing: 0.05
2641
+ }
2642
+ )}
2643
+ </div>
2644
+ );
2645
+ }
2646
+ ```
2647
+
2648
+ ### Available Functions
2649
+
2650
+ All `textPath` functions return an array of positioned character elements:
2651
+
2652
+ **`textPath.onCircle(text, cx, cy, radius, offset, options?)`**
2653
+ ```tsx
2654
+ // Text around a circle
2655
+ textPath.onCircle("HELLO WORLD", 960, 540, 400, 0, {
2656
+ fontSize: "4xl",
2657
+ color: "white"
2658
+ })
2659
+ ```
2660
+
2661
+ **`textPath.onPath(text, pathFollower, options?)`**
2662
+ ```tsx
2663
+ // Text along any custom path
2664
+ textPath.onPath("CUSTOM PATH", (t) => ({
2665
+ x: 100 + t * 800,
2666
+ y: 200 + Math.sin(t * Math.PI) * 100,
2667
+ angle: Math.cos(t * Math.PI) * 20
2668
+ }), {
2669
+ fontSize: "2xl",
2670
+ fontWeight: "semibold"
2671
+ })
2672
+ ```
2673
+
2674
+ **`textPath.onQuadratic(text, p0, p1, p2, options?)`**
2675
+ ```tsx
2676
+ // Text along a quadratic Bezier curve
2677
+ textPath.onQuadratic(
2678
+ "CURVED TEXT",
2679
+ { x: 200, y: 400 }, // start
2680
+ { x: 960, y: 100 }, // control point
2681
+ { x: 1720, y: 400 }, // end
2682
+ { fontSize: "3xl", color: "blue-300" }
2683
+ )
2684
+ ```
2685
+
2686
+ **`textPath.onCubic(text, p0, p1, p2, p3, options?)`**
2687
+ ```tsx
2688
+ // Text along a cubic Bezier curve
2689
+ textPath.onCubic(
2690
+ "S-CURVE",
2691
+ { x: 200, y: 600 }, // start
2692
+ { x: 600, y: 400 }, // control 1
2693
+ { x: 1320, y: 800 }, // control 2
2694
+ { x: 1720, y: 600 }, // end
2695
+ { fontSize: "3xl", color: "purple-300" }
2696
+ )
2697
+ ```
2698
+
2699
+ **`textPath.onArc(text, cx, cy, radius, startAngle, endAngle, options?)`**
2700
+ ```tsx
2701
+ // Text along a circular arc
2702
+ textPath.onArc(
2703
+ "ARC TEXT",
2704
+ 960, // center x
2705
+ 540, // center y
2706
+ 400, // radius
2707
+ 0, // start angle (degrees)
2708
+ 180, // end angle (degrees)
2709
+ { fontSize: "2xl", color: "pink-300" }
2710
+ )
2711
+ ```
2712
+
2713
+ ### Options
2714
+
2715
+ All `textPath` functions accept an optional `options` object:
2716
+
2717
+ ```typescript
2718
+ {
2719
+ fontSize?: string; // Tailwind size: "xl", "2xl", "4xl", etc.
2720
+ fontWeight?: string; // Tailwind weight: "bold", "semibold", etc.
2721
+ color?: string; // Tailwind color: "white", "blue-500", etc.
2722
+ letterSpacing?: number; // Space between characters (0-1, default: 0)
2723
+ style?: any; // Additional inline styles
2724
+ }
2725
+ ```
2726
+
2727
+ ### Examples
2728
+
2729
+ **Animated rotating text:**
2730
+ ```tsx
2731
+ export default function RotatingText({ tw, textPath, progress }) {
2732
+ return (
2733
+ <div style={tw('relative w-full h-full bg-black')}>
2734
+ {textPath.onCircle(
2735
+ "SPINNING • TEXT • ",
2736
+ 960, 540, 400,
2737
+ progress, // Rotate based on video progress
2738
+ { fontSize: "3xl", color: "yellow-300" }
2739
+ )}
2740
+ </div>
2741
+ );
2742
+ }
2743
+ ```
2744
+
2745
+ **Multiple text paths:**
2746
+ ```tsx
2747
+ export default function MultiPath({ tw, textPath }) {
2748
+ return (
2749
+ <div style={tw('relative w-full h-full bg-gradient-to-br from-slate-900 to-slate-700')}>
2750
+ {/* Text on outer circle */}
2751
+ {textPath.onCircle(
2752
+ "OUTER RING",
2753
+ 960, 540, 500, 0,
2754
+ { fontSize: "5xl", fontWeight: "bold", color: "white" }
2755
+ )}
2756
+
2757
+ {/* Text on inner circle */}
2758
+ {textPath.onCircle(
2759
+ "inner ring",
2760
+ 960, 540, 300, 0.5, // offset by 50% for rotation
2761
+ { fontSize: "2xl", color: "white/60" }
2762
+ )}
2763
+ </div>
2764
+ );
2765
+ }
2766
+ ```
2767
+
2768
+ **Text following a drawn path:**
2769
+ ```tsx
2770
+ export default function PathText({ tw, textPath }) {
2771
+ return (
2772
+ <div style={tw('relative w-full h-full bg-gray-900')}>
2773
+ {/* Draw the path */}
2774
+ <svg width="1920" height="1080" style={{ position: 'absolute' }}>
2775
+ <path
2776
+ d="M 200 400 Q 960 150 1720 400"
2777
+ stroke="rgba(255,255,255,0.2)"
2778
+ strokeWidth={2}
2779
+ fill="none"
2780
+ />
2781
+ </svg>
2782
+
2783
+ {/* Text following the path */}
2784
+ {textPath.onQuadratic(
2785
+ "FOLLOWING THE CURVE",
2786
+ { x: 200, y: 400 },
2787
+ { x: 960, y: 150 },
2788
+ { x: 1720, y: 400 },
2789
+ { fontSize: "3xl", fontWeight: "bold", color: "blue-300" }
2790
+ )}
2791
+ </div>
2792
+ );
2793
+ }
2794
+ ```
2795
+
2796
+ For animated text paths, see [Text Path Animations](/animation#text-path-animations).
2797
+
2798
+ ## Reserved Prop Names
2799
+
2800
+ The following prop names are **reserved** and cannot be used in your template's `meta.props`:
2801
+
2802
+ - `tw`, `qr`, `image`, `template` - Core helpers
2803
+ - `path`, `textPath` - Path and text helpers
2804
+ - `config`, `frame`, `progress` - System props
2805
+
2806
+ **Why?** These names are used for loopwind's built-in helpers. Using them as prop names would cause conflicts.
2807
+
2808
+ **Example:**
2809
+ ```tsx
2810
+ // ❌ BAD - 'image' is reserved
2811
+ export const meta = {
2812
+ props: {
2813
+ title: "string",
2814
+ image: "string" // Error!
2815
+ }
2816
+ };
2817
+
2818
+ // ✅ GOOD - Use descriptive alternatives
2819
+ export const meta = {
2820
+ props: {
2821
+ title: "string",
2822
+ imageUrl: "string", // or imageSrc, photoUrl, etc.
2823
+ logoUrl: "string"
2824
+ }
2825
+ };
2826
+ ```
2827
+
2828
+ If you try to use a reserved name, you'll get a helpful error:
2829
+ ```
2830
+ Template uses reserved prop names: image
2831
+
2832
+ Try renaming: "image" → "imageUrl" or "imageSrc"
2833
+
2834
+ Reserved names: tw, qr, image, template, path, textPath, config, frame, progress
2835
+ ```
2836
+
1989
2837
  ## All Props Reference
1990
2838
 
1991
2839
  Every template receives these props:
1992
2840
 
1993
2841
  ```tsx
1994
- export default function MyTemplate({
1995
- // Core helpers
2842
+ export default function MyTemplate({
2843
+ // Core helpers (RESERVED - cannot be used as prop names)
1996
2844
  tw, // Tailwind class converter
1997
2845
  qr, // QR code generator (this page)
1998
2846
  template, // Template composer (this page)
1999
2847
  config, // User config from loopwind.json (this page)
2000
-
2001
- // Media helpers (see dedicated pages)
2848
+ textPath, // Text on path helpers (this page)
2849
+
2850
+ // Media helpers (RESERVED)
2002
2851
  image, // Image embedder → see /images
2852
+ path, // Path following → see /animation
2003
2853
 
2004
- // Video-specific (only in video templates)
2854
+ // Video-specific (RESERVED - only in video templates)
2005
2855
  frame, // Current frame number → see /video
2006
2856
  progress, // Animation progress 0-1 → see /video
2007
-
2008
- // Your custom props
2009
- ...props // Any props from your props.json
2857
+
2858
+ // Your custom props (use any names EXCEPT the reserved ones above)
2859
+ ...props // Any props from your meta.props
2010
2860
  }) {
2011
2861
  // Your template code
2012
2862
  }