egaki 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/CHANGELOG.md +145 -9
  2. package/dist/cli/cache-utils.d.ts +33 -0
  3. package/dist/cli/cache-utils.d.ts.map +1 -0
  4. package/dist/cli/cache-utils.js +157 -0
  5. package/dist/cli/cached-generate.d.ts +105 -0
  6. package/dist/cli/cached-generate.d.ts.map +1 -0
  7. package/dist/cli/cached-generate.js +208 -0
  8. package/dist/cli/cached-generate.test.d.ts +11 -0
  9. package/dist/cli/cached-generate.test.d.ts.map +1 -0
  10. package/dist/cli/cached-generate.test.js +431 -0
  11. package/dist/cli/cartesia-provider.d.ts +16 -0
  12. package/dist/cli/cartesia-provider.d.ts.map +1 -0
  13. package/dist/cli/cartesia-provider.js +184 -0
  14. package/dist/cli/chatgpt-auth.d.ts.map +1 -0
  15. package/dist/cli/cli.d.ts +4 -0
  16. package/dist/cli/cli.d.ts.map +1 -0
  17. package/dist/{cli.js → cli/cli.js} +626 -600
  18. package/dist/cli/clone-generate.d.ts +26 -0
  19. package/dist/cli/clone-generate.d.ts.map +1 -0
  20. package/dist/cli/clone-generate.js +148 -0
  21. package/dist/cli/credentials.d.ts.map +1 -0
  22. package/dist/{credentials.js → cli/credentials.js} +20 -0
  23. package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
  24. package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
  25. package/dist/cli/elevenlabs-speech-provider.js +199 -0
  26. package/dist/cli/generate.d.ts +140 -0
  27. package/dist/cli/generate.d.ts.map +1 -0
  28. package/dist/cli/generate.js +604 -0
  29. package/dist/cli/generate.test.d.ts +2 -0
  30. package/dist/cli/generate.test.d.ts.map +1 -0
  31. package/dist/cli/generate.test.js +131 -0
  32. package/dist/cli/login.d.ts.map +1 -0
  33. package/dist/cli/main.d.ts +3 -0
  34. package/dist/cli/main.d.ts.map +1 -0
  35. package/dist/cli/main.js +7 -0
  36. package/dist/{model-catalog.d.ts → cli/model-catalog.d.ts} +6 -0
  37. package/dist/cli/model-catalog.d.ts.map +1 -0
  38. package/dist/{model-catalog.js → cli/model-catalog.js} +714 -4
  39. package/dist/{models.d.ts → cli/models.d.ts} +12 -5
  40. package/dist/cli/models.d.ts.map +1 -0
  41. package/dist/{models.js → cli/models.js} +58 -57
  42. package/dist/cli/open-browser.d.ts.map +1 -0
  43. package/dist/cli/openai-speech-provider.d.ts +3 -0
  44. package/dist/cli/openai-speech-provider.d.ts.map +1 -0
  45. package/dist/cli/openai-speech-provider.js +28 -0
  46. package/dist/cli/separation-generate.d.ts +34 -0
  47. package/dist/cli/separation-generate.d.ts.map +1 -0
  48. package/dist/cli/separation-generate.js +117 -0
  49. package/dist/cli/speech-catalog.d.ts +35 -0
  50. package/dist/cli/speech-catalog.d.ts.map +1 -0
  51. package/dist/cli/speech-catalog.js +195 -0
  52. package/dist/cli/speech-generate.d.ts +86 -0
  53. package/dist/cli/speech-generate.d.ts.map +1 -0
  54. package/dist/cli/speech-generate.js +156 -0
  55. package/dist/cli/speech-models.d.ts +8 -0
  56. package/dist/cli/speech-models.d.ts.map +1 -0
  57. package/dist/cli/speech-models.js +109 -0
  58. package/dist/cli/speech-provider.test.d.ts +2 -0
  59. package/dist/cli/speech-provider.test.d.ts.map +1 -0
  60. package/dist/cli/speech-provider.test.js +197 -0
  61. package/dist/cli/subscription.d.ts.map +1 -0
  62. package/dist/cli/transcription-catalog.d.ts +35 -0
  63. package/dist/cli/transcription-catalog.d.ts.map +1 -0
  64. package/dist/cli/transcription-catalog.js +192 -0
  65. package/dist/cli/transcription-generate.d.ts +62 -0
  66. package/dist/cli/transcription-generate.d.ts.map +1 -0
  67. package/dist/cli/transcription-generate.js +144 -0
  68. package/dist/cli/transcription-models.d.ts +9 -0
  69. package/dist/cli/transcription-models.d.ts.map +1 -0
  70. package/dist/cli/transcription-models.js +100 -0
  71. package/dist/cli/transcription-providers.d.ts +22 -0
  72. package/dist/cli/transcription-providers.d.ts.map +1 -0
  73. package/dist/cli/transcription-providers.js +290 -0
  74. package/dist/cli/video-model-catalog.d.ts.map +1 -0
  75. package/dist/cli/xai-auth.d.ts.map +1 -0
  76. package/dist/{xai-auth.js → cli/xai-auth.js} +1 -1
  77. package/dist/vite/angled-screen.d.ts +57 -0
  78. package/dist/vite/angled-screen.d.ts.map +1 -0
  79. package/dist/vite/angled-screen.js +115 -0
  80. package/dist/vite/app.d.ts +51 -0
  81. package/dist/vite/app.d.ts.map +1 -0
  82. package/dist/vite/app.js +150 -0
  83. package/dist/vite/bands-shader.d.ts +47 -0
  84. package/dist/vite/bands-shader.d.ts.map +1 -0
  85. package/dist/vite/bands-shader.js +267 -0
  86. package/dist/vite/code-block.d.ts +107 -0
  87. package/dist/vite/code-block.d.ts.map +1 -0
  88. package/dist/vite/code-block.js +641 -0
  89. package/dist/vite/components.d.ts +36 -0
  90. package/dist/vite/components.d.ts.map +1 -0
  91. package/dist/vite/components.js +113 -0
  92. package/dist/vite/dispersion-rings-shader.d.ts +44 -0
  93. package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
  94. package/dist/vite/dispersion-rings-shader.js +282 -0
  95. package/dist/vite/easing-curves.d.ts +129 -0
  96. package/dist/vite/easing-curves.d.ts.map +1 -0
  97. package/dist/vite/easing-curves.js +584 -0
  98. package/dist/vite/easing-curves.test.d.ts +2 -0
  99. package/dist/vite/easing-curves.test.d.ts.map +1 -0
  100. package/dist/vite/easing-curves.test.js +200 -0
  101. package/dist/vite/generated-media-client.d.ts +35 -0
  102. package/dist/vite/generated-media-client.d.ts.map +1 -0
  103. package/dist/vite/generated-media-client.js +76 -0
  104. package/dist/vite/keyframes.d.ts +129 -0
  105. package/dist/vite/keyframes.d.ts.map +1 -0
  106. package/dist/vite/keyframes.js +194 -0
  107. package/dist/vite/layout-editor.d.ts +45 -0
  108. package/dist/vite/layout-editor.d.ts.map +1 -0
  109. package/dist/vite/layout-editor.js +587 -0
  110. package/dist/vite/layout-transition.d.ts +136 -0
  111. package/dist/vite/layout-transition.d.ts.map +1 -0
  112. package/dist/vite/layout-transition.js +581 -0
  113. package/dist/vite/liquid-gradient-shader.d.ts +62 -0
  114. package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
  115. package/dist/vite/liquid-gradient-shader.js +413 -0
  116. package/dist/vite/mdx-client.d.ts +15 -0
  117. package/dist/vite/mdx-client.d.ts.map +1 -0
  118. package/dist/vite/mdx-client.js +265 -0
  119. package/dist/vite/mdx-parse.d.ts +102 -0
  120. package/dist/vite/mdx-parse.d.ts.map +1 -0
  121. package/dist/vite/mdx-parse.js +251 -0
  122. package/dist/vite/mdx-provided-components.d.ts +18 -0
  123. package/dist/vite/mdx-provided-components.d.ts.map +1 -0
  124. package/dist/vite/mdx-provided-components.js +14 -0
  125. package/dist/vite/mdx-video.d.ts +327 -0
  126. package/dist/vite/mdx-video.d.ts.map +1 -0
  127. package/dist/vite/mdx-video.js +596 -0
  128. package/dist/vite/mdx-video.test.d.ts +9 -0
  129. package/dist/vite/mdx-video.test.d.ts.map +1 -0
  130. package/dist/vite/mdx-video.test.js +1945 -0
  131. package/dist/vite/media-components.d.ts +45 -0
  132. package/dist/vite/media-components.d.ts.map +1 -0
  133. package/dist/vite/media-components.js +355 -0
  134. package/dist/vite/media-duration-store.d.ts +66 -0
  135. package/dist/vite/media-duration-store.d.ts.map +1 -0
  136. package/dist/vite/media-duration-store.js +204 -0
  137. package/dist/vite/player-page.d.ts +35 -0
  138. package/dist/vite/player-page.d.ts.map +1 -0
  139. package/dist/vite/player-page.js +720 -0
  140. package/dist/vite/render-client.d.ts +18 -0
  141. package/dist/vite/render-client.d.ts.map +1 -0
  142. package/dist/vite/render-client.js +67 -0
  143. package/dist/vite/sdk.d.ts +221 -0
  144. package/dist/vite/sdk.d.ts.map +1 -0
  145. package/dist/vite/sdk.js +355 -0
  146. package/dist/vite/server-components.d.ts +55 -0
  147. package/dist/vite/server-components.d.ts.map +1 -0
  148. package/dist/vite/server-components.js +156 -0
  149. package/dist/vite/server-mdx.d.ts +74 -0
  150. package/dist/vite/server-mdx.d.ts.map +1 -0
  151. package/dist/vite/server-mdx.js +259 -0
  152. package/dist/vite/shader-renderer.d.ts +48 -0
  153. package/dist/vite/shader-renderer.d.ts.map +1 -0
  154. package/dist/vite/shader-renderer.js +411 -0
  155. package/dist/vite/store-hooks.d.ts +12 -0
  156. package/dist/vite/store-hooks.d.ts.map +1 -0
  157. package/dist/vite/store-hooks.js +93 -0
  158. package/dist/vite/store.d.ts +75 -0
  159. package/dist/vite/store.d.ts.map +1 -0
  160. package/dist/vite/store.js +52 -0
  161. package/dist/vite/store.test.d.ts +6 -0
  162. package/dist/vite/store.test.d.ts.map +1 -0
  163. package/dist/vite/store.test.js +172 -0
  164. package/dist/vite/tweakpane-hook.d.ts +94 -0
  165. package/dist/vite/tweakpane-hook.d.ts.map +1 -0
  166. package/dist/vite/tweakpane-hook.js +450 -0
  167. package/dist/vite/vite-plugin.d.ts +28 -0
  168. package/dist/vite/vite-plugin.d.ts.map +1 -0
  169. package/dist/vite/vite-plugin.js +539 -0
  170. package/dist/vite/wave-gradient-shader.d.ts +41 -0
  171. package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
  172. package/dist/vite/wave-gradient-shader.js +207 -0
  173. package/package.json +101 -24
  174. package/skills/egaki/SKILL.md +162 -1
  175. package/src/cli/cache-utils.ts +169 -0
  176. package/src/cli/cached-generate.test.ts +531 -0
  177. package/src/cli/cached-generate.ts +348 -0
  178. package/src/cli/cartesia-provider.ts +249 -0
  179. package/src/{cli.ts → cli/cli.ts} +836 -884
  180. package/src/cli/clone-generate.ts +202 -0
  181. package/src/{credentials.ts → cli/credentials.ts} +20 -0
  182. package/src/cli/elevenlabs-speech-provider.ts +243 -0
  183. package/src/cli/generate.test.ts +166 -0
  184. package/src/cli/generate.ts +912 -0
  185. package/src/cli/main.ts +8 -0
  186. package/src/{model-catalog.ts → cli/model-catalog.ts} +787 -58
  187. package/src/{models.ts → cli/models.ts} +65 -86
  188. package/src/cli/openai-speech-provider.ts +34 -0
  189. package/src/cli/separation-generate.ts +187 -0
  190. package/src/cli/speech-catalog.ts +253 -0
  191. package/src/cli/speech-generate.ts +275 -0
  192. package/src/cli/speech-models.ts +146 -0
  193. package/src/cli/speech-provider.test.ts +226 -0
  194. package/src/cli/transcription-catalog.ts +250 -0
  195. package/src/cli/transcription-generate.ts +214 -0
  196. package/src/cli/transcription-models.ts +136 -0
  197. package/src/cli/transcription-providers.ts +427 -0
  198. package/src/{xai-auth.ts → cli/xai-auth.ts} +1 -1
  199. package/src/vite/angled-screen.tsx +214 -0
  200. package/src/vite/app.tsx +197 -0
  201. package/src/vite/bands-shader.tsx +319 -0
  202. package/src/vite/code-block.tsx +939 -0
  203. package/src/vite/components.tsx +278 -0
  204. package/src/vite/dispersion-rings-shader.tsx +331 -0
  205. package/src/vite/easing-curves.test.ts +232 -0
  206. package/src/vite/easing-curves.ts +743 -0
  207. package/src/vite/generated-media-client.tsx +121 -0
  208. package/src/vite/keyframes.tsx +347 -0
  209. package/src/vite/layout-editor.tsx +709 -0
  210. package/src/vite/layout-transition.tsx +706 -0
  211. package/src/vite/liquid-gradient-shader.tsx +474 -0
  212. package/src/vite/mdx-client.tsx +420 -0
  213. package/src/vite/mdx-parse.ts +343 -0
  214. package/src/vite/mdx-provided-components.ts +19 -0
  215. package/src/vite/mdx-video.test.tsx +2219 -0
  216. package/src/vite/mdx-video.tsx +976 -0
  217. package/src/vite/media-components.tsx +473 -0
  218. package/src/vite/media-duration-store.ts +225 -0
  219. package/src/vite/motion-timing.ts +194 -0
  220. package/src/vite/player-page.tsx +1107 -0
  221. package/src/vite/render-client.ts +84 -0
  222. package/src/vite/sdk.ts +598 -0
  223. package/src/vite/server-components.tsx +241 -0
  224. package/src/vite/server-mdx.ts +285 -0
  225. package/src/vite/shader-renderer.tsx +535 -0
  226. package/src/vite/store-hooks.ts +117 -0
  227. package/src/vite/store.test.ts +202 -0
  228. package/src/vite/store.ts +115 -0
  229. package/src/vite/styles.css +21 -0
  230. package/src/vite/tweakpane-hook.tsx +593 -0
  231. package/src/vite/virtual-modules.d.ts +30 -0
  232. package/src/vite/vite-plugin.ts +585 -0
  233. package/src/vite/wave-gradient-shader.tsx +253 -0
  234. package/README.md +0 -370
  235. package/dist/antigravity-auth.d.ts +0 -15
  236. package/dist/antigravity-auth.d.ts.map +0 -1
  237. package/dist/antigravity-auth.js +0 -544
  238. package/dist/antigravity-image.d.ts +0 -11
  239. package/dist/antigravity-image.d.ts.map +0 -1
  240. package/dist/antigravity-image.js +0 -285
  241. package/dist/chatgpt-auth.d.ts.map +0 -1
  242. package/dist/cli.d.ts +0 -3
  243. package/dist/cli.d.ts.map +0 -1
  244. package/dist/credentials.d.ts.map +0 -1
  245. package/dist/gateway-model-candidates.generated.d.ts +0 -11
  246. package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
  247. package/dist/gateway-model-candidates.generated.js +0 -64
  248. package/dist/login.d.ts.map +0 -1
  249. package/dist/model-catalog.d.ts.map +0 -1
  250. package/dist/models.d.ts.map +0 -1
  251. package/dist/open-browser.d.ts.map +0 -1
  252. package/dist/subscription.d.ts.map +0 -1
  253. package/dist/video-model-catalog.d.ts.map +0 -1
  254. package/dist/xai-auth.d.ts.map +0 -1
  255. /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
  256. /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
  257. /package/dist/{credentials.d.ts → cli/credentials.d.ts} +0 -0
  258. /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
  259. /package/dist/{login.js → cli/login.js} +0 -0
  260. /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
  261. /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
  262. /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
  263. /package/dist/{subscription.js → cli/subscription.js} +0 -0
  264. /package/dist/{video-model-catalog.d.ts → cli/video-model-catalog.d.ts} +0 -0
  265. /package/dist/{video-model-catalog.js → cli/video-model-catalog.js} +0 -0
  266. /package/dist/{xai-auth.d.ts → cli/xai-auth.d.ts} +0 -0
  267. /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
  268. /package/src/{login.ts → cli/login.ts} +0 -0
  269. /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
  270. /package/src/{subscription.ts → cli/subscription.ts} +0 -0
  271. /package/src/{video-model-catalog.ts → cli/video-model-catalog.ts} +0 -0
@@ -0,0 +1,976 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Client-side video components for MDX rendering.
5
+ *
6
+ * Contains animation wrappers (enter/exit), element overrides for safe-mdx,
7
+ * and the components map builder. All marked 'use client' because animation
8
+ * components use Remotion hooks (useCurrentFrame, useVideoConfig).
9
+ *
10
+ * The server imports this file and gets client references for the components
11
+ * map. safe-mdx creates React elements with these references on the server;
12
+ * hooks execute on the client inside Remotion's Player render loop.
13
+ */
14
+
15
+ import { createContext, useContext, useId, useInsertionEffect, useLayoutEffect, type ReactNode } from 'react'
16
+ import type { SafeMdxError } from 'safe-mdx'
17
+ import type { EagerModules } from 'safe-mdx/parse'
18
+ import {
19
+ splitIntoSections,
20
+ calculateTotalDuration,
21
+ resolveAutoDurations,
22
+ type MdxSection,
23
+ type SplitResult,
24
+ type VideoFrontmatter,
25
+ } from './mdx-parse.ts'
26
+ import {
27
+ BlurReveal,
28
+ MaskedSlideReveal,
29
+ StaggeredFadeUp,
30
+ ShimmerSweep,
31
+ } from './components.tsx'
32
+ import { AngledScreen } from './angled-screen.tsx'
33
+ import { CodeBlock, CODE_THEMES } from './code-block.tsx'
34
+ import { BandsShader } from './bands-shader.tsx'
35
+ import { WaveGradientShader } from './wave-gradient-shader.tsx'
36
+ import { LiquidGradientShader } from './liquid-gradient-shader.tsx'
37
+ import { DispersionRingsShader } from './dispersion-rings-shader.tsx'
38
+
39
+
40
+ export { splitIntoSections, calculateTotalDuration, resolveAutoDurations }
41
+ import { useTweakpane, TWEAKPANE_DISABLED } from './tweakpane-hook.tsx'
42
+ export { useTweakpane, TWEAKPANE_DISABLED }
43
+ export type { MdxSection, SplitResult, VideoFrontmatter, EagerModules, SafeMdxError }
44
+
45
+ // ExportContext and useIsExporting are defined in media-components.tsx and
46
+ // re-exported from this file (see the media components re-export section below).
47
+
48
+ declare global {
49
+ var __egakiMotionSeekTo: ((ms: number, scopeId?: string) => void) | undefined
50
+ var __egakiMotionPrepareTime: ((ms: number) => void) | undefined
51
+ var __egakiMotionRegistry:
52
+ | {
53
+ allAnimations: Set<any>
54
+ scopeIdMap: WeakMap<any, string>
55
+ wrappedStops: WeakSet<any>
56
+ patched: boolean
57
+ currentTimeMs: number | undefined
58
+ }
59
+ | undefined
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // MotionTimingSync — bridges Framer Motion (motion/react) with Remotion.
64
+ //
65
+ // Reads the global seekTo function set by the virtual:egaki-motion-timing
66
+ // module (emitted by the Vite plugin when the user has `motion` installed).
67
+ // Calls seekTo with the current frame time from useLayoutEffect so motion.div
68
+ // animations stay in sync with Remotion's frame-based rendering. No-op when
69
+ // motion isn't installed.
70
+ // ---------------------------------------------------------------------------
71
+
72
+ export function MotionTimingSync({ children }: { children: ReactNode }) {
73
+ const scopeId = useId()
74
+ const frame = useCurrentFrame()
75
+ const { fps } = useVideoConfig()
76
+ const timeMs = (frame / fps) * 1000
77
+
78
+ // Runs before any child layout effects create JSAnimation instances, so
79
+ // the play() patch can immediately sample new animations at this frame.
80
+ useInsertionEffect(() => {
81
+ globalThis.__egakiMotionPrepareTime?.(timeMs)
82
+ }, [timeMs])
83
+
84
+ // useLayoutEffect, not useEffect: motion/react creates JSAnimation
85
+ // instances during layout/effect lifecycle, not during render. Using
86
+ // useLayoutEffect ensures seekTo runs AFTER motion has mounted its
87
+ // animations but BEFORE the browser paints, so Remotion's frame
88
+ // capture sees the correct animation state.
89
+ useLayoutEffect(() => {
90
+ globalThis.__egakiMotionSeekTo?.(timeMs, scopeId)
91
+ }, [timeMs, scopeId])
92
+
93
+ return (
94
+ <div
95
+ data-egaki-motion-scope-id={scopeId}
96
+ style={{ display: 'contents' }}
97
+ >
98
+ {children}
99
+ </div>
100
+ )
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Server slots context — carries RSC-rendered <Server> slot content so that
105
+ // the SDK's renderStillOnWeb / renderMediaOnWeb can access them in the fresh
106
+ // React tree. Defined here (not in mdx-client.tsx) to avoid a circular import
107
+ // between mdx-client.tsx and player-page.tsx.
108
+ // ---------------------------------------------------------------------------
109
+
110
+ export type ServerSlots = Record<string, ReactNode>
111
+ export const ServerSlotsContext = createContext<ServerSlots>({})
112
+
113
+ import {
114
+ AbsoluteFill,
115
+ Internals,
116
+ interpolate,
117
+ spring,
118
+ useCurrentFrame,
119
+ useVideoConfig,
120
+ } from 'remotion'
121
+ import {
122
+ BEZIER_POINTS,
123
+ cubicBezier,
124
+ naturalThrowSamples,
125
+ decelerateOvershootSamples,
126
+ decelerateElasticSamples,
127
+ accelerateImpulseSamples,
128
+ accelerateElasticSamples,
129
+ elasticSnapSamples,
130
+ bounceSamples,
131
+ bounceAnticipateSamples,
132
+ bounceThrowSamples,
133
+ impulseSlowSamples,
134
+ impulseOvershootSamples,
135
+ overshootSamples,
136
+ overshootElasticSamples,
137
+ overshootBouncySamples,
138
+ lerpSamples,
139
+ } from './easing-curves.ts'
140
+
141
+ // Re-export the continuous preset functions (Jitter easings at ANY intensity,
142
+ // e.g. impulseOvershoot(96), interpolated in config space) and the engine
143
+ // primitives for building custom curves.
144
+ export {
145
+ type ControlPoint,
146
+ type EasingPreset,
147
+ type Intensity,
148
+ BEZIER_POINTS,
149
+ cubicBezier,
150
+ polybezier,
151
+ pathPreset,
152
+ springPreset,
153
+ bouncePreset,
154
+ samplePreset,
155
+ naturalThrow,
156
+ decelerateOvershoot,
157
+ decelerateElastic,
158
+ accelerateImpulse,
159
+ accelerateElastic,
160
+ elasticSnap,
161
+ bounce,
162
+ bounceAnticipate,
163
+ bounceThrow,
164
+ impulseSlow,
165
+ impulseOvershoot,
166
+ overshoot,
167
+ overshootElastic,
168
+ overshootBouncy,
169
+ } from './easing-curves.ts'
170
+
171
+ // Raw sampled curves (51 points per intensity level 0/25/50/75/100).
172
+ export {
173
+ naturalThrowSamples,
174
+ decelerateOvershootSamples,
175
+ decelerateElasticSamples,
176
+ accelerateImpulseSamples,
177
+ accelerateElasticSamples,
178
+ elasticSnapSamples,
179
+ bounceSamples,
180
+ bounceAnticipateSamples,
181
+ bounceThrowSamples,
182
+ impulseSlowSamples,
183
+ impulseOvershootSamples,
184
+ overshootSamples,
185
+ overshootElasticSamples,
186
+ overshootBouncySamples,
187
+ lerpSamples,
188
+ }
189
+
190
+ // ---------------------------------------------------------------------------
191
+ // useAbsoluteCurrentFrame() — absolute composition frame hook
192
+ //
193
+ // Remotion's useCurrentFrame() returns the frame relative to the current
194
+ // Series.Sequence (resets to 0 at each section). This hook returns the
195
+ // absolute frame across the entire composition, useful for global timing,
196
+ // syncing elements across sections, or computing total elapsed time.
197
+ // Uses Remotion's internal useTimelinePosition which is what useCurrentFrame
198
+ // reads before subtracting the sequence offset.
199
+ // ---------------------------------------------------------------------------
200
+
201
+ /**
202
+ * Returns the absolute frame number across the entire composition.
203
+ *
204
+ * Unlike `useCurrentFrame()` which resets to 0 at each section boundary
205
+ * (Series.Sequence), this returns the global frame from 0 to totalDuration.
206
+ *
207
+ * ```tsx
208
+ * const absoluteFrame = useAbsoluteCurrentFrame()
209
+ * const elapsedSeconds = absoluteFrame / fps
210
+ * ```
211
+ */
212
+ export function useAbsoluteCurrentFrame(): number {
213
+ return Internals.Timeline.useTimelinePosition()
214
+ }
215
+
216
+ /**
217
+ * Returns `true` when the component is being rendered inside a premounted
218
+ * `<Series.Sequence premountFor={…}>` — i.e. the sequence is mounted early
219
+ * for preloading but invisible (opacity 0, pointer-events none, frozen at
220
+ * its start frame).
221
+ *
222
+ * Use this to skip expensive side effects (tweakpane registration, heavy
223
+ * canvas work, etc.) that shouldn't run during premount.
224
+ *
225
+ * Uses Remotion's internal `SequenceContext.premounting` flag — the same
226
+ * mechanism Remotion's own `<Video>`, `<Audio>`, and `<Img>` use to skip
227
+ * buffering during premount.
228
+ */
229
+ export function useIsPremounting(): boolean {
230
+ const ctx = useContext(Internals.SequenceContext)
231
+ return ctx?.premounting ?? false
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // springFromDuration() — Framer Motion-style spring API for Remotion
236
+ //
237
+ // Remotion's spring() uses physics parameters (damping, stiffness, mass)
238
+ // which are hard to reason about. This converts the intuitive
239
+ // (duration, bounce) pair into those physics params, matching how
240
+ // Framer Motion's simplified spring API works internally.
241
+ //
242
+ // - duration: seconds (how long the animation takes)
243
+ // - bounce: 0 = critically damped (no overshoot), 1 = maximum bounce
244
+ //
245
+ // Always use this over raw spring() config for readability.
246
+ // ---------------------------------------------------------------------------
247
+
248
+ export interface SpringConfig {
249
+ stiffness: number
250
+ damping: number
251
+ mass: number
252
+ }
253
+
254
+ /**
255
+ * Convert a human-readable (duration, bounce) pair into Remotion spring
256
+ * physics config. Matches Motion's `visualDuration` spring formula exactly.
257
+ *
258
+ * The `duration` param is a *perceptual* duration (how long the animation
259
+ * *feels*), not the exact settling time. Motion multiplies it by 1.2
260
+ * internally to derive the natural frequency, so the spring actually
261
+ * oscillates slightly longer than `duration` but *appears* to complete
262
+ * within it. We use the same 1.2 factor for identical behavior.
263
+ *
264
+ * @param duration - Perceptual animation duration in seconds (e.g. 0.5).
265
+ * Internally multiplied by 1.2 to match Motion's visualDuration formula.
266
+ * @param bounce - Bounciness from 0 (no overshoot) to 1 (max bounce). Default 0.
267
+ *
268
+ * ```ts
269
+ * // No bounce, 400ms
270
+ * spring({ frame, fps, config: springFromDuration(0.4) })
271
+ *
272
+ * // Subtle Apple-like overshoot, 600ms
273
+ * spring({ frame, fps, config: springFromDuration(0.6, 0.25) })
274
+ *
275
+ * // Playful bounce, 500ms
276
+ * spring({ frame, fps, config: springFromDuration(0.5, 0.5) })
277
+ * ```
278
+ */
279
+ export function springFromDuration(
280
+ duration: number,
281
+ bounce: number = 0,
282
+ ): SpringConfig {
283
+ // Matches Motion's visualDuration path (motion-dom spring.ts lines 191-205).
284
+ // The 1.2 factor converts perceptual duration to the spring's natural period.
285
+ const omega = (2 * Math.PI) / (duration * 1.2)
286
+ const stiffness = omega * omega
287
+ // Motion clamps dampingRatio (not bounce) to [0.05, 1]
288
+ const dampingRatio = Math.max(0.05, Math.min(1, 1 - bounce))
289
+ return {
290
+ stiffness,
291
+ damping: 2 * dampingRatio * Math.sqrt(stiffness),
292
+ mass: 1,
293
+ }
294
+ }
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // findSpringConfig — Motion's duration-based spring solver (Newton-Raphson).
298
+ //
299
+ // Unlike springFromDuration (which uses a perceptual visual-duration), this
300
+ // finds spring params where the envelope decays to near-zero at exactly the
301
+ // given duration. Ported from Motion's findSpring() (motion-dom spring.ts).
302
+ // ---------------------------------------------------------------------------
303
+
304
+ function calcAngularFreq(undampedFreq: number, dampingRatio: number) {
305
+ return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio)
306
+ }
307
+
308
+ function approximateRoot(
309
+ envelope: (n: number) => number,
310
+ derivative: (n: number) => number,
311
+ initialGuess: number,
312
+ ): number {
313
+ let result = initialGuess
314
+ for (let i = 1; i < 12; i++) {
315
+ result = result - envelope(result) / derivative(result)
316
+ }
317
+ return result
318
+ }
319
+
320
+ /**
321
+ * Find spring physics config where the spring settles at exactly `duration`.
322
+ * Ported from Motion's `findSpring()` — uses Newton-Raphson root-finding to
323
+ * solve for the undamped natural frequency that makes the spring envelope
324
+ * reach near-zero at the given duration.
325
+ *
326
+ * Use this when you need the spring to settle at a precise time (e.g. syncing
327
+ * to a beat or section boundary). For general-purpose springs where the feel
328
+ * matters more than exact timing, prefer `springFromDuration()`.
329
+ *
330
+ * Note: Motion's generator also forcibly snaps `value = target` when
331
+ * `t >= duration`. Remotion's spring() does not; it naturally settles.
332
+ * The envelope will be near-zero at `duration` but not forcibly clamped.
333
+ *
334
+ * @param duration - Exact settling time in seconds.
335
+ * @param bounce - Bounciness from 0 (no overshoot) to 1 (max bounce). Default 0.
336
+ *
337
+ * ```ts
338
+ * // Spring that settles in exactly 800ms with moderate bounce
339
+ * spring({ frame, fps, config: findSpringConfig(0.8, 0.3) })
340
+ * ```
341
+ */
342
+ export function findSpringConfig(
343
+ duration: number,
344
+ bounce: number = 0,
345
+ ): SpringConfig {
346
+ const mass = 1
347
+ const velocity = 0
348
+ const safeMin = 0.001
349
+
350
+ let dampingRatio = 1 - bounce
351
+ dampingRatio = Math.max(0.05, Math.min(1, dampingRatio))
352
+ duration = Math.max(0.01, Math.min(10, duration))
353
+
354
+ let envelope: (n: number) => number
355
+ let derivative: (n: number) => number
356
+
357
+ if (dampingRatio < 1) {
358
+ // Underdamped
359
+ envelope = (undampedFreq) => {
360
+ const exponentialDecay = undampedFreq * dampingRatio
361
+ const delta = exponentialDecay * duration
362
+ const a = exponentialDecay - velocity
363
+ const b = calcAngularFreq(undampedFreq, dampingRatio)
364
+ const c = Math.exp(-delta)
365
+ return safeMin - (a / b) * c
366
+ }
367
+ derivative = (undampedFreq) => {
368
+ const exponentialDecay = undampedFreq * dampingRatio
369
+ const delta = exponentialDecay * duration
370
+ const d = delta * velocity + velocity
371
+ const e = Math.pow(dampingRatio, 2) * Math.pow(undampedFreq, 2) * duration
372
+ const f = Math.exp(-delta)
373
+ const g = calcAngularFreq(Math.pow(undampedFreq, 2), dampingRatio)
374
+ const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1
375
+ return (factor * ((d - e) * f)) / g
376
+ }
377
+ } else {
378
+ // Critically damped
379
+ envelope = (undampedFreq) => {
380
+ const a = Math.exp(-undampedFreq * duration)
381
+ const b = (undampedFreq - velocity) * duration + 1
382
+ return -safeMin + a * b
383
+ }
384
+ derivative = (undampedFreq) => {
385
+ const a = Math.exp(-undampedFreq * duration)
386
+ const b = (velocity - undampedFreq) * (duration * duration)
387
+ return a * b
388
+ }
389
+ }
390
+
391
+ const undampedFreq = approximateRoot(envelope, derivative, 5 / duration)
392
+
393
+ if (isNaN(undampedFreq)) {
394
+ return { stiffness: 100, damping: 10, mass: 1 }
395
+ }
396
+
397
+ const stiffness = Math.pow(undampedFreq, 2) * mass
398
+ return {
399
+ stiffness,
400
+ damping: dampingRatio * 2 * Math.sqrt(mass * stiffness),
401
+ mass,
402
+ }
403
+ }
404
+
405
+ /**
406
+ * Shorthand: run a spring animation driven by springFromDuration().
407
+ * Returns 0-to-1 progress (or 0-to-`to` if specified).
408
+ *
409
+ * ```tsx
410
+ * const scale = dspring(frame, fps, 0.5, 0.3) // 500ms, slight bounce
411
+ * ```
412
+ */
413
+ export function dspring(
414
+ frame: number,
415
+ fps: number,
416
+ duration: number,
417
+ bounce: number = 0,
418
+ options?: { delay?: number; to?: number; from?: number },
419
+ ): number {
420
+ return spring({
421
+ frame,
422
+ fps,
423
+ config: springFromDuration(duration, bounce),
424
+ delay: options?.delay,
425
+ to: options?.to,
426
+ from: options?.from,
427
+ })
428
+ }
429
+
430
+ // ---------------------------------------------------------------------------
431
+ // Easing presets — named bezier curves for common motion styles.
432
+ // Use with interpolate(frame, range, range, { easing: EASE.apple }).
433
+ //
434
+ // These are the After Effects / Apple motion graphics "cheat codes":
435
+ // curves that motion designers converge on for premium-feeling animation.
436
+ // ---------------------------------------------------------------------------
437
+
438
+ export const EASE = {
439
+ /** AE 75% influence — tight S-curve, the "Apple ease" */
440
+ apple: cubicBezier(0.76, 0, 0.24, 1),
441
+ /** Fast enter, gentle settle — elements arriving with momentum */
442
+ enterFast: cubicBezier(0.22, 1, 0.36, 1),
443
+ /** Slow start, fast exit — elements leaving the frame */
444
+ exitSlow: cubicBezier(0.55, 0, 1, 0.45),
445
+ /** Social media punch — very sharp burst */
446
+ snappy: cubicBezier(0.87, 0, 0.13, 1),
447
+ /** Luxurious, slow cinematic feel */
448
+ cinematic: cubicBezier(0.83, 0, 0.17, 1),
449
+
450
+ // --- Bezier presets (intensity 50 defaults) ---
451
+
452
+ /** Strong ease-out, snaps into place. The workhorse motion curve. */
453
+ smooth: cubicBezier(0.5, 0, 0, 1),
454
+ /** Symmetric S-curve, natural feeling in-out */
455
+ natural: cubicBezier(0.8, 0, 0.2, 1),
456
+ /** Pure deceleration, no ease-in. Objects arriving at full speed. */
457
+ decelerate: cubicBezier(0, 0, 0, 1),
458
+ /** Pure acceleration, no ease-out. Objects leaving from rest. */
459
+ accelerate: cubicBezier(1, 0, 1, 1),
460
+
461
+ // --- Sampled presets (intensity 50, spring/bounce/overshoot) ---
462
+ // Values can exceed 0-1 range. Use with interpolate().
463
+
464
+ /** Elastic snap into place with ringing oscillation */
465
+ elasticSnap: sampledEasing(elasticSnapSamples[50]),
466
+ /** Standard bounce, like a ball dropping */
467
+ bounce: sampledEasing(bounceSamples[50]),
468
+ /** Bounce with anticipation (pulls back first) */
469
+ bounceAnticipate: sampledEasing(bounceAnticipateSamples[50]),
470
+ /** Throw with bounce on landing */
471
+ bounceThrow: sampledEasing(bounceThrowSamples[50]),
472
+ /** Overshoot then settle back */
473
+ overshoot: sampledEasing(overshootSamples[50]),
474
+ /** Overshoot with elastic ringing */
475
+ overshootElastic: sampledEasing(overshootElasticSamples[50]),
476
+ /** Overshoot with bouncy settle */
477
+ overshootBouncy: sampledEasing(overshootBouncySamples[50]),
478
+ /** Deceleration with overshoot */
479
+ decelerateOvershoot: sampledEasing(decelerateOvershootSamples[50]),
480
+ /** Deceleration with elastic overshoot */
481
+ decelerateElastic: sampledEasing(decelerateElasticSamples[50]),
482
+ /** Natural throw with momentum */
483
+ naturalThrow: sampledEasing(naturalThrowSamples[50]),
484
+ /** Accelerating impulse burst */
485
+ accelerateImpulse: sampledEasing(accelerateImpulseSamples[50]),
486
+ /** Acceleration with elastic windup */
487
+ accelerateElastic: sampledEasing(accelerateElasticSamples[50]),
488
+ /** Slow impulse, gradual build then release */
489
+ impulseSlow: sampledEasing(impulseSlowSamples[50]),
490
+ /** Impulse with overshoot settle */
491
+ impulseOvershoot: sampledEasing(impulseOvershootSamples[50]),
492
+ } as const
493
+
494
+ // ---------------------------------------------------------------------------
495
+ // Sampled easing helper — wraps a sample array into an EasingFunction
496
+ // ---------------------------------------------------------------------------
497
+
498
+ type EasingFunction = (t: number) => number
499
+
500
+ /** Wrap a sampled curve into a Remotion-compatible easing function. */
501
+ function sampledEasing(samples: readonly number[]): EasingFunction {
502
+ return (t: number) => lerpSamples(samples, t)
503
+ }
504
+
505
+ // ---------------------------------------------------------------------------
506
+ // Intensity-parameterized easing constructors
507
+ //
508
+ // The default presets in EASE use intensity 50. These functions let you
509
+ // pick any intensity from 0-100 (snapped to nearest 25).
510
+ // Bezier types compute exact curves; sampled types look up the table.
511
+ // ---------------------------------------------------------------------------
512
+
513
+ type Intensity = 0 | 25 | 50 | 75 | 100
514
+ function snapIntensity(i: number): Intensity {
515
+ return (Math.round(i / 25) * 25) as Intensity
516
+ }
517
+
518
+ /** smooth at custom intensity. Pattern: cubic-bezier(lerp(0.3, 0.9, i/100), 0, 0, 1) */
519
+ export function smoothEasing(intensity: number): EasingFunction {
520
+ const x1 = 0.3 + 0.6 * (intensity / 100)
521
+ return cubicBezier(x1, 0, 0, 1)
522
+ }
523
+
524
+ /** natural at custom intensity. Pattern: cubic-bezier(lerp(0.5, 1, i/100), 0, lerp(0.5, 0, i/100), 1) */
525
+ export function naturalEasing(intensity: number): EasingFunction {
526
+ const x1 = 0.5 + 0.5 * (intensity / 100)
527
+ const x2 = 0.5 - 0.5 * (intensity / 100)
528
+ return cubicBezier(x1, 0, x2, 1)
529
+ }
530
+
531
+ /** decelerate at custom intensity */
532
+ export function decelerateEasing(intensity: number): EasingFunction {
533
+ // 0→(0, 0, 0.3, 1), 50→(0, 0, 0, 1), 75→(0, 0.45, 0, 1), 100→(0, 0.9, 0, 1)
534
+ if (intensity <= 50) {
535
+ const x2 = 0.3 * (1 - intensity / 50)
536
+ return cubicBezier(0, 0, x2, 1)
537
+ }
538
+ const y1 = 0.9 * ((intensity - 50) / 50)
539
+ return cubicBezier(0, y1, 0, 1)
540
+ }
541
+
542
+ /** accelerate at custom intensity */
543
+ export function accelerateEasing(intensity: number): EasingFunction {
544
+ // 0→(0.7, 0, 1, 1), 50→(1, 0, 1, 1), 75→(1, 0, 1, 0.55), 100→(1, 0, 1, 0.1)
545
+ if (intensity <= 50) {
546
+ const x1 = 0.7 + 0.3 * (intensity / 50)
547
+ return cubicBezier(x1, 0, 1, 1)
548
+ }
549
+ const y2 = 1 - 0.9 * ((intensity - 50) / 50)
550
+ return cubicBezier(1, 0, 1, y2)
551
+ }
552
+
553
+ /** elasticSnap at custom intensity (sampled) */
554
+ export function elasticSnapEasing(intensity: number): EasingFunction {
555
+ return sampledEasing(elasticSnapSamples[snapIntensity(intensity)])
556
+ }
557
+
558
+ /** bounce at custom intensity (sampled) */
559
+ export function bounceEasing(intensity: number): EasingFunction {
560
+ return sampledEasing(bounceSamples[snapIntensity(intensity)])
561
+ }
562
+
563
+ /** bounceAnticipate at custom intensity (sampled) */
564
+ export function bounceAnticipateEasing(intensity: number): EasingFunction {
565
+ return sampledEasing(bounceAnticipateSamples[snapIntensity(intensity)])
566
+ }
567
+
568
+ /** bounceThrow at custom intensity (sampled) */
569
+ export function bounceThrowEasing(intensity: number): EasingFunction {
570
+ return sampledEasing(bounceThrowSamples[snapIntensity(intensity)])
571
+ }
572
+
573
+ /** overshoot at custom intensity (sampled) */
574
+ export function overshootEasing(intensity: number): EasingFunction {
575
+ return sampledEasing(overshootSamples[snapIntensity(intensity)])
576
+ }
577
+
578
+ /** overshootElastic at custom intensity (sampled) */
579
+ export function overshootElasticEasing(intensity: number): EasingFunction {
580
+ return sampledEasing(overshootElasticSamples[snapIntensity(intensity)])
581
+ }
582
+
583
+ /** overshootBouncy at custom intensity (sampled) */
584
+ export function overshootBouncyEasing(intensity: number): EasingFunction {
585
+ return sampledEasing(overshootBouncySamples[snapIntensity(intensity)])
586
+ }
587
+
588
+ /** decelerateOvershoot at custom intensity (sampled) */
589
+ export function decelerateOvershootEasing(intensity: number): EasingFunction {
590
+ return sampledEasing(decelerateOvershootSamples[snapIntensity(intensity)])
591
+ }
592
+
593
+ /** decelerateElastic at custom intensity (sampled) */
594
+ export function decelerateElasticEasing(intensity: number): EasingFunction {
595
+ return sampledEasing(decelerateElasticSamples[snapIntensity(intensity)])
596
+ }
597
+
598
+ /** naturalThrow at custom intensity (sampled) */
599
+ export function naturalThrowEasing(intensity: number): EasingFunction {
600
+ return sampledEasing(naturalThrowSamples[snapIntensity(intensity)])
601
+ }
602
+
603
+ /** accelerateImpulse at custom intensity (sampled) */
604
+ export function accelerateImpulseEasing(intensity: number): EasingFunction {
605
+ return sampledEasing(accelerateImpulseSamples[snapIntensity(intensity)])
606
+ }
607
+
608
+ /** accelerateElastic at custom intensity (sampled) */
609
+ export function accelerateElasticEasing(intensity: number): EasingFunction {
610
+ return sampledEasing(accelerateElasticSamples[snapIntensity(intensity)])
611
+ }
612
+
613
+ /** impulseSlow at custom intensity (sampled) */
614
+ export function impulseSlowEasing(intensity: number): EasingFunction {
615
+ return sampledEasing(impulseSlowSamples[snapIntensity(intensity)])
616
+ }
617
+
618
+ /** impulseOvershoot at custom intensity (sampled) */
619
+ export function impulseOvershootEasing(intensity: number): EasingFunction {
620
+ return sampledEasing(impulseOvershootSamples[snapIntensity(intensity)])
621
+ }
622
+
623
+ // ---------------------------------------------------------------------------
624
+ // Fill — full-frame layer with stretch + vertical center
625
+ //
626
+ // Like Remotion's AbsoluteFill but with better defaults for video content:
627
+ // children stretch horizontally to fill the frame, center vertically.
628
+ // ---------------------------------------------------------------------------
629
+
630
+ export function Fill({
631
+ children,
632
+ style,
633
+ ...rest
634
+ }: { children?: ReactNode } & React.HTMLAttributes<HTMLDivElement>) {
635
+ return (
636
+ <AbsoluteFill
637
+ style={{
638
+ alignItems: 'stretch',
639
+ justifyContent: 'center',
640
+ ...style,
641
+ }}
642
+ {...rest}
643
+ >
644
+ {children}
645
+ </AbsoluteFill>
646
+ )
647
+ }
648
+
649
+ // ---------------------------------------------------------------------------
650
+ // Background — real component that self-positions as an absolute layer
651
+ //
652
+ // Works both in MDX and when imported from TSX components. Renders its
653
+ // children in a full-frame AbsoluteFill behind sibling content. In Remotion
654
+ // all layout is absolute, so this naturally layers behind content that
655
+ // comes after it in DOM order.
656
+ // ---------------------------------------------------------------------------
657
+
658
+ export function Background({ children }: { children?: ReactNode }) {
659
+ return <AbsoluteFill style={{ zIndex: -1 }}>{children}</AbsoluteFill>
660
+ }
661
+
662
+ const FONT_SANS =
663
+ '-apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", sans-serif'
664
+ const FONT_MONO =
665
+ '"SF Mono", ui-monospace, SFMono-Regular, "Cascadia Code", monospace'
666
+
667
+ // ---------------------------------------------------------------------------
668
+ // Animation primitives — composable, single-responsibility wrappers.
669
+ //
670
+ // Each animates ONE CSS property from `from` to `to` over `duration` frames.
671
+ // Enter vs exit is inferred from `startInFrames`: positive = enter (offset
672
+ // from section start), negative = exit (offset from section end).
673
+ //
674
+ // All use <Fill> so they're full-frame absolute layers. Compose by nesting:
675
+ // <Opacity from={0} to={1} duration={15}>
676
+ // <TranslateX from={-140} to={0} duration={20}>
677
+ // content
678
+ // </TranslateX>
679
+ // </Opacity>
680
+ //
681
+ // Opacity is never implicit. TranslateX doesn't auto-fade; wrap in <Opacity>.
682
+ // ---------------------------------------------------------------------------
683
+
684
+ export interface AnimateProps {
685
+ children: ReactNode
686
+ /** Start value of the animated property. */
687
+ from: number
688
+ /** End value of the animated property. */
689
+ to: number
690
+ /** Animation duration in frames. */
691
+ duration: number
692
+ /** Custom easing function. Defaults to ease-out for enter, ease-in for exit. */
693
+ easing?: (t: number) => number
694
+ /**
695
+ * Frame offset for when the animation starts.
696
+ *
697
+ * Positive or zero: offset from section start (enter animation).
698
+ * `startInFrames={10}` waits 10 frames then starts.
699
+ *
700
+ * Negative: offset from section end (exit animation).
701
+ * `startInFrames={-30}` means the animation starts 30 frames before the
702
+ * section ends.
703
+ */
704
+ startInFrames?: number
705
+ /**
706
+ * Fraction (0-1) of the animation clipped by the scene boundary.
707
+ *
708
+ * For enter (startInFrames >= 0): shifts animation earlier so it starts
709
+ * before the scene — element appears mid-motion at the cut point.
710
+ * cutInMotion={0.1} on a 20-frame animation: starts 2 frames before scene.
711
+ *
712
+ * For exit (startInFrames < 0): shifts animation later so it extends past
713
+ * the scene end — element is still moving at the cut point.
714
+ * cutInMotion={0.2} on a 20-frame exit: extends 4 frames past scene end.
715
+ */
716
+ cutInMotion?: number
717
+ /**
718
+ * When true, wraps children in a plain `<div>` instead of `<Fill>`.
719
+ * Use this when the animated element is inside a flex/grid/flow layout
720
+ * and should not take the full frame. Without `inline`, the wrapper is
721
+ * an AbsoluteFill that covers the entire composition.
722
+ */
723
+ inline?: boolean
724
+ /** Extra styles applied to the wrapper element (both inline and Fill modes). */
725
+ style?: React.CSSProperties
726
+ /**
727
+ * Label for this animation instance. Used as:
728
+ * - The tweakpane folder title (for visual identification)
729
+ * - An HTML `data-animation` attribute (for agent inspection)
730
+ */
731
+ label: string
732
+ }
733
+
734
+ // Ease-out for enters: arrives with momentum, decelerates into place.
735
+ const ENTER_EASING = cubicBezier(0.5, 0, 0, 1)
736
+
737
+ // Ease-in for exits: starts slow, accelerates away.
738
+ const EXIT_EASING = cubicBezier(1, 0, 1, 1)
739
+
740
+ /** Resolve the effective start frame and default easing from AnimateProps. */
741
+ function resolveAnimateStart(
742
+ startInFrames: number,
743
+ duration: number,
744
+ cutInMotion: number,
745
+ durationInFrames: number,
746
+ ): { effectiveStart: number; isExit: boolean } {
747
+ if (startInFrames < 0) {
748
+ // Exit: negative start counts from section end
749
+ const resolvedStart = durationInFrames + startInFrames
750
+ return {
751
+ effectiveStart: resolvedStart + cutInMotion * duration,
752
+ isExit: true,
753
+ }
754
+ }
755
+ // Enter: positive start counts from section start
756
+ return {
757
+ effectiveStart: startInFrames - cutInMotion * duration,
758
+ isExit: false,
759
+ }
760
+ }
761
+
762
+ // ---------------------------------------------------------------------------
763
+ // Easing function → bezier control points extraction.
764
+ //
765
+ // cubicBezier() attaches a [BEZIER_POINTS] symbol property to the returned
766
+ // function with the original [x1, y1, x2, y2] tuple. This lets the tweakpane
767
+ // bezier blade show and edit the exact curve for any easing created with
768
+ // cubicBezier(), including EASE presets and user-defined curves.
769
+ // Non-bezier presets (sampled from spring/bounce physics) don't have this
770
+ // property and fall back to a default curve.
771
+ // ---------------------------------------------------------------------------
772
+
773
+ /** Default bezier control points for enter (ease-out) and exit (ease-in). */
774
+ const DEFAULT_ENTER_BEZIER: [number, number, number, number] = [0.5, 0, 0, 1] // smooth
775
+ const DEFAULT_EXIT_BEZIER: [number, number, number, number] = [1, 0, 1, 1] // accelerate
776
+
777
+ /** Extract bezier control points from an easing function, or null if not a bezier. */
778
+ function easingToBezier(fn: ((t: number) => number) | undefined): [number, number, number, number] | null {
779
+ if (!fn) return null
780
+ const points = (fn as any)[BEZIER_POINTS]
781
+ if (Array.isArray(points) && points.length === 4) return points as [number, number, number, number]
782
+ return null
783
+ }
784
+
785
+ /** Shared tweakpane + interpolation logic for all animation primitives. */
786
+ function useAnimateValue(
787
+ componentName: string,
788
+ props: AnimateProps,
789
+ ): { value: number; resolvedInline: boolean; resolvedStyle: React.CSSProperties | undefined; label: string | undefined } {
790
+ const frame = useCurrentFrame()
791
+ const { durationInFrames, fps } = useVideoConfig()
792
+
793
+ const tpLabel = props.label ? `${componentName}: ${props.label}` : componentName
794
+
795
+ // Determine default bezier control points from the easing prop
796
+ const defaultIsExit = (props.startInFrames ?? 0) < 0
797
+ const propBezier = easingToBezier(props.easing)
798
+ const defaultBezier = propBezier ?? (defaultIsExit ? DEFAULT_EXIT_BEZIER : DEFAULT_ENTER_BEZIER)
799
+
800
+ const tp = useTweakpane(tpLabel, {
801
+ from: { value: props.from, min: -2000, max: 2000, step: 1 },
802
+ to: { value: props.to, min: -2000, max: 2000, step: 1 },
803
+ duration: { value: props.duration, min: 1, max: 10 * fps, step: 1 },
804
+ startInFrames: { value: props.startInFrames ?? 0, min: -10 * fps, max: 10 * fps, step: 1 },
805
+ cutInMotion: { value: props.cutInMotion ?? 0, min: 0, max: 1, step: 0.01 },
806
+ easing: { type: 'cubicBezier' as const, value: defaultBezier },
807
+ inline: props.inline ?? false,
808
+ })
809
+
810
+ const tpBezier: [number, number, number, number] = tp.easing
811
+ // If the user changed the bezier curve in tweakpane, use it.
812
+ // Otherwise use the original prop easing (which may be a non-bezier function).
813
+ const bezierChanged = tpBezier[0] !== defaultBezier[0]
814
+ || tpBezier[1] !== defaultBezier[1]
815
+ || tpBezier[2] !== defaultBezier[2]
816
+ || tpBezier[3] !== defaultBezier[3]
817
+ const finalEasing = bezierChanged
818
+ ? cubicBezier(tpBezier[0], tpBezier[1], tpBezier[2], tpBezier[3])
819
+ : (props.easing ?? (defaultIsExit ? EXIT_EASING : ENTER_EASING))
820
+
821
+ const { effectiveStart } = resolveAnimateStart(
822
+ tp.startInFrames as number, tp.duration as number,
823
+ tp.cutInMotion as number, durationInFrames,
824
+ )
825
+ const value = interpolate(
826
+ frame,
827
+ [effectiveStart, effectiveStart + (tp.duration as number)],
828
+ [tp.from as number, tp.to as number],
829
+ { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: finalEasing },
830
+ )
831
+
832
+ return {
833
+ value,
834
+ resolvedInline: tp.inline as boolean,
835
+ resolvedStyle: props.style,
836
+ label: props.label,
837
+ }
838
+ }
839
+
840
+ export function Opacity(props: AnimateProps) {
841
+ const { value, resolvedInline, resolvedStyle, label } = useAnimateValue('Opacity', props)
842
+ const animStyle = { opacity: value, ...resolvedStyle }
843
+ if (resolvedInline) return <div style={animStyle} data-animation={label}>{props.children}</div>
844
+ return <Fill style={animStyle} data-animation={label}>{props.children}</Fill>
845
+ }
846
+
847
+ export function Scale(props: AnimateProps) {
848
+ const { value, resolvedInline, resolvedStyle, label } = useAnimateValue('Scale', props)
849
+ const animStyle = { transform: `scale(${value})`, willChange: 'transform' as const, ...resolvedStyle }
850
+ if (resolvedInline) return <div style={animStyle} data-animation={label}>{props.children}</div>
851
+ return <Fill style={animStyle} data-animation={label}>{props.children}</Fill>
852
+ }
853
+
854
+ export function TranslateX(props: AnimateProps) {
855
+ const { value, resolvedInline, resolvedStyle, label } = useAnimateValue('TranslateX', props)
856
+ const animStyle = { transform: `translateX(${value}px)`, willChange: 'transform' as const, ...resolvedStyle }
857
+ if (resolvedInline) return <div style={animStyle} data-animation={label}>{props.children}</div>
858
+ return <Fill style={animStyle} data-animation={label}>{props.children}</Fill>
859
+ }
860
+
861
+ export function TranslateY(props: AnimateProps) {
862
+ const { value, resolvedInline, resolvedStyle, label } = useAnimateValue('TranslateY', props)
863
+ const animStyle = { transform: `translateY(${value}px)`, willChange: 'transform' as const, ...resolvedStyle }
864
+ if (resolvedInline) return <div style={animStyle} data-animation={label}>{props.children}</div>
865
+ return <Fill style={animStyle} data-animation={label}>{props.children}</Fill>
866
+ }
867
+
868
+ export function Blur(props: AnimateProps) {
869
+ const { value, resolvedInline, resolvedStyle, label } = useAnimateValue('Blur', props)
870
+ const animStyle = { filter: `blur(${value}px)`, ...resolvedStyle }
871
+ if (resolvedInline) return <div style={animStyle} data-animation={label}>{props.children}</div>
872
+ return <Fill style={animStyle} data-animation={label}>{props.children}</Fill>
873
+ }
874
+
875
+ // ---------------------------------------------------------------------------
876
+ // ---------------------------------------------------------------------------
877
+ // Keyframes & Lottie utilities — extracted to ./keyframes.tsx
878
+ // Re-exported here so all existing consumers keep working.
879
+ // ---------------------------------------------------------------------------
880
+
881
+ export {
882
+ keyframes,
883
+ fromLottieProperty,
884
+ extractLottieDimensionEasing,
885
+ type BezierCurve,
886
+ type Keyframe,
887
+ type KeyframesDimensionOptions,
888
+ type LottieEasingHandle,
889
+ type LottieKeyframe,
890
+ type LottieAnimatedProperty,
891
+ } from './keyframes.tsx'
892
+
893
+ // ---------------------------------------------------------------------------
894
+ // LayoutTransition — extracted to ./layout-transition.tsx
895
+ // Re-exported here so all existing consumers keep working.
896
+ // ---------------------------------------------------------------------------
897
+
898
+ export {
899
+ LayoutContainerContext,
900
+ LayoutTransition,
901
+ LayoutTransitionProvider,
902
+ LayoutGhost,
903
+ LayoutAnimationLayer,
904
+ type LayoutTransitionMode,
905
+ type LayoutEntry,
906
+ type LayoutRegistry,
907
+ } from './layout-transition.tsx'
908
+ // Local import for use in MDX_BUILTIN_COMPONENTS
909
+ import { LayoutTransition } from './layout-transition.tsx'
910
+
911
+ // ---------------------------------------------------------------------------
912
+ // Media components & ExportContext — extracted to ./media-components.tsx
913
+ // Re-exported here so all existing consumers keep working.
914
+ // ---------------------------------------------------------------------------
915
+
916
+ import { ExportContext, useIsExporting, Img, Audio, Video } from './media-components.tsx'
917
+ export { ExportContext, useIsExporting, Img, Audio, Video } from './media-components.tsx'
918
+
919
+ // Visual components and animations are re-exported so they're available
920
+ // as named imports from this client module for MDX usage.
921
+ export {
922
+ BlurReveal,
923
+ MaskedSlideReveal,
924
+ StaggeredFadeUp,
925
+ ShimmerSweep,
926
+ AngledScreen,
927
+ CodeBlock,
928
+ CODE_THEMES,
929
+ BandsShader,
930
+ WaveGradientShader,
931
+ LiquidGradientShader,
932
+ DispersionRingsShader,
933
+ }
934
+
935
+ /** Built-in JSX names available in MDX without user imports. Shared by
936
+ * client rendering (mdx-client.tsx) and <Server> slot rendering (app.tsx).
937
+ * Add new presentation components here once — not in app.tsx / mdx-client. */
938
+ /** Client-side stubs for generated media components. These are always
939
+ * rendered inside auto-wrapped <Server> slots, so the client never calls
940
+ * them directly. They exist so safe-mdx's component resolution finds
941
+ * them, and the prop types enable MDX LSP autocomplete. */
942
+ import type {
943
+ GeneratedImageProps,
944
+ GeneratedVideoProps,
945
+ GeneratedSpeechProps,
946
+ } from './server-components.tsx'
947
+ function GeneratedImage(_props: GeneratedImageProps) { return null }
948
+ function GeneratedVideo(_props: GeneratedVideoProps) { return null }
949
+ function GeneratedSpeech(_props: GeneratedSpeechProps) { return null }
950
+
951
+ export const MDX_BUILTIN_COMPONENTS = {
952
+ Fill,
953
+ Background,
954
+ LayoutTransition,
955
+ AngledScreen,
956
+ BandsShader,
957
+ WaveGradientShader,
958
+ LiquidGradientShader,
959
+ DispersionRingsShader,
960
+ BlurReveal,
961
+ MaskedSlideReveal,
962
+ StaggeredFadeUp,
963
+ CodeBlock,
964
+ ShimmerSweep,
965
+ Img,
966
+ Audio,
967
+ Video,
968
+ Opacity,
969
+ Scale,
970
+ TranslateX,
971
+ TranslateY,
972
+ Blur,
973
+ GeneratedImage,
974
+ GeneratedVideo,
975
+ GeneratedSpeech,
976
+ } as const