egaki 0.5.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 (280) hide show
  1. package/CHANGELOG.md +188 -0
  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/cli.js +1329 -0
  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/{credentials.d.ts → cli/credentials.d.ts} +11 -2
  22. package/dist/cli/credentials.d.ts.map +1 -0
  23. package/dist/{credentials.js → cli/credentials.js} +88 -5
  24. package/dist/cli/elevenlabs-speech-provider.d.ts +26 -0
  25. package/dist/cli/elevenlabs-speech-provider.d.ts.map +1 -0
  26. package/dist/cli/elevenlabs-speech-provider.js +199 -0
  27. package/dist/cli/generate.d.ts +140 -0
  28. package/dist/cli/generate.d.ts.map +1 -0
  29. package/dist/cli/generate.js +604 -0
  30. package/dist/cli/generate.test.d.ts +2 -0
  31. package/dist/cli/generate.test.d.ts.map +1 -0
  32. package/dist/cli/generate.test.js +131 -0
  33. package/dist/cli/login.d.ts.map +1 -0
  34. package/dist/{login.js → cli/login.js} +45 -4
  35. package/dist/cli/main.d.ts +3 -0
  36. package/dist/cli/main.d.ts.map +1 -0
  37. package/dist/cli/main.js +7 -0
  38. package/dist/cli/model-catalog.d.ts +141 -0
  39. package/dist/cli/model-catalog.d.ts.map +1 -0
  40. package/dist/cli/model-catalog.js +2135 -0
  41. package/dist/{models.d.ts → cli/models.d.ts} +16 -9
  42. package/dist/cli/models.d.ts.map +1 -0
  43. package/dist/cli/models.js +313 -0
  44. package/dist/cli/open-browser.d.ts.map +1 -0
  45. package/dist/cli/openai-speech-provider.d.ts +3 -0
  46. package/dist/cli/openai-speech-provider.d.ts.map +1 -0
  47. package/dist/cli/openai-speech-provider.js +28 -0
  48. package/dist/cli/separation-generate.d.ts +34 -0
  49. package/dist/cli/separation-generate.d.ts.map +1 -0
  50. package/dist/cli/separation-generate.js +117 -0
  51. package/dist/cli/speech-catalog.d.ts +35 -0
  52. package/dist/cli/speech-catalog.d.ts.map +1 -0
  53. package/dist/cli/speech-catalog.js +195 -0
  54. package/dist/cli/speech-generate.d.ts +86 -0
  55. package/dist/cli/speech-generate.d.ts.map +1 -0
  56. package/dist/cli/speech-generate.js +156 -0
  57. package/dist/cli/speech-models.d.ts +8 -0
  58. package/dist/cli/speech-models.d.ts.map +1 -0
  59. package/dist/cli/speech-models.js +109 -0
  60. package/dist/cli/speech-provider.test.d.ts +2 -0
  61. package/dist/cli/speech-provider.test.d.ts.map +1 -0
  62. package/dist/cli/speech-provider.test.js +197 -0
  63. package/dist/cli/subscription.d.ts.map +1 -0
  64. package/dist/cli/transcription-catalog.d.ts +35 -0
  65. package/dist/cli/transcription-catalog.d.ts.map +1 -0
  66. package/dist/cli/transcription-catalog.js +192 -0
  67. package/dist/cli/transcription-generate.d.ts +62 -0
  68. package/dist/cli/transcription-generate.d.ts.map +1 -0
  69. package/dist/cli/transcription-generate.js +144 -0
  70. package/dist/cli/transcription-models.d.ts +9 -0
  71. package/dist/cli/transcription-models.d.ts.map +1 -0
  72. package/dist/cli/transcription-models.js +100 -0
  73. package/dist/cli/transcription-providers.d.ts +22 -0
  74. package/dist/cli/transcription-providers.d.ts.map +1 -0
  75. package/dist/cli/transcription-providers.js +290 -0
  76. package/dist/cli/video-model-catalog.d.ts +2 -0
  77. package/dist/cli/video-model-catalog.d.ts.map +1 -0
  78. package/dist/cli/video-model-catalog.js +5 -0
  79. package/dist/cli/xai-auth.d.ts +29 -0
  80. package/dist/cli/xai-auth.d.ts.map +1 -0
  81. package/dist/cli/xai-auth.js +325 -0
  82. package/dist/vite/angled-screen.d.ts +57 -0
  83. package/dist/vite/angled-screen.d.ts.map +1 -0
  84. package/dist/vite/angled-screen.js +115 -0
  85. package/dist/vite/app.d.ts +51 -0
  86. package/dist/vite/app.d.ts.map +1 -0
  87. package/dist/vite/app.js +150 -0
  88. package/dist/vite/bands-shader.d.ts +47 -0
  89. package/dist/vite/bands-shader.d.ts.map +1 -0
  90. package/dist/vite/bands-shader.js +267 -0
  91. package/dist/vite/code-block.d.ts +107 -0
  92. package/dist/vite/code-block.d.ts.map +1 -0
  93. package/dist/vite/code-block.js +641 -0
  94. package/dist/vite/components.d.ts +36 -0
  95. package/dist/vite/components.d.ts.map +1 -0
  96. package/dist/vite/components.js +113 -0
  97. package/dist/vite/dispersion-rings-shader.d.ts +44 -0
  98. package/dist/vite/dispersion-rings-shader.d.ts.map +1 -0
  99. package/dist/vite/dispersion-rings-shader.js +282 -0
  100. package/dist/vite/easing-curves.d.ts +129 -0
  101. package/dist/vite/easing-curves.d.ts.map +1 -0
  102. package/dist/vite/easing-curves.js +584 -0
  103. package/dist/vite/easing-curves.test.d.ts +2 -0
  104. package/dist/vite/easing-curves.test.d.ts.map +1 -0
  105. package/dist/vite/easing-curves.test.js +200 -0
  106. package/dist/vite/generated-media-client.d.ts +35 -0
  107. package/dist/vite/generated-media-client.d.ts.map +1 -0
  108. package/dist/vite/generated-media-client.js +76 -0
  109. package/dist/vite/keyframes.d.ts +129 -0
  110. package/dist/vite/keyframes.d.ts.map +1 -0
  111. package/dist/vite/keyframes.js +194 -0
  112. package/dist/vite/layout-editor.d.ts +45 -0
  113. package/dist/vite/layout-editor.d.ts.map +1 -0
  114. package/dist/vite/layout-editor.js +587 -0
  115. package/dist/vite/layout-transition.d.ts +136 -0
  116. package/dist/vite/layout-transition.d.ts.map +1 -0
  117. package/dist/vite/layout-transition.js +581 -0
  118. package/dist/vite/liquid-gradient-shader.d.ts +62 -0
  119. package/dist/vite/liquid-gradient-shader.d.ts.map +1 -0
  120. package/dist/vite/liquid-gradient-shader.js +413 -0
  121. package/dist/vite/mdx-client.d.ts +15 -0
  122. package/dist/vite/mdx-client.d.ts.map +1 -0
  123. package/dist/vite/mdx-client.js +265 -0
  124. package/dist/vite/mdx-parse.d.ts +102 -0
  125. package/dist/vite/mdx-parse.d.ts.map +1 -0
  126. package/dist/vite/mdx-parse.js +251 -0
  127. package/dist/vite/mdx-provided-components.d.ts +18 -0
  128. package/dist/vite/mdx-provided-components.d.ts.map +1 -0
  129. package/dist/vite/mdx-provided-components.js +14 -0
  130. package/dist/vite/mdx-video.d.ts +327 -0
  131. package/dist/vite/mdx-video.d.ts.map +1 -0
  132. package/dist/vite/mdx-video.js +596 -0
  133. package/dist/vite/mdx-video.test.d.ts +9 -0
  134. package/dist/vite/mdx-video.test.d.ts.map +1 -0
  135. package/dist/vite/mdx-video.test.js +1945 -0
  136. package/dist/vite/media-components.d.ts +45 -0
  137. package/dist/vite/media-components.d.ts.map +1 -0
  138. package/dist/vite/media-components.js +355 -0
  139. package/dist/vite/media-duration-store.d.ts +66 -0
  140. package/dist/vite/media-duration-store.d.ts.map +1 -0
  141. package/dist/vite/media-duration-store.js +204 -0
  142. package/dist/vite/player-page.d.ts +35 -0
  143. package/dist/vite/player-page.d.ts.map +1 -0
  144. package/dist/vite/player-page.js +720 -0
  145. package/dist/vite/render-client.d.ts +18 -0
  146. package/dist/vite/render-client.d.ts.map +1 -0
  147. package/dist/vite/render-client.js +67 -0
  148. package/dist/vite/sdk.d.ts +221 -0
  149. package/dist/vite/sdk.d.ts.map +1 -0
  150. package/dist/vite/sdk.js +355 -0
  151. package/dist/vite/server-components.d.ts +55 -0
  152. package/dist/vite/server-components.d.ts.map +1 -0
  153. package/dist/vite/server-components.js +156 -0
  154. package/dist/vite/server-mdx.d.ts +74 -0
  155. package/dist/vite/server-mdx.d.ts.map +1 -0
  156. package/dist/vite/server-mdx.js +259 -0
  157. package/dist/vite/shader-renderer.d.ts +48 -0
  158. package/dist/vite/shader-renderer.d.ts.map +1 -0
  159. package/dist/vite/shader-renderer.js +411 -0
  160. package/dist/vite/store-hooks.d.ts +12 -0
  161. package/dist/vite/store-hooks.d.ts.map +1 -0
  162. package/dist/vite/store-hooks.js +93 -0
  163. package/dist/vite/store.d.ts +75 -0
  164. package/dist/vite/store.d.ts.map +1 -0
  165. package/dist/vite/store.js +52 -0
  166. package/dist/vite/store.test.d.ts +6 -0
  167. package/dist/vite/store.test.d.ts.map +1 -0
  168. package/dist/vite/store.test.js +172 -0
  169. package/dist/vite/tweakpane-hook.d.ts +94 -0
  170. package/dist/vite/tweakpane-hook.d.ts.map +1 -0
  171. package/dist/vite/tweakpane-hook.js +450 -0
  172. package/dist/vite/vite-plugin.d.ts +28 -0
  173. package/dist/vite/vite-plugin.d.ts.map +1 -0
  174. package/dist/vite/vite-plugin.js +538 -0
  175. package/dist/vite/wave-gradient-shader.d.ts +41 -0
  176. package/dist/vite/wave-gradient-shader.d.ts.map +1 -0
  177. package/dist/vite/wave-gradient-shader.js +207 -0
  178. package/package.json +103 -25
  179. package/skills/egaki/SKILL.md +162 -1
  180. package/src/cli/cache-utils.ts +169 -0
  181. package/src/cli/cached-generate.test.ts +531 -0
  182. package/src/cli/cached-generate.ts +348 -0
  183. package/src/cli/cartesia-provider.ts +249 -0
  184. package/src/cli/cli.ts +1818 -0
  185. package/src/cli/clone-generate.ts +202 -0
  186. package/src/{credentials.ts → cli/credentials.ts} +90 -7
  187. package/src/cli/elevenlabs-speech-provider.ts +243 -0
  188. package/src/cli/generate.test.ts +166 -0
  189. package/src/cli/generate.ts +912 -0
  190. package/src/{login.ts → cli/login.ts} +48 -3
  191. package/src/cli/main.ts +8 -0
  192. package/src/cli/model-catalog.ts +2336 -0
  193. package/src/cli/models.ts +408 -0
  194. package/src/cli/openai-speech-provider.ts +34 -0
  195. package/src/cli/separation-generate.ts +187 -0
  196. package/src/cli/speech-catalog.ts +253 -0
  197. package/src/cli/speech-generate.ts +275 -0
  198. package/src/cli/speech-models.ts +146 -0
  199. package/src/cli/speech-provider.test.ts +226 -0
  200. package/src/cli/transcription-catalog.ts +250 -0
  201. package/src/cli/transcription-generate.ts +214 -0
  202. package/src/cli/transcription-models.ts +136 -0
  203. package/src/cli/transcription-providers.ts +427 -0
  204. package/src/cli/video-model-catalog.ts +14 -0
  205. package/src/cli/xai-auth.ts +396 -0
  206. package/src/vite/angled-screen.tsx +214 -0
  207. package/src/vite/app.tsx +197 -0
  208. package/src/vite/bands-shader.tsx +319 -0
  209. package/src/vite/code-block.tsx +939 -0
  210. package/src/vite/components.tsx +278 -0
  211. package/src/vite/dispersion-rings-shader.tsx +331 -0
  212. package/src/vite/easing-curves.test.ts +232 -0
  213. package/src/vite/easing-curves.ts +743 -0
  214. package/src/vite/generated-media-client.tsx +121 -0
  215. package/src/vite/keyframes.tsx +347 -0
  216. package/src/vite/layout-editor.tsx +709 -0
  217. package/src/vite/layout-transition.tsx +706 -0
  218. package/src/vite/liquid-gradient-shader.tsx +474 -0
  219. package/src/vite/mdx-client.tsx +420 -0
  220. package/src/vite/mdx-parse.ts +343 -0
  221. package/src/vite/mdx-provided-components.ts +19 -0
  222. package/src/vite/mdx-video.test.tsx +2219 -0
  223. package/src/vite/mdx-video.tsx +976 -0
  224. package/src/vite/media-components.tsx +473 -0
  225. package/src/vite/media-duration-store.ts +225 -0
  226. package/src/vite/motion-timing.ts +194 -0
  227. package/src/vite/player-page.tsx +1107 -0
  228. package/src/vite/render-client.ts +84 -0
  229. package/src/vite/sdk.ts +598 -0
  230. package/src/vite/server-components.tsx +241 -0
  231. package/src/vite/server-mdx.ts +285 -0
  232. package/src/vite/shader-renderer.tsx +535 -0
  233. package/src/vite/store-hooks.ts +117 -0
  234. package/src/vite/store.test.ts +202 -0
  235. package/src/vite/store.ts +115 -0
  236. package/src/vite/styles.css +21 -0
  237. package/src/vite/tweakpane-hook.tsx +593 -0
  238. package/src/vite/virtual-modules.d.ts +30 -0
  239. package/src/vite/vite-plugin.ts +584 -0
  240. package/src/vite/wave-gradient-shader.tsx +253 -0
  241. package/README.md +0 -266
  242. package/dist/antigravity-auth.d.ts +0 -15
  243. package/dist/antigravity-auth.d.ts.map +0 -1
  244. package/dist/antigravity-auth.js +0 -544
  245. package/dist/antigravity-image.d.ts +0 -11
  246. package/dist/antigravity-image.d.ts.map +0 -1
  247. package/dist/antigravity-image.js +0 -285
  248. package/dist/chatgpt-auth.d.ts.map +0 -1
  249. package/dist/cli.d.ts +0 -3
  250. package/dist/cli.d.ts.map +0 -1
  251. package/dist/cli.js +0 -968
  252. package/dist/credentials.d.ts.map +0 -1
  253. package/dist/gateway-model-candidates.generated.d.ts +0 -11
  254. package/dist/gateway-model-candidates.generated.d.ts.map +0 -1
  255. package/dist/gateway-model-candidates.generated.js +0 -64
  256. package/dist/login.d.ts.map +0 -1
  257. package/dist/model-catalog.d.ts +0 -42
  258. package/dist/model-catalog.d.ts.map +0 -1
  259. package/dist/model-catalog.js +0 -1001
  260. package/dist/models.d.ts.map +0 -1
  261. package/dist/models.js +0 -246
  262. package/dist/open-browser.d.ts.map +0 -1
  263. package/dist/subscription.d.ts.map +0 -1
  264. package/dist/video-model-catalog.d.ts +0 -52
  265. package/dist/video-model-catalog.d.ts.map +0 -1
  266. package/dist/video-model-catalog.js +0 -306
  267. package/src/cli.ts +0 -1378
  268. package/src/model-catalog.ts +0 -1077
  269. package/src/models.ts +0 -309
  270. package/src/video-model-catalog.ts +0 -363
  271. /package/dist/{chatgpt-auth.d.ts → cli/chatgpt-auth.d.ts} +0 -0
  272. /package/dist/{chatgpt-auth.js → cli/chatgpt-auth.js} +0 -0
  273. /package/dist/{login.d.ts → cli/login.d.ts} +0 -0
  274. /package/dist/{open-browser.d.ts → cli/open-browser.d.ts} +0 -0
  275. /package/dist/{open-browser.js → cli/open-browser.js} +0 -0
  276. /package/dist/{subscription.d.ts → cli/subscription.d.ts} +0 -0
  277. /package/dist/{subscription.js → cli/subscription.js} +0 -0
  278. /package/src/{chatgpt-auth.ts → cli/chatgpt-auth.ts} +0 -0
  279. /package/src/{open-browser.ts → cli/open-browser.ts} +0 -0
  280. /package/src/{subscription.ts → cli/subscription.ts} +0 -0
@@ -0,0 +1,535 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * WebGL2 shader component factory for egaki video.
5
+ *
6
+ * Egaki's equivalent of Framer's defineShader() API, adapted for Remotion's
7
+ * frame-based rendering. Time is derived from useCurrentFrame()/fps so shaders
8
+ * are deterministic and seekable. Property controls are exposed via useTweakpane
9
+ * for live editing in the player.
10
+ *
11
+ * Port of Framer's WebGL2 shader infrastructure. Original sources (in unframer):
12
+ * - defineShader(): unframer/src/framer.js:50569-50588
13
+ * - generateShaderHead(): unframer/src/framer.js:50493-50546
14
+ * - WebGL2ShaderRenderer: unframer/src/framer.js:49750-50346
15
+ * - ShaderCanvas: unframer/src/framer.js:51376-51593
16
+ * - DEFAULT_VERTEX_SHADER: unframer/src/framer.js:51184-51196
17
+ * - builtInUniforms: unframer/src/framer.js:49718-49747
18
+ * - setUniform(): unframer/src/framer.js:50224-50246
19
+ * - colorToVec4(): unframer/src/framer.js:50923-50927
20
+ * - bindFullScreenQuadAttribs: unframer/src/framer.js:50354-50366
21
+ *
22
+ * Key differences from Framer:
23
+ * - u_time = frame / fps (not wall-clock performance.now())
24
+ * - No requestAnimationFrame loop; one GL draw per Remotion frame
25
+ * - useTweakpane replaces Framer's ControlType property controls UI
26
+ * - No mouse input, heightmaps, or multi-pass buffers (can add later)
27
+ * - preserveDrawingBuffer=true for @remotion/web-renderer HtmlInCanvas
28
+ */
29
+
30
+ import { useCurrentFrame, useVideoConfig } from 'remotion'
31
+ import { useLayoutEffect, useRef, type CSSProperties } from 'react'
32
+ import { useTweakpane } from './tweakpane-hook.tsx'
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Types
36
+ // ---------------------------------------------------------------------------
37
+
38
+ export interface NumberControl {
39
+ type: 'number'
40
+ defaultValue: number
41
+ min: number
42
+ max: number
43
+ step: number
44
+ /** Hidden controls are not shown in the tweakpane UI but still set as uniforms. */
45
+ hidden?: boolean
46
+ }
47
+
48
+ export interface ColorArrayControl {
49
+ type: 'array'
50
+ control: { type: 'color' }
51
+ /** Maximum number of colors. Determines the GLSL array size. */
52
+ maxCount: number
53
+ /** Max color slots shown in tweakpane UI. Defaults to maxCount. */
54
+ maxVisible?: number
55
+ defaultValue: string[]
56
+ }
57
+
58
+ export type PropertyControl = NumberControl | ColorArrayControl
59
+
60
+ export interface ShaderConfig {
61
+ /** Display name shown in tweakpane and used as React displayName. */
62
+ title: string
63
+ /** Fragment shader body (everything after the auto-generated header). */
64
+ fragment: string
65
+ /** Property controls that become uniforms and tweakpane sliders. */
66
+ propertyControls: Record<string, PropertyControl>
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Color parsing
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /** Parse a hex color string (#RGB, #RRGGBB, #RRGGBBAA) to [r, g, b, a] in 0-1 range. */
74
+ function parseHexColor(hex: string): [number, number, number, number] {
75
+ let h = hex.replace('#', '')
76
+ if (h.length === 3 || h.length === 4) {
77
+ h = h
78
+ .split('')
79
+ .map((c) => c + c)
80
+ .join('')
81
+ }
82
+ const r = parseInt(h.slice(0, 2), 16) / 255
83
+ const g = parseInt(h.slice(2, 4), 16) / 255
84
+ const b = parseInt(h.slice(4, 6), 16) / 255
85
+ const a = h.length >= 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1
86
+ return [r, g, b, a]
87
+ }
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // GLSL constants
91
+ // ---------------------------------------------------------------------------
92
+
93
+ /**
94
+ * Fullscreen quad vertex shader. Two attributes:
95
+ * a_position — clip-space quad corners (2 triangles covering [-1,1]²)
96
+ * a_texCoord — UV coordinates [0,1]² passed as v_uv to the fragment shader
97
+ *
98
+ * Identical to Framer's DEFAULT_VERTEX_SHADER (framer.js:51184-51196).
99
+ */
100
+ const VERTEX_SHADER = `#version 300 es
101
+ precision highp float;
102
+ in vec2 a_position;
103
+ in vec2 a_texCoord;
104
+ out vec2 v_uv;
105
+ void main() {
106
+ v_uv = a_texCoord;
107
+ gl_Position = vec4(a_position, 0.0, 1.0);
108
+ }
109
+ `
110
+
111
+ /**
112
+ * Build the full fragment shader source by prepending uniform declarations
113
+ * and built-in uniforms before the user-provided shader body.
114
+ *
115
+ * Mirrors Framer's generateShaderHead() (framer.js:50493-50546).
116
+ * Property control keys are converted to uniform names with a `u_` prefix,
117
+ * matching Framer's toUniformName() convention (framer.js:50458-50459).
118
+ */
119
+ function generateFragmentSource(
120
+ body: string,
121
+ controls: Record<string, PropertyControl>,
122
+ ): string {
123
+ const lines: string[] = [
124
+ '#version 300 es',
125
+ 'precision highp float;',
126
+ '',
127
+ 'in vec2 v_uv;',
128
+ 'out vec4 fragColor;',
129
+ ]
130
+
131
+ // #define for array max counts (Framer's toArrayMaxLengthName, framer.js:50470-50473)
132
+ for (const [key, ctrl] of Object.entries(controls)) {
133
+ if (ctrl.type === 'array') {
134
+ const constName = key.replace(/[a-z0-9](?=[A-Z])/g, '$&_').toUpperCase()
135
+ lines.push('', `#define NUM_${constName} ${ctrl.maxCount}`)
136
+ }
137
+ }
138
+
139
+ lines.push('')
140
+
141
+ // Uniform declarations from property controls
142
+ for (const [key, ctrl] of Object.entries(controls)) {
143
+ const uName = `u_${key}`
144
+ if (ctrl.type === 'array') {
145
+ const constName = key.replace(/[a-z0-9](?=[A-Z])/g, '$&_').toUpperCase()
146
+ lines.push(`uniform vec4 ${uName}[NUM_${constName}];`)
147
+ lines.push(`uniform int ${uName}_length;`)
148
+ } else {
149
+ lines.push(`uniform float ${uName};`)
150
+ }
151
+ }
152
+
153
+ lines.push('')
154
+
155
+ // Built-in uniforms (Framer's builtInUniforms, framer.js:49718-49747)
156
+ lines.push(
157
+ 'uniform float u_time;',
158
+ 'uniform vec2 u_resolution;',
159
+ 'uniform float u_deltaTime;',
160
+ 'uniform float u_pixelRatio;',
161
+ 'uniform vec4 u_mousePosition;',
162
+ 'uniform float u_mousePointerDown;',
163
+ 'uniform float u_mouseHover;',
164
+ '',
165
+ )
166
+
167
+ lines.push(body)
168
+ return lines.join('\n')
169
+ }
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // WebGL helpers
173
+ // ---------------------------------------------------------------------------
174
+
175
+ function compileGlShader(
176
+ gl: WebGL2RenderingContext,
177
+ type: number,
178
+ source: string,
179
+ ): WebGLShader {
180
+ const shader = gl.createShader(type)
181
+ if (!shader) throw new Error('Failed to create shader')
182
+ gl.shaderSource(shader, source)
183
+ gl.compileShader(shader)
184
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
185
+ const log = gl.getShaderInfoLog(shader)
186
+ gl.deleteShader(shader)
187
+ throw new Error(`Shader compile error:\n${log}`)
188
+ }
189
+ return shader
190
+ }
191
+
192
+ function linkGlProgram(
193
+ gl: WebGL2RenderingContext,
194
+ vs: WebGLShader,
195
+ fs: WebGLShader,
196
+ ): WebGLProgram {
197
+ const program = gl.createProgram()
198
+ if (!program) throw new Error('Failed to create program')
199
+ gl.attachShader(program, vs)
200
+ gl.attachShader(program, fs)
201
+ gl.linkProgram(program)
202
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
203
+ const log = gl.getProgramInfoLog(program)
204
+ gl.deleteProgram(program)
205
+ throw new Error(`Program link error:\n${log}`)
206
+ }
207
+ return program
208
+ }
209
+
210
+ // Pre-computed metadata for hot-path uniform updates during draw
211
+ interface NumberMeta {
212
+ key: string
213
+ uniformName: string
214
+ defaultValue: number
215
+ hidden: boolean
216
+ }
217
+ interface ArrayMeta {
218
+ key: string
219
+ uniformName: string
220
+ lengthName: string
221
+ maxCount: number
222
+ }
223
+
224
+ const BUILTIN_UNIFORM_NAMES = [
225
+ 'u_time',
226
+ 'u_resolution',
227
+ 'u_deltaTime',
228
+ 'u_pixelRatio',
229
+ 'u_mousePosition',
230
+ 'u_mousePointerDown',
231
+ 'u_mouseHover',
232
+ ] as const
233
+
234
+ interface GlState {
235
+ gl: WebGL2RenderingContext
236
+ program: WebGLProgram
237
+ vao: WebGLVertexArrayObject
238
+ posBuf: WebGLBuffer
239
+ texBuf: WebGLBuffer
240
+ locs: Record<string, WebGLUniformLocation | null>
241
+ }
242
+
243
+ // ---------------------------------------------------------------------------
244
+ // defineShader
245
+ // ---------------------------------------------------------------------------
246
+
247
+ /**
248
+ * Create a React component that renders a WebGL2 fragment shader full-screen.
249
+ *
250
+ * The returned component accepts all property control keys as optional props
251
+ * plus a `style` prop. Number controls are exposed in tweakpane as sliders
252
+ * (unless `hidden: true`). Color array items are exposed as individual
253
+ * tweakpane color pickers with swatch UI.
254
+ *
255
+ * Time and resolution are derived from Remotion's hooks, making the shader
256
+ * fully deterministic and seekable.
257
+ */
258
+ export function defineShader(config: ShaderConfig) {
259
+ const fragmentSource = generateFragmentSource(
260
+ config.fragment,
261
+ config.propertyControls,
262
+ )
263
+
264
+ // Pre-compute per-control metadata so the draw path avoids Object.entries()
265
+ const numberMetas: NumberMeta[] = []
266
+ const arrayMetas: ArrayMeta[] = []
267
+ const tweakpaneDefaults: Record<
268
+ string,
269
+ { value: number; min: number; max: number; step: number }
270
+ > = {}
271
+ const arrayDefaults: Record<string, string[]> = {}
272
+
273
+ for (const [key, ctrl] of Object.entries(config.propertyControls)) {
274
+ if (ctrl.type === 'number') {
275
+ numberMetas.push({
276
+ key,
277
+ uniformName: `u_${key}`,
278
+ defaultValue: ctrl.defaultValue,
279
+ hidden: !!ctrl.hidden,
280
+ })
281
+ if (!ctrl.hidden) {
282
+ tweakpaneDefaults[key] = {
283
+ value: ctrl.defaultValue,
284
+ min: ctrl.min,
285
+ max: ctrl.max,
286
+ step: ctrl.step,
287
+ }
288
+ }
289
+ } else if (ctrl.type === 'array') {
290
+ arrayMetas.push({
291
+ key,
292
+ uniformName: `u_${key}`,
293
+ lengthName: `u_${key}_length`,
294
+ maxCount: ctrl.maxCount,
295
+ })
296
+ arrayDefaults[key] = ctrl.defaultValue
297
+ }
298
+ }
299
+
300
+ // Build tweakpane keys for color array items. Each color slot up to maxCount
301
+ // becomes an individual tweakpane color picker (e.g. "colors 1", "colors 2").
302
+ // A "{key} count" slider controls how many are active (sent as u_{key}_length).
303
+ // Tweakpane auto-detects #hex strings as color inputs with a swatch.
304
+ const arrayTweakpaneKeys: {
305
+ arrayKey: string
306
+ countKey: string
307
+ tpKeys: string[]
308
+ defaults: string[]
309
+ maxCount: number
310
+ }[] = []
311
+ for (const m of arrayMetas) {
312
+ const ctrl = config.propertyControls[m.key] as ColorArrayControl
313
+ const visible = ctrl.maxVisible ?? ctrl.maxCount
314
+ const tpKeys: string[] = []
315
+ for (let i = 0; i < visible; i++) {
316
+ tpKeys.push(`${m.key} ${i + 1}`)
317
+ }
318
+ arrayTweakpaneKeys.push({
319
+ arrayKey: m.key,
320
+ countKey: `${m.key} count`,
321
+ tpKeys,
322
+ defaults: ctrl.defaultValue,
323
+ maxCount: ctrl.maxCount,
324
+ })
325
+ }
326
+
327
+ function ShaderComponent(
328
+ props: Record<string, any> & { style?: CSSProperties },
329
+ ) {
330
+ const frame = useCurrentFrame()
331
+ const { fps } = useVideoConfig()
332
+ const time = frame / fps
333
+
334
+ const canvasRef = useRef<HTMLCanvasElement>(null)
335
+ const glStateRef = useRef<GlState | null>(null)
336
+
337
+ // Build tweakpane schema with props overriding defaults.
338
+ // Number controls become sliders; color array items become individual
339
+ // color pickers (tweakpane auto-detects #hex strings as color inputs).
340
+ const tpSchema: Record<string, any> = {}
341
+ for (const [key, base] of Object.entries(tweakpaneDefaults)) {
342
+ tpSchema[key] = { ...base, value: props[key] ?? base.value }
343
+ }
344
+ for (const { arrayKey, countKey, tpKeys, defaults } of arrayTweakpaneKeys) {
345
+ const propColors: string[] | undefined = props[arrayKey]
346
+ const activeCount = Math.min(propColors?.length ?? defaults.length, tpKeys.length)
347
+ tpSchema[countKey] = { value: activeCount, min: 1, max: tpKeys.length, step: 1 }
348
+ for (let i = 0; i < tpKeys.length; i++) {
349
+ tpSchema[tpKeys[i]!] = propColors?.[i] ?? defaults[i] ?? '#000000'
350
+ }
351
+ }
352
+ const tp = useTweakpane(config.title, tpSchema)
353
+
354
+ // Initialize WebGL2 on mount (useLayoutEffect so the first frame draws)
355
+ useLayoutEffect(() => {
356
+ const canvas = canvasRef.current
357
+ if (!canvas) return
358
+
359
+ const gl = canvas.getContext('webgl2', {
360
+ alpha: true,
361
+ premultipliedAlpha: false,
362
+ antialias: false,
363
+ // Must be true for @remotion/web-renderer HtmlInCanvas screenshots
364
+ preserveDrawingBuffer: true,
365
+ })
366
+ if (!gl) {
367
+ console.error(`[${config.title}] WebGL2 not supported`)
368
+ return
369
+ }
370
+
371
+ let vs: WebGLShader | null = null
372
+ let fs: WebGLShader | null = null
373
+ try {
374
+ vs = compileGlShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)
375
+ fs = compileGlShader(gl, gl.FRAGMENT_SHADER, fragmentSource)
376
+ const program = linkGlProgram(gl, vs, fs)
377
+ // Shaders can be deleted after successful link
378
+ gl.deleteShader(vs)
379
+ gl.deleteShader(fs)
380
+ vs = null
381
+ fs = null
382
+
383
+ // Fullscreen quad VAO (2 triangles, 6 vertices)
384
+ // Position/texCoord data matches Framer's createStaticArrayBuffer (framer.js:50347-50352)
385
+ const vao = gl.createVertexArray()!
386
+ gl.bindVertexArray(vao)
387
+
388
+ const posBuf = gl.createBuffer()!
389
+ gl.bindBuffer(gl.ARRAY_BUFFER, posBuf)
390
+ // prettier-ignore
391
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
392
+ -1, -1, 1, -1, -1, 1,
393
+ -1, 1, 1, -1, 1, 1,
394
+ ]), gl.STATIC_DRAW)
395
+ const posLoc = gl.getAttribLocation(program, 'a_position')
396
+ if (posLoc >= 0) {
397
+ gl.enableVertexAttribArray(posLoc)
398
+ gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0)
399
+ }
400
+
401
+ const texBuf = gl.createBuffer()!
402
+ gl.bindBuffer(gl.ARRAY_BUFFER, texBuf)
403
+ // prettier-ignore
404
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
405
+ 0, 0, 1, 0, 0, 1,
406
+ 0, 1, 1, 0, 1, 1,
407
+ ]), gl.STATIC_DRAW)
408
+ const texLoc = gl.getAttribLocation(program, 'a_texCoord')
409
+ if (texLoc >= 0) {
410
+ gl.enableVertexAttribArray(texLoc)
411
+ gl.vertexAttribPointer(texLoc, 2, gl.FLOAT, false, 0, 0)
412
+ }
413
+
414
+ gl.bindVertexArray(null)
415
+
416
+ // Look up all uniform locations once
417
+ const locs: Record<string, WebGLUniformLocation | null> = {}
418
+ for (const name of BUILTIN_UNIFORM_NAMES) {
419
+ locs[name] = gl.getUniformLocation(program, name)
420
+ }
421
+ for (const m of numberMetas) {
422
+ locs[m.uniformName] = gl.getUniformLocation(program, m.uniformName)
423
+ }
424
+ for (const m of arrayMetas) {
425
+ locs[m.uniformName] = gl.getUniformLocation(program, m.uniformName)
426
+ locs[m.lengthName] = gl.getUniformLocation(program, m.lengthName)
427
+ }
428
+
429
+ // Bind program + VAO for all subsequent draws (single-pass hot path,
430
+ // mirrors Framer's constructor binding in framer.js:49795-49797)
431
+ gl.useProgram(program)
432
+ gl.bindVertexArray(vao)
433
+ gl.clearColor(0, 0, 0, 0)
434
+
435
+ glStateRef.current = { gl, program, vao, posBuf, texBuf, locs }
436
+ } catch (e) {
437
+ console.error(`[${config.title}] Shader init failed:`, e)
438
+ if (vs) gl.deleteShader(vs)
439
+ if (fs) gl.deleteShader(fs)
440
+ }
441
+
442
+ return () => {
443
+ const state = glStateRef.current
444
+ if (state) {
445
+ const { gl: g, program, vao, posBuf, texBuf } = state
446
+ g.deleteProgram(program)
447
+ g.deleteVertexArray(vao)
448
+ g.deleteBuffer(posBuf)
449
+ g.deleteBuffer(texBuf)
450
+ glStateRef.current = null
451
+ }
452
+ }
453
+ }, [])
454
+
455
+ // Draw every Remotion frame. No deps = runs on every render, which is
456
+ // triggered by useCurrentFrame() changing each frame.
457
+ useLayoutEffect(() => {
458
+ const state = glStateRef.current
459
+ const canvas = canvasRef.current
460
+ if (!state || !canvas) return
461
+
462
+ const { gl, locs } = state
463
+ const dpr =
464
+ typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1
465
+ const w = Math.round(canvas.clientWidth * dpr)
466
+ const h = Math.round(canvas.clientHeight * dpr)
467
+
468
+ // Resize backing buffer to match CSS size × DPR
469
+ if (canvas.width !== w || canvas.height !== h) {
470
+ canvas.width = w
471
+ canvas.height = h
472
+ gl.viewport(0, 0, w, h)
473
+ }
474
+
475
+ // Built-in uniforms (mirrors updatePassBuiltIns, framer.js:50318-50344)
476
+ const loc = locs
477
+ if (loc.u_time !== null) gl.uniform1f(loc.u_time!, time)
478
+ if (loc.u_resolution !== null) gl.uniform2f(loc.u_resolution!, w, h)
479
+ if (loc.u_deltaTime !== null) gl.uniform1f(loc.u_deltaTime!, 1 / fps)
480
+ if (loc.u_pixelRatio !== null) gl.uniform1f(loc.u_pixelRatio!, dpr)
481
+ if (loc.u_mousePosition !== null)
482
+ gl.uniform4fv(loc.u_mousePosition!, [0, 0, 0, 0])
483
+ if (loc.u_mousePointerDown !== null)
484
+ gl.uniform1f(loc.u_mousePointerDown!, 0)
485
+ if (loc.u_mouseHover !== null) gl.uniform1f(loc.u_mouseHover!, 0)
486
+
487
+ // Number control uniforms (from tweakpane for visible, props for hidden)
488
+ for (const m of numberMetas) {
489
+ const l = loc[m.uniformName]
490
+ if (l == null) continue
491
+ const value = m.hidden
492
+ ? (props[m.key] ?? m.defaultValue)
493
+ : ((tp as any)[m.key] ?? m.defaultValue)
494
+ gl.uniform1f(l, value)
495
+ }
496
+
497
+ // Color array uniforms — read count + individual colors from tweakpane
498
+ for (const { arrayKey, countKey, tpKeys } of arrayTweakpaneKeys) {
499
+ const m = arrayMetas.find((am) => am.key === arrayKey)!
500
+ const count = (tp as any)[countKey] as number
501
+ const flat: number[] = []
502
+ for (let i = 0; i < m.maxCount; i++) {
503
+ if (i < tpKeys.length) {
504
+ flat.push(...parseHexColor((tp as any)[tpKeys[i]!] ?? '#000000'))
505
+ } else {
506
+ flat.push(0, 0, 0, 0)
507
+ }
508
+ }
509
+ const uLoc = loc[m.uniformName]
510
+ if (uLoc != null) gl.uniform4fv(uLoc, flat)
511
+ const lLoc = loc[m.lengthName]
512
+ if (lLoc != null) gl.uniform1i(lLoc, count)
513
+ }
514
+
515
+ // Draw the fullscreen quad
516
+ gl.clear(gl.COLOR_BUFFER_BIT)
517
+ gl.drawArrays(gl.TRIANGLES, 0, 6)
518
+ })
519
+
520
+ return (
521
+ <canvas
522
+ ref={canvasRef}
523
+ style={{
524
+ display: 'block',
525
+ width: '100%',
526
+ height: '100%',
527
+ ...props.style,
528
+ }}
529
+ />
530
+ )
531
+ }
532
+
533
+ ShaderComponent.displayName = config.title
534
+ return ShaderComponent
535
+ }
@@ -0,0 +1,117 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * React hooks for the egaki client state store.
5
+ *
6
+ * Separated from store.ts so the vanilla store has no React dependency and
7
+ * can be imported from any environment (RSC, SSR, client). These hooks are
8
+ * only imported by 'use client' components.
9
+ *
10
+ * Uses useSyncExternalStore with stable module-level selectors. This avoids
11
+ * zustand's useStore + useShallow which internally calls useRef and fails
12
+ * during Spiceflow's SSR pass when the Vite SSR optimizer creates a split
13
+ * React instance (optimized react-dom/server.edge vs real pnpm react).
14
+ */
15
+
16
+ import { useSyncExternalStore } from 'react'
17
+ import {
18
+ egakiStore,
19
+ selectMediaDurations,
20
+ selectGenerationStatus,
21
+ type GenerationStatus,
22
+ type GenerationError,
23
+ } from './store.ts'
24
+
25
+ // Same as safe-mdx's EagerModules
26
+ type EagerModules = Record<string, Record<string, any>>
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Stable subscribe/getSnapshot functions (module-level constants so
30
+ // useSyncExternalStore never re-subscribes on re-render).
31
+ //
32
+ // getSnapshot must return a referentially stable value when the derived
33
+ // result hasn't changed. useSyncExternalStore compares snapshots with
34
+ // Object.is(), so returning a new object every call causes infinite
35
+ // re-render loops. We cache the last snapshot and shallow-compare.
36
+ // ---------------------------------------------------------------------------
37
+
38
+ function shallowEqualRecord(a: Record<string, number>, b: Record<string, number>): boolean {
39
+ const keysA = Object.keys(a)
40
+ const keysB = Object.keys(b)
41
+ if (keysA.length !== keysB.length) return false
42
+ for (const key of keysA) {
43
+ if (a[key] !== b[key]) return false
44
+ }
45
+ return true
46
+ }
47
+
48
+ function shallowEqualStatus(a: GenerationStatus | null, b: GenerationStatus | null): boolean {
49
+ if (a === b) return true
50
+ if (a === null || b === null) return false
51
+ if (a.total !== b.total) return false
52
+ return shallowEqualRecord(a.counts, b.counts)
53
+ }
54
+
55
+ // Media durations
56
+ const subscribeMediaDurations = (callback: () => void) =>
57
+ egakiStore.subscribe((state) => state.sectionReports, callback)
58
+ let cachedMediaDurations: Record<string, number> = {}
59
+ const getMediaDurations = () => {
60
+ const next = selectMediaDurations(egakiStore.getState())
61
+ if (!shallowEqualRecord(cachedMediaDurations, next)) {
62
+ cachedMediaDurations = next
63
+ }
64
+ return cachedMediaDurations
65
+ }
66
+ const getMediaDurationsServer = () => cachedMediaDurations
67
+
68
+ // Generation status
69
+ const subscribeGenerationStatus = (callback: () => void) =>
70
+ egakiStore.subscribe((state) => state.serverGenerationStatus, callback)
71
+ let cachedGenerationStatus: GenerationStatus | null = null
72
+ const getGenerationStatus = () => {
73
+ const next = selectGenerationStatus(egakiStore.getState())
74
+ if (!shallowEqualStatus(cachedGenerationStatus, next)) {
75
+ cachedGenerationStatus = next
76
+ }
77
+ return cachedGenerationStatus
78
+ }
79
+ const getGenerationStatusServer = () => null
80
+
81
+ // Generation errors
82
+ const subscribeGenerationErrors = (callback: () => void) =>
83
+ egakiStore.subscribe((state) => state.serverGenerationErrors, callback)
84
+ const getGenerationErrors = () => egakiStore.getState().serverGenerationErrors
85
+ const EMPTY_ERRORS: GenerationError[] = []
86
+ const getGenerationErrorsServer = () => EMPTY_ERRORS
87
+
88
+ // Modules
89
+ const subscribeModules = (callback: () => void) =>
90
+ egakiStore.subscribe((state) => state.modules, callback)
91
+ const getModules = () => egakiStore.getState().modules
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Hooks
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /** Hook: per-section max media durations. */
98
+ export function useMediaDurations(): Record<string, number> {
99
+ return useSyncExternalStore(subscribeMediaDurations, getMediaDurations, getMediaDurationsServer)
100
+ }
101
+
102
+ /** Hook: generation counts while media is being generated, or null. */
103
+ export function useGenerationStatus(): GenerationStatus | null {
104
+ return useSyncExternalStore(subscribeGenerationStatus, getGenerationStatus, getGenerationStatusServer)
105
+ }
106
+
107
+ /** Hook: recent generation errors (auto-cleared after 8s). */
108
+ export function useGenerationErrors(): GenerationError[] {
109
+ return useSyncExternalStore(subscribeGenerationErrors, getGenerationErrors, getGenerationErrorsServer)
110
+ }
111
+
112
+ /** Hook: user modules from virtual:egaki-modules. */
113
+ export function useModules(): EagerModules {
114
+ return useSyncExternalStore(subscribeModules, getModules, getModules)
115
+ }
116
+
117
+ export type { GenerationStatus, GenerationError }