egaki 0.6.0 → 0.7.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 (271) hide show
  1. package/CHANGELOG.md +140 -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 +538 -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 +584 -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,121 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Client wrappers for server-generated media components.
5
+ *
6
+ * Each wrapper receives a `srcPromise: Promise<string>` from the server
7
+ * component (streamed through RSC flight) and a `fallbackSrc` for the
8
+ * stale file to show while generation is in progress. React 19's `use()`
9
+ * suspends until the promise resolves, then renders the actual media
10
+ * component. While suspended, the fallback (stale generation) renders if
11
+ * available; otherwise nothing renders.
12
+ *
13
+ * Passthrough props are forwarded to the underlying media component so
14
+ * users can set style, className, trimBefore, trimAfter, playbackRate, etc.
15
+ *
16
+ * Generation tracking: handled server-side via the /api/generation-progress
17
+ * SSE stream. The server tracks all active generations across all scenes
18
+ * and the client fetches progress updates to show in the toolbar.
19
+ * The Suspense fallback only handles delayRender for export mode.
20
+ */
21
+
22
+ import { Suspense, use, useLayoutEffect, type ComponentProps, type ReactNode } from 'react'
23
+ import { useDelayRender } from 'remotion'
24
+ import { Img, Audio, Video, useIsExporting } from './mdx-video.tsx'
25
+ import { useGenerationStatus, type GenerationStatus } from './store-hooks.ts'
26
+
27
+ export { useGenerationStatus, type GenerationStatus }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // Export-aware Suspense fallback — blocks Remotion frame capture while
31
+ // a generated media promise is still pending. Also registers/unregisters
32
+ // the generation in the tracker so the toolbar can show status.
33
+ // ---------------------------------------------------------------------------
34
+
35
+ /** Export-aware Suspense fallback — blocks Remotion frame capture while
36
+ * a generated media promise is still pending. Generation progress tracking
37
+ * is now handled server-side via the /api/generation-progress SSE stream,
38
+ * so this component only needs to handle delayRender for exports. */
39
+ function GeneratedMediaFallback({ children }: { children?: ReactNode }) {
40
+ const isExporting = useIsExporting()
41
+ const { delayRender, continueRender } = useDelayRender()
42
+
43
+ // useLayoutEffect prevents a first-frame capture race: the delay handle
44
+ // is registered before the browser paints, matching Remotion's own pattern.
45
+ useLayoutEffect(() => {
46
+ if (!isExporting) return
47
+ const handle = delayRender('Waiting for generated media', {
48
+ timeoutInMilliseconds: 10 * 60 * 1000,
49
+ })
50
+ return () => continueRender(handle)
51
+ }, [isExporting, delayRender, continueRender])
52
+ return <>{children}</>
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Inner components that call use() — must be inside <Suspense>
57
+ // ---------------------------------------------------------------------------
58
+
59
+ function ResolvedImage({ srcPromise, ...rest }: { srcPromise: Promise<string> } & ComponentProps<typeof Img>) {
60
+ const src = use(srcPromise)
61
+ return <Img src={src} {...rest} />
62
+ }
63
+
64
+ function ResolvedVideo({ srcPromise, ...rest }: { srcPromise: Promise<string> } & ComponentProps<typeof Video>) {
65
+ const src = use(srcPromise)
66
+ return <Video src={src} {...rest} />
67
+ }
68
+
69
+ function ResolvedAudio({ srcPromise, ...rest }: { srcPromise: Promise<string> } & ComponentProps<typeof Audio>) {
70
+ const src = use(srcPromise)
71
+ return <Audio src={src} {...rest} />
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Exported client wrappers — render fallback while promise is pending
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export function GeneratedImageClient({
79
+ srcPromise,
80
+ fallbackSrc,
81
+ ...rest
82
+ }: {
83
+ srcPromise: Promise<string>
84
+ fallbackSrc?: string
85
+ } & ComponentProps<typeof Img>) {
86
+ return (
87
+ <Suspense fallback={<GeneratedMediaFallback>{fallbackSrc ? <Img src={fallbackSrc} {...rest} /> : null}</GeneratedMediaFallback>}>
88
+ <ResolvedImage srcPromise={srcPromise} {...rest} />
89
+ </Suspense>
90
+ )
91
+ }
92
+
93
+ export function GeneratedVideoClient({
94
+ srcPromise,
95
+ fallbackSrc,
96
+ ...rest
97
+ }: {
98
+ srcPromise: Promise<string>
99
+ fallbackSrc?: string
100
+ } & ComponentProps<typeof Video>) {
101
+ return (
102
+ <Suspense fallback={<GeneratedMediaFallback>{fallbackSrc ? <Video src={fallbackSrc} {...rest} /> : null}</GeneratedMediaFallback>}>
103
+ <ResolvedVideo srcPromise={srcPromise} {...rest} />
104
+ </Suspense>
105
+ )
106
+ }
107
+
108
+ export function GeneratedSpeechClient({
109
+ srcPromise,
110
+ fallbackSrc,
111
+ ...rest
112
+ }: {
113
+ srcPromise: Promise<string>
114
+ fallbackSrc?: string
115
+ } & ComponentProps<typeof Audio>) {
116
+ return (
117
+ <Suspense fallback={<GeneratedMediaFallback>{fallbackSrc ? <Audio src={fallbackSrc} {...rest} /> : null}</GeneratedMediaFallback>}>
118
+ <ResolvedAudio srcPromise={srcPromise} {...rest} />
119
+ </Suspense>
120
+ )
121
+ }
@@ -0,0 +1,347 @@
1
+ /**
2
+ * keyframes() — evaluate a keyframed animation at a given frame.
3
+ *
4
+ * Accepts an array of typed keyframe descriptors with bezier easing, hold,
5
+ * and per-dimension control. Wraps Remotion's interpolate() + cubicBezier()
6
+ * so you get the full Lottie/After Effects easing model with clean parameters.
7
+ *
8
+ * Also includes fromLottieProperty() for converting raw Lottie animated
9
+ * properties to the Keyframe[] format, and extractLottieDimensionEasing()
10
+ * for per-dimension easing overrides.
11
+ *
12
+ * See docs/lottie-to-remotion.md for the Lottie field mapping.
13
+ */
14
+
15
+ import { interpolate } from 'remotion'
16
+ import { cubicBezier } from './easing-curves.ts'
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Types
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Cubic bezier control points: [x1, y1, x2, y2].
24
+ * Same as CSS `cubic-bezier(x1, y1, x2, y2)`.
25
+ * x values must be in [0, 1]. y values can overshoot (< 0 or > 1).
26
+ */
27
+ export type BezierCurve = [x1: number, y1: number, x2: number, y2: number]
28
+
29
+ /** A single keyframe in a `keyframes()` animation. */
30
+ export interface Keyframe<T extends number | number[] = number> {
31
+ /** Frame number where this keyframe occurs. */
32
+ time: number
33
+ /** Value at this keyframe. Scalar or vector (e.g. [x, y] for position). */
34
+ value: T
35
+ /**
36
+ * Bezier easing curve for the transition FROM this keyframe TO the next.
37
+ * Four control points [x1, y1, x2, y2], same as CSS cubic-bezier().
38
+ * Omit for linear interpolation. Ignored on the last keyframe.
39
+ */
40
+ easing?: BezierCurve
41
+ /**
42
+ * If true, value holds constant until the next keyframe (step function).
43
+ * No interpolation occurs. Overrides `easing`.
44
+ */
45
+ hold?: boolean
46
+ }
47
+
48
+ /** Options for per-dimension easing on vector keyframes. */
49
+ export interface KeyframesDimensionOptions {
50
+ /**
51
+ * Per-dimension bezier easing overrides.
52
+ * Index matches the dimension index in the value array.
53
+ * When set, overrides the keyframe-level `easing` for that dimension.
54
+ * Each entry is a [x1, y1, x2, y2] bezier curve or undefined to use the keyframe's easing.
55
+ */
56
+ dimensionEasing?: (BezierCurve | undefined)[]
57
+ }
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Internal helpers
61
+ // ---------------------------------------------------------------------------
62
+
63
+ // Step easing: holds at 0 until t=1, then jumps to 1
64
+ function stepEasing(t: number): number {
65
+ return t < 1 ? 0 : 1
66
+ }
67
+
68
+ function buildEasingFn(curve: BezierCurve | undefined): (t: number) => number {
69
+ if (!curve) return (t: number) => t // linear
70
+ return cubicBezier(curve[0], curve[1], curve[2], curve[3])
71
+ }
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // keyframes() — main API
75
+ // ---------------------------------------------------------------------------
76
+
77
+ /**
78
+ * Evaluate a keyframed animation at the given frame.
79
+ *
80
+ * Scalar version: each keyframe has a numeric `value`, returns a number.
81
+ *
82
+ * ```ts
83
+ * const opacity = keyframes(frame, [
84
+ * { time: 0, value: 0, easing: [0.33, 0, 0.67, 1] },
85
+ * { time: 30, value: 1 },
86
+ * ])
87
+ * ```
88
+ */
89
+ export function keyframes(
90
+ frame: number,
91
+ kfs: Keyframe<number>[],
92
+ options?: KeyframesDimensionOptions,
93
+ ): number
94
+
95
+ /**
96
+ * Vector version: each keyframe has an array `value`, returns an array
97
+ * of the same length. Supports per-dimension easing via options.
98
+ *
99
+ * ```ts
100
+ * const [x, y] = keyframes(frame, [
101
+ * { time: 0, value: [0, 0], easing: [0.33, 0, 0.67, 1] },
102
+ * { time: 30, value: [200, 400] },
103
+ * ])
104
+ * ```
105
+ */
106
+ export function keyframes<N extends number>(
107
+ frame: number,
108
+ kfs: Keyframe<number[] & { length: N }>[],
109
+ options?: KeyframesDimensionOptions,
110
+ ): number[]
111
+
112
+ export function keyframes(
113
+ frame: number,
114
+ kfs: Keyframe<number | number[]>[],
115
+ options?: KeyframesDimensionOptions,
116
+ ): number | number[] {
117
+ if (kfs.length === 0) {
118
+ throw new Error('keyframes() requires at least one keyframe')
119
+ }
120
+
121
+ if (kfs.length === 1) {
122
+ return kfs[0]!.value
123
+ }
124
+
125
+ const first = kfs[0]!.value
126
+ const isVector = Array.isArray(first)
127
+
128
+ if (isVector) {
129
+ return evaluateVector(frame, kfs as Keyframe<number[]>[], options)
130
+ }
131
+
132
+ return evaluateScalar(frame, kfs as Keyframe<number>[], options)
133
+ }
134
+
135
+ function evaluateScalar(
136
+ frame: number,
137
+ kfs: Keyframe<number>[],
138
+ options?: KeyframesDimensionOptions,
139
+ ): number {
140
+ const inputRange = kfs.map((kf) => kf.time)
141
+ const outputRange = kfs.map((kf) => kf.value)
142
+ const dimEasing = options?.dimensionEasing?.[0]
143
+
144
+ const easings: ((t: number) => number)[] = []
145
+ for (let i = 0; i < kfs.length - 1; i++) {
146
+ const kf = kfs[i]!
147
+ if (kf.hold) {
148
+ easings.push(stepEasing)
149
+ } else {
150
+ easings.push(buildEasingFn(dimEasing ?? kf.easing))
151
+ }
152
+ }
153
+
154
+ return interpolate(frame, inputRange, outputRange, {
155
+ easing: easings,
156
+ extrapolateLeft: 'clamp',
157
+ extrapolateRight: 'clamp',
158
+ })
159
+ }
160
+
161
+ function evaluateVector(
162
+ frame: number,
163
+ kfs: Keyframe<number[]>[],
164
+ options?: KeyframesDimensionOptions,
165
+ ): number[] {
166
+ const dimensions = kfs[0]!.value.length
167
+ const result: number[] = new Array(dimensions)
168
+
169
+ for (let dim = 0; dim < dimensions; dim++) {
170
+ const inputRange = kfs.map((kf) => kf.time)
171
+ const outputRange = kfs.map((kf) => kf.value[dim]!)
172
+ const dimEasing = options?.dimensionEasing?.[dim]
173
+
174
+ const easings: ((t: number) => number)[] = []
175
+ for (let i = 0; i < kfs.length - 1; i++) {
176
+ const kf = kfs[i]!
177
+ if (kf.hold) {
178
+ easings.push(stepEasing)
179
+ } else {
180
+ easings.push(buildEasingFn(dimEasing ?? kf.easing))
181
+ }
182
+ }
183
+
184
+ result[dim] = interpolate(frame, inputRange, outputRange as number[], {
185
+ easing: easings,
186
+ extrapolateLeft: 'clamp',
187
+ extrapolateRight: 'clamp',
188
+ })
189
+ }
190
+
191
+ return result
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // Lottie conversion utilities
196
+ // ---------------------------------------------------------------------------
197
+
198
+ /** Raw Lottie easing handle shape. */
199
+ export interface LottieEasingHandle {
200
+ x: number | number[]
201
+ y: number | number[]
202
+ }
203
+
204
+ /** Raw Lottie keyframe as found in a .json file. */
205
+ export interface LottieKeyframe {
206
+ /** Frame number */
207
+ t: number
208
+ /** Value at this keyframe */
209
+ s?: number[]
210
+ /** Hold flag */
211
+ h?: 0 | 1
212
+ /** In tangent (easing into next keyframe) */
213
+ i?: LottieEasingHandle
214
+ /** Out tangent (easing leaving this keyframe) */
215
+ o?: LottieEasingHandle
216
+ }
217
+
218
+ /** Raw Lottie animated property: { a: 0|1, k: value | keyframes }. */
219
+ export interface LottieAnimatedProperty {
220
+ /** 1 if animated, 0 if static */
221
+ a: 0 | 1
222
+ /** Static value (when a=0) or array of keyframes (when a=1) */
223
+ k: number | number[] | LottieKeyframe[]
224
+ }
225
+
226
+ /**
227
+ * Convert a raw Lottie animated property into a `Keyframe[]` array
228
+ * that can be passed directly to `keyframes()`.
229
+ *
230
+ * ```ts
231
+ * import lottieJson from './animation.json'
232
+ *
233
+ * // Get the opacity property from layer 0
234
+ * const opacityKfs = fromLottieProperty(lottieJson.layers[0].ks.o)
235
+ * const opacity = keyframes(frame, opacityKfs)
236
+ * ```
237
+ *
238
+ * For static properties (a=0), returns a single keyframe at time 0.
239
+ * For vector properties, values are preserved as arrays.
240
+ */
241
+ export function fromLottieProperty(
242
+ property: LottieAnimatedProperty,
243
+ ): Keyframe<number>[] | Keyframe<number[]>[] {
244
+ // Static property
245
+ if (!property.a || !Array.isArray(property.k) || property.k.length === 0) {
246
+ const val = property.k
247
+ if (Array.isArray(val)) {
248
+ return [{ time: 0, value: val }] as Keyframe<number[]>[]
249
+ }
250
+ return [{ time: 0, value: val as number }]
251
+ }
252
+
253
+ const lottieKfs = property.k as LottieKeyframe[]
254
+
255
+ // Detect if this is a scalar or vector property from the first keyframe's value
256
+ const firstValue = lottieKfs[0]!.s
257
+ if (!firstValue || firstValue.length === 0) {
258
+ return [{ time: 0, value: 0 }]
259
+ }
260
+
261
+ const isScalar = firstValue.length === 1
262
+
263
+ if (isScalar) {
264
+ return lottieKfs.map((lkf, i): Keyframe<number> => {
265
+ const kf: Keyframe<number> = {
266
+ time: lkf.t,
267
+ value: lkf.s?.[0] ?? 0,
268
+ }
269
+ if (lkf.h === 1) {
270
+ kf.hold = true
271
+ } else if (lkf.o && lkf.i && i < lottieKfs.length - 1) {
272
+ kf.easing = extractBezier(lkf.o, lkf.i, 0)
273
+ }
274
+ return kf
275
+ })
276
+ }
277
+
278
+ // Vector property
279
+ return lottieKfs.map((lkf, i): Keyframe<number[]> => {
280
+ const kf: Keyframe<number[]> = {
281
+ time: lkf.t,
282
+ value: lkf.s ? [...lkf.s] : [],
283
+ }
284
+ if (lkf.h === 1) {
285
+ kf.hold = true
286
+ } else if (lkf.o && lkf.i && i < lottieKfs.length - 1) {
287
+ // Use dimension 0 for the keyframe-level easing
288
+ kf.easing = extractBezier(lkf.o, lkf.i, 0)
289
+ }
290
+ return kf
291
+ })
292
+ }
293
+
294
+ /**
295
+ * Extract per-dimension easing overrides from a Lottie keyframe sequence.
296
+ * Returns a `dimensionEasing` array suitable for the `keyframes()` options.
297
+ * Only needed when the Lottie property has different easing per dimension
298
+ * (e.g. position with independent X/Y curves).
299
+ *
300
+ * ```ts
301
+ * const posKfs = fromLottieProperty(lottieJson.layers[0].ks.p)
302
+ * const dimEasing = extractLottieDimensionEasing(lottieJson.layers[0].ks.p, segmentIndex)
303
+ * const [x, y] = keyframes(frame, posKfs, { dimensionEasing: dimEasing })
304
+ * ```
305
+ */
306
+ export function extractLottieDimensionEasing(
307
+ property: LottieAnimatedProperty,
308
+ segmentIndex: number,
309
+ ): (BezierCurve | undefined)[] | undefined {
310
+ if (!property.a || !Array.isArray(property.k)) return undefined
311
+ const lottieKfs = property.k as LottieKeyframe[]
312
+ const lkf = lottieKfs[segmentIndex]
313
+ if (!lkf?.o || !lkf?.i || !lkf.s) return undefined
314
+
315
+ const dimensions = lkf.s.length
316
+ if (dimensions <= 1) return undefined
317
+
318
+ // Check if all dimensions have the same easing — if so, no overrides needed
319
+ const ox = lkf.o.x
320
+ const oy = lkf.o.y
321
+ const ix = lkf.i.x
322
+ const iy = lkf.i.y
323
+ if (!Array.isArray(ox) || !Array.isArray(oy) || !Array.isArray(ix) || !Array.isArray(iy)) {
324
+ return undefined
325
+ }
326
+ if (ox.length <= 1 && oy.length <= 1 && ix.length <= 1 && iy.length <= 1) {
327
+ return undefined
328
+ }
329
+
330
+ const result: (BezierCurve | undefined)[] = []
331
+ for (let dim = 0; dim < dimensions; dim++) {
332
+ result.push(extractBezier(lkf.o, lkf.i, dim))
333
+ }
334
+ return result
335
+ }
336
+
337
+ function extractBezier(
338
+ out: LottieEasingHandle,
339
+ into: LottieEasingHandle,
340
+ dimension: number,
341
+ ): BezierCurve {
342
+ const ox = Array.isArray(out.x) ? (out.x[dimension] ?? out.x[0]!) : out.x
343
+ const oy = Array.isArray(out.y) ? (out.y[dimension] ?? out.y[0]!) : out.y
344
+ const ix = Array.isArray(into.x) ? (into.x[dimension] ?? into.x[0]!) : into.x
345
+ const iy = Array.isArray(into.y) ? (into.y[dimension] ?? into.y[0]!) : into.y
346
+ return [ox, oy, ix, iy]
347
+ }