@basementstudio/shader-lab 0.1.0 → 1.0.2

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 (399) hide show
  1. package/dist/src/easings.d.ts +3 -0
  2. package/dist/src/easings.js +3 -0
  3. package/dist/src/index.d.ts +5 -0
  4. package/dist/src/index.js +4 -0
  5. package/dist/src/lib/editor/custom-shader/shared.d.ts +1 -0
  6. package/dist/src/lib/editor/custom-shader/shared.js +1 -0
  7. package/dist/src/renderer/ascii-atlas.d.ts +5 -0
  8. package/dist/src/renderer/ascii-atlas.js +61 -0
  9. package/dist/src/renderer/ascii-pass.d.ts +50 -0
  10. package/dist/src/renderer/ascii-pass.js +271 -0
  11. package/dist/src/renderer/asset-url.d.ts +1 -0
  12. package/dist/src/renderer/asset-url.js +3 -0
  13. package/dist/src/renderer/blend-modes.d.ts +4 -0
  14. package/dist/src/renderer/blend-modes.js +157 -0
  15. package/dist/src/renderer/contracts.d.ts +26 -0
  16. package/dist/src/renderer/contracts.js +13 -0
  17. package/dist/src/renderer/create-webgpu-renderer.d.ts +3 -0
  18. package/dist/src/renderer/create-webgpu-renderer.js +37 -0
  19. package/dist/src/renderer/crt-pass.d.ts +72 -0
  20. package/dist/src/renderer/crt-pass.js +536 -0
  21. package/dist/src/renderer/custom-shader-pass.d.ts +17 -0
  22. package/dist/src/renderer/custom-shader-pass.js +79 -0
  23. package/dist/src/renderer/custom-shader-runtime.d.ts +16 -0
  24. package/dist/src/renderer/custom-shader-runtime.js +169 -0
  25. package/dist/src/renderer/dither-textures.d.ts +8 -0
  26. package/dist/src/renderer/dither-textures.js +66 -0
  27. package/dist/src/renderer/dithering-pass.d.ts +45 -0
  28. package/dist/src/renderer/dithering-pass.js +229 -0
  29. package/dist/src/renderer/gradient-pass.d.ts +39 -0
  30. package/dist/src/renderer/gradient-pass.js +358 -0
  31. package/dist/src/renderer/halftone-pass.d.ts +65 -0
  32. package/dist/src/renderer/halftone-pass.js +530 -0
  33. package/dist/src/renderer/ink-pass.d.ts +84 -0
  34. package/dist/src/renderer/ink-pass.js +526 -0
  35. package/dist/src/renderer/live-pass.d.ts +31 -0
  36. package/dist/src/renderer/live-pass.js +136 -0
  37. package/dist/src/renderer/media-pass.d.ts +32 -0
  38. package/dist/src/renderer/media-pass.js +130 -0
  39. package/dist/src/renderer/media-texture.d.ts +8 -0
  40. package/dist/src/renderer/media-texture.js +41 -0
  41. package/dist/src/renderer/particle-grid-pass.d.ts +48 -0
  42. package/dist/src/renderer/particle-grid-pass.js +269 -0
  43. package/dist/src/renderer/pass-node.d.ts +36 -0
  44. package/dist/src/renderer/pass-node.js +120 -0
  45. package/dist/src/renderer/pattern-atlas.d.ts +8 -0
  46. package/dist/src/renderer/pattern-atlas.js +79 -0
  47. package/dist/src/renderer/pattern-pass.d.ts +58 -0
  48. package/dist/src/renderer/pattern-pass.js +316 -0
  49. package/dist/src/renderer/pipeline-manager.d.ts +39 -0
  50. package/dist/src/renderer/pipeline-manager.js +287 -0
  51. package/dist/src/renderer/pixel-sorting-pass.d.ts +33 -0
  52. package/dist/src/renderer/pixel-sorting-pass.js +179 -0
  53. package/dist/src/renderer/shaders/tsl/color/tonemapping.d.ts +9 -0
  54. package/dist/src/renderer/shaders/tsl/color/tonemapping.js +59 -0
  55. package/dist/src/renderer/shaders/tsl/cosine-palette.d.ts +4 -0
  56. package/{packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts → dist/src/renderer/shaders/tsl/cosine-palette.js} +3 -4
  57. package/dist/src/renderer/shaders/tsl/noise/common.d.ts +5 -0
  58. package/dist/src/renderer/shaders/tsl/noise/common.js +24 -0
  59. package/dist/src/renderer/shaders/tsl/noise/curl-noise-3d.d.ts +1 -0
  60. package/dist/src/renderer/shaders/tsl/noise/curl-noise-3d.js +27 -0
  61. package/dist/src/renderer/shaders/tsl/noise/curl-noise-4d.d.ts +1 -0
  62. package/dist/src/renderer/shaders/tsl/noise/curl-noise-4d.js +27 -0
  63. package/dist/src/renderer/shaders/tsl/noise/fbm.d.ts +1 -0
  64. package/dist/src/renderer/shaders/tsl/noise/fbm.js +11 -0
  65. package/dist/src/renderer/shaders/tsl/noise/perlin-noise-3d.d.ts +5 -0
  66. package/dist/src/renderer/shaders/tsl/noise/perlin-noise-3d.js +66 -0
  67. package/dist/src/renderer/shaders/tsl/noise/ridge-noise.d.ts +1 -0
  68. package/dist/src/renderer/shaders/tsl/noise/ridge-noise.js +19 -0
  69. package/dist/src/renderer/shaders/tsl/noise/simplex-noise-3d.d.ts +1 -0
  70. package/dist/src/renderer/shaders/tsl/noise/simplex-noise-3d.js +44 -0
  71. package/dist/src/renderer/shaders/tsl/noise/simplex-noise-4d.d.ts +1 -0
  72. package/dist/src/renderer/shaders/tsl/noise/simplex-noise-4d.js +51 -0
  73. package/dist/src/renderer/shaders/tsl/noise/turbulence.d.ts +7 -0
  74. package/dist/src/renderer/shaders/tsl/noise/turbulence.js +34 -0
  75. package/dist/src/renderer/shaders/tsl/noise/value-noise-3d.d.ts +1 -0
  76. package/dist/src/renderer/shaders/tsl/noise/value-noise-3d.js +27 -0
  77. package/dist/src/renderer/shaders/tsl/noise/voronoi-noise-3d.d.ts +1 -0
  78. package/dist/src/renderer/shaders/tsl/noise/voronoi-noise-3d.js +45 -0
  79. package/dist/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.d.ts +4 -0
  80. package/dist/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.js +13 -0
  81. package/dist/src/renderer/shaders/tsl/patterns/bloom.d.ts +4 -0
  82. package/{packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts → dist/src/renderer/shaders/tsl/patterns/bloom.js} +4 -6
  83. package/dist/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.d.ts +4 -0
  84. package/dist/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.js +16 -0
  85. package/dist/src/renderer/shaders/tsl/patterns/grain-texture-pattern.d.ts +4 -0
  86. package/{packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts → dist/src/renderer/shaders/tsl/patterns/grain-texture-pattern.js} +3 -4
  87. package/dist/src/renderer/shaders/tsl/patterns/repeating-pattern.d.ts +4 -0
  88. package/{packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts → dist/src/renderer/shaders/tsl/patterns/repeating-pattern.js} +4 -6
  89. package/dist/src/renderer/shaders/tsl/utils/atan2.d.ts +1 -0
  90. package/dist/src/renderer/shaders/tsl/utils/atan2.js +7 -0
  91. package/dist/src/renderer/shaders/tsl/utils/complex-conj.d.ts +4 -0
  92. package/{packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts → dist/src/renderer/shaders/tsl/utils/complex-conj.js} +3 -4
  93. package/dist/src/renderer/shaders/tsl/utils/complex-cos.d.ts +4 -0
  94. package/dist/src/renderer/shaders/tsl/utils/complex-cos.js +9 -0
  95. package/dist/src/renderer/shaders/tsl/utils/complex-div.d.ts +1 -0
  96. package/dist/src/renderer/shaders/tsl/utils/complex-div.js +6 -0
  97. package/dist/src/renderer/shaders/tsl/utils/complex-log.d.ts +1 -0
  98. package/dist/src/renderer/shaders/tsl/utils/complex-log.js +6 -0
  99. package/dist/src/renderer/shaders/tsl/utils/complex-mobius.d.ts +4 -0
  100. package/dist/src/renderer/shaders/tsl/utils/complex-mobius.js +10 -0
  101. package/dist/src/renderer/shaders/tsl/utils/complex-mul.d.ts +4 -0
  102. package/{packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts → dist/src/renderer/shaders/tsl/utils/complex-mul.js} +3 -4
  103. package/dist/src/renderer/shaders/tsl/utils/complex-pow.d.ts +5 -0
  104. package/dist/src/renderer/shaders/tsl/utils/complex-pow.js +14 -0
  105. package/dist/src/renderer/shaders/tsl/utils/complex-sin.d.ts +4 -0
  106. package/dist/src/renderer/shaders/tsl/utils/complex-sin.js +9 -0
  107. package/dist/src/renderer/shaders/tsl/utils/complex-sqrt.d.ts +5 -0
  108. package/dist/src/renderer/shaders/tsl/utils/complex-sqrt.js +12 -0
  109. package/dist/src/renderer/shaders/tsl/utils/complex-tan.d.ts +4 -0
  110. package/dist/src/renderer/shaders/tsl/utils/complex-tan.js +11 -0
  111. package/dist/src/renderer/shaders/tsl/utils/complex-to-polar.d.ts +4 -0
  112. package/dist/src/renderer/shaders/tsl/utils/complex-to-polar.js +9 -0
  113. package/dist/src/renderer/shaders/tsl/utils/hyperbolic.d.ts +8 -0
  114. package/{packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts → dist/src/renderer/shaders/tsl/utils/hyperbolic.js} +7 -11
  115. package/dist/src/renderer/shaders/tsl/utils/index.d.ts +38 -0
  116. package/dist/src/renderer/shaders/tsl/utils/index.js +39 -0
  117. package/dist/src/renderer/shaders/tsl/utils/rotate.d.ts +2 -0
  118. package/dist/src/renderer/shaders/tsl/utils/rotate.js +7 -0
  119. package/dist/src/renderer/shaders/tsl/utils/screen-aspect-uv.d.ts +2 -0
  120. package/dist/src/renderer/shaders/tsl/utils/screen-aspect-uv.js +6 -0
  121. package/dist/src/renderer/shaders/tsl/utils/sd-box-2d.d.ts +1 -0
  122. package/dist/src/renderer/shaders/tsl/utils/sd-box-2d.js +5 -0
  123. package/dist/src/renderer/shaders/tsl/utils/sd-diamond.d.ts +1 -0
  124. package/dist/src/renderer/shaders/tsl/utils/sd-diamond.js +5 -0
  125. package/dist/src/renderer/shaders/tsl/utils/sd-rhombus.d.ts +1 -0
  126. package/dist/src/renderer/shaders/tsl/utils/sd-rhombus.js +14 -0
  127. package/dist/src/renderer/shaders/tsl/utils/sd-sphere.d.ts +1 -0
  128. package/dist/src/renderer/shaders/tsl/utils/sd-sphere.js +5 -0
  129. package/dist/src/renderer/shaders/tsl/utils/smax.d.ts +1 -0
  130. package/dist/src/renderer/shaders/tsl/utils/smax.js +6 -0
  131. package/dist/src/renderer/shaders/tsl/utils/smin.d.ts +1 -0
  132. package/dist/src/renderer/shaders/tsl/utils/smin.js +6 -0
  133. package/dist/src/renderer/text-pass.d.ts +23 -0
  134. package/dist/src/renderer/text-pass.js +129 -0
  135. package/dist/src/runtime-clock.d.ts +9 -0
  136. package/dist/src/runtime-clock.js +20 -0
  137. package/dist/src/runtime-frame.d.ts +11 -0
  138. package/dist/src/runtime-frame.js +12 -0
  139. package/dist/src/shader-lab-composition.d.ts +9 -0
  140. package/dist/src/shader-lab-composition.js +96 -0
  141. package/dist/src/timeline.d.ts +8 -0
  142. package/dist/src/timeline.js +179 -0
  143. package/dist/src/types/editor.d.ts +3 -0
  144. package/dist/src/types/editor.js +1 -0
  145. package/dist/src/types.d.ts +81 -0
  146. package/dist/src/types.js +1 -0
  147. package/package.json +28 -65
  148. package/.biome/plugins/README.md +0 -21
  149. package/.biome/plugins/no-anchor-element.grit +0 -12
  150. package/.biome/plugins/no-relative-parent-imports.grit +0 -10
  151. package/.biome/plugins/no-unnecessary-forwardref.grit +0 -9
  152. package/.changeset/README.md +0 -17
  153. package/.changeset/config.json +0 -11
  154. package/.editorconfig +0 -40
  155. package/.env.example +0 -81
  156. package/.gitattributes +0 -19
  157. package/.github/workflows/canary.yml +0 -80
  158. package/.github/workflows/ci.yml +0 -37
  159. package/.github/workflows/release.yml +0 -56
  160. package/.tldrignore +0 -84
  161. package/.vscode/extensions.json +0 -20
  162. package/.vscode/settings.json +0 -105
  163. package/biome.json +0 -249
  164. package/bun.lock +0 -1224
  165. package/next.config.ts +0 -131
  166. package/packages/shader-lab-react/CHANGELOG.md +0 -9
  167. package/packages/shader-lab-react/README.md +0 -119
  168. package/packages/shader-lab-react/package.json +0 -36
  169. package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +0 -57
  170. package/packages/shader-lab-react/scripts/prepare-dist.mjs +0 -4
  171. package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +0 -146
  172. package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +0 -51
  173. package/packages/shader-lab-react/src/easings.ts +0 -4
  174. package/packages/shader-lab-react/src/index.ts +0 -35
  175. package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +0 -2
  176. package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +0 -83
  177. package/packages/shader-lab-react/src/renderer/ascii-pass.ts +0 -416
  178. package/packages/shader-lab-react/src/renderer/asset-url.ts +0 -3
  179. package/packages/shader-lab-react/src/renderer/blend-modes.ts +0 -229
  180. package/packages/shader-lab-react/src/renderer/contracts.ts +0 -54
  181. package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +0 -48
  182. package/packages/shader-lab-react/src/renderer/crt-pass.ts +0 -1040
  183. package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +0 -108
  184. package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +0 -309
  185. package/packages/shader-lab-react/src/renderer/dither-textures.ts +0 -99
  186. package/packages/shader-lab-react/src/renderer/dithering-pass.ts +0 -322
  187. package/packages/shader-lab-react/src/renderer/gradient-pass.ts +0 -521
  188. package/packages/shader-lab-react/src/renderer/halftone-pass.ts +0 -932
  189. package/packages/shader-lab-react/src/renderer/ink-pass.ts +0 -802
  190. package/packages/shader-lab-react/src/renderer/live-pass.ts +0 -194
  191. package/packages/shader-lab-react/src/renderer/media-pass.ts +0 -187
  192. package/packages/shader-lab-react/src/renderer/media-texture.ts +0 -66
  193. package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +0 -389
  194. package/packages/shader-lab-react/src/renderer/pass-node.ts +0 -209
  195. package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +0 -133
  196. package/packages/shader-lab-react/src/renderer/pattern-pass.ts +0 -552
  197. package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +0 -369
  198. package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +0 -277
  199. package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +0 -87
  200. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +0 -31
  201. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +0 -36
  202. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +0 -36
  203. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +0 -13
  204. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +0 -96
  205. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +0 -24
  206. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +0 -79
  207. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +0 -89
  208. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +0 -56
  209. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +0 -32
  210. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +0 -60
  211. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +0 -15
  212. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +0 -24
  213. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +0 -9
  214. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +0 -10
  215. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +0 -11
  216. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +0 -7
  217. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +0 -12
  218. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +0 -16
  219. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +0 -10
  220. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +0 -18
  221. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +0 -12
  222. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +0 -10
  223. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +0 -48
  224. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +0 -15
  225. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +0 -15
  226. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +0 -6
  227. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +0 -6
  228. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +0 -27
  229. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +0 -6
  230. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +0 -7
  231. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +0 -7
  232. package/packages/shader-lab-react/src/renderer/text-pass.ts +0 -176
  233. package/packages/shader-lab-react/src/runtime-clock.ts +0 -42
  234. package/packages/shader-lab-react/src/runtime-frame.ts +0 -29
  235. package/packages/shader-lab-react/src/shader-lab-composition.tsx +0 -163
  236. package/packages/shader-lab-react/src/timeline.ts +0 -283
  237. package/packages/shader-lab-react/src/types/editor.ts +0 -5
  238. package/packages/shader-lab-react/src/types.ts +0 -141
  239. package/packages/shader-lab-react/tsconfig.build.json +0 -8
  240. package/packages/shader-lab-react/tsconfig.json +0 -21
  241. package/postcss.config.mjs +0 -5
  242. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
  243. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +0 -1412
  244. package/public/assets/patterns/bars/1.svg +0 -3
  245. package/public/assets/patterns/bars/2.svg +0 -3
  246. package/public/assets/patterns/bars/3.svg +0 -3
  247. package/public/assets/patterns/bars/4.svg +0 -3
  248. package/public/assets/patterns/bars/5.svg +0 -3
  249. package/public/assets/patterns/bars/6.svg +0 -3
  250. package/public/assets/patterns/candles/1.svg +0 -3
  251. package/public/assets/patterns/candles/2.svg +0 -3
  252. package/public/assets/patterns/candles/3.svg +0 -3
  253. package/public/assets/patterns/candles/4.svg +0 -3
  254. package/public/assets/patterns/shapes/1.svg +0 -3
  255. package/public/assets/patterns/shapes/2.svg +0 -3
  256. package/public/assets/patterns/shapes/3.svg +0 -3
  257. package/public/assets/patterns/shapes/4.svg +0 -4
  258. package/public/assets/patterns/shapes/5.svg +0 -3
  259. package/public/assets/patterns/shapes/6.svg +0 -4
  260. package/public/fonts/geist/Geist-Mono.woff2 +0 -0
  261. package/public/textures/blue-noise.png +0 -0
  262. package/public/textures/crt-mask.png +0 -0
  263. package/src/app/design/page.tsx +0 -398
  264. package/src/app/favicon.ico +0 -0
  265. package/src/app/globals.css +0 -280
  266. package/src/app/layout.tsx +0 -89
  267. package/src/app/page.tsx +0 -20
  268. package/src/app/robots.ts +0 -13
  269. package/src/app/sitemap.ts +0 -13
  270. package/src/components/editor/editor-canvas-viewport.tsx +0 -116
  271. package/src/components/editor/editor-export-dialog.tsx +0 -1177
  272. package/src/components/editor/editor-timeline-overlay.tsx +0 -983
  273. package/src/components/editor/editor-topbar.tsx +0 -287
  274. package/src/components/editor/layer-sidebar.tsx +0 -738
  275. package/src/components/editor/properties-sidebar-content.tsx +0 -574
  276. package/src/components/editor/properties-sidebar-fields.tsx +0 -389
  277. package/src/components/editor/properties-sidebar-utils.ts +0 -178
  278. package/src/components/editor/properties-sidebar.tsx +0 -421
  279. package/src/components/ui/button/index.tsx +0 -57
  280. package/src/components/ui/color-picker/index.tsx +0 -358
  281. package/src/components/ui/glass-panel/index.tsx +0 -45
  282. package/src/components/ui/icon-button/index.tsx +0 -46
  283. package/src/components/ui/select/index.tsx +0 -136
  284. package/src/components/ui/slider/index.tsx +0 -192
  285. package/src/components/ui/toggle/index.tsx +0 -34
  286. package/src/components/ui/typography/index.tsx +0 -61
  287. package/src/components/ui/xy-pad/index.tsx +0 -160
  288. package/src/features/editor/components/editor-export-dialog.module.css +0 -271
  289. package/src/hooks/use-editor-renderer.ts +0 -182
  290. package/src/lib/app.ts +0 -6
  291. package/src/lib/cn.ts +0 -7
  292. package/src/lib/easings.ts +0 -240
  293. package/src/lib/editor/config/layer-registry.ts +0 -2434
  294. package/src/lib/editor/custom-shader/shared.ts +0 -28
  295. package/src/lib/editor/export.ts +0 -420
  296. package/src/lib/editor/history.ts +0 -71
  297. package/src/lib/editor/layers.ts +0 -76
  298. package/src/lib/editor/parameter-schema.ts +0 -75
  299. package/src/lib/editor/project-file.ts +0 -145
  300. package/src/lib/editor/shader-export-snippet.ts +0 -37
  301. package/src/lib/editor/shader-export.ts +0 -315
  302. package/src/lib/editor/timeline/evaluate.ts +0 -252
  303. package/src/lib/editor/view-transform.ts +0 -58
  304. package/src/lib/fonts.ts +0 -28
  305. package/src/renderer/ascii-atlas.ts +0 -83
  306. package/src/renderer/ascii-pass.ts +0 -416
  307. package/src/renderer/blend-modes.ts +0 -229
  308. package/src/renderer/contracts.ts +0 -161
  309. package/src/renderer/create-webgpu-renderer.ts +0 -48
  310. package/src/renderer/crt-pass.ts +0 -1040
  311. package/src/renderer/custom-shader-pass.ts +0 -117
  312. package/src/renderer/custom-shader-runtime.ts +0 -309
  313. package/src/renderer/dither-textures.ts +0 -99
  314. package/src/renderer/dithering-pass.ts +0 -322
  315. package/src/renderer/gradient-pass.ts +0 -520
  316. package/src/renderer/halftone-pass.ts +0 -932
  317. package/src/renderer/ink-pass.ts +0 -683
  318. package/src/renderer/live-pass.ts +0 -194
  319. package/src/renderer/media-pass.ts +0 -187
  320. package/src/renderer/media-texture.ts +0 -66
  321. package/src/renderer/particle-grid-pass.ts +0 -389
  322. package/src/renderer/pass-node-factory.ts +0 -33
  323. package/src/renderer/pass-node.ts +0 -209
  324. package/src/renderer/pattern-atlas.ts +0 -97
  325. package/src/renderer/pattern-pass.ts +0 -552
  326. package/src/renderer/pipeline-manager.ts +0 -343
  327. package/src/renderer/pixel-sorting-pass.ts +0 -277
  328. package/src/renderer/project-clock.ts +0 -57
  329. package/src/renderer/shaders/tsl/color/tonemapping.ts +0 -86
  330. package/src/renderer/shaders/tsl/cosine-palette.ts +0 -8
  331. package/src/renderer/shaders/tsl/noise/common.ts +0 -30
  332. package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +0 -35
  333. package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +0 -35
  334. package/src/renderer/shaders/tsl/noise/fbm.ts +0 -12
  335. package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +0 -97
  336. package/src/renderer/shaders/tsl/noise/ridge-noise.ts +0 -23
  337. package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +0 -78
  338. package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +0 -88
  339. package/src/renderer/shaders/tsl/noise/turbulence.ts +0 -55
  340. package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +0 -31
  341. package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +0 -59
  342. package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +0 -14
  343. package/src/renderer/shaders/tsl/patterns/bloom.ts +0 -10
  344. package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +0 -23
  345. package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +0 -8
  346. package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +0 -10
  347. package/src/renderer/shaders/tsl/utils/atan2.ts +0 -8
  348. package/src/renderer/shaders/tsl/utils/complex-conj.ts +0 -8
  349. package/src/renderer/shaders/tsl/utils/complex-cos.ts +0 -9
  350. package/src/renderer/shaders/tsl/utils/complex-div.ts +0 -10
  351. package/src/renderer/shaders/tsl/utils/complex-log.ts +0 -6
  352. package/src/renderer/shaders/tsl/utils/complex-mobius.ts +0 -11
  353. package/src/renderer/shaders/tsl/utils/complex-mul.ts +0 -8
  354. package/src/renderer/shaders/tsl/utils/complex-pow.ts +0 -15
  355. package/src/renderer/shaders/tsl/utils/complex-sin.ts +0 -9
  356. package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +0 -17
  357. package/src/renderer/shaders/tsl/utils/complex-tan.ts +0 -11
  358. package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +0 -9
  359. package/src/renderer/shaders/tsl/utils/hyperbolic.ts +0 -19
  360. package/src/renderer/shaders/tsl/utils/index.ts +0 -47
  361. package/src/renderer/shaders/tsl/utils/rotate.ts +0 -14
  362. package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +0 -14
  363. package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +0 -5
  364. package/src/renderer/shaders/tsl/utils/sd-diamond.ts +0 -5
  365. package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +0 -26
  366. package/src/renderer/shaders/tsl/utils/sd-sphere.ts +0 -5
  367. package/src/renderer/shaders/tsl/utils/smax.ts +0 -7
  368. package/src/renderer/shaders/tsl/utils/smin.ts +0 -6
  369. package/src/renderer/text-pass.ts +0 -176
  370. package/src/store/asset-store.ts +0 -193
  371. package/src/store/editor-store.ts +0 -223
  372. package/src/store/history-store.ts +0 -172
  373. package/src/store/index.ts +0 -31
  374. package/src/store/layer-store.ts +0 -675
  375. package/src/store/timeline-store.ts +0 -572
  376. package/src/types/assets.d.ts +0 -6
  377. package/src/types/css.d.ts +0 -21
  378. package/src/types/editor.ts +0 -357
  379. package/src/types/react.d.ts +0 -15
  380. package/src/types/three-tsl.d.ts +0 -146
  381. package/src/types/three-webgpu.d.ts +0 -51
  382. package/tsconfig.json +0 -49
  383. /package/{packages/shader-lab-react/assets → assets}/patterns/bars/1.svg +0 -0
  384. /package/{packages/shader-lab-react/assets → assets}/patterns/bars/2.svg +0 -0
  385. /package/{packages/shader-lab-react/assets → assets}/patterns/bars/3.svg +0 -0
  386. /package/{packages/shader-lab-react/assets → assets}/patterns/bars/4.svg +0 -0
  387. /package/{packages/shader-lab-react/assets → assets}/patterns/bars/5.svg +0 -0
  388. /package/{packages/shader-lab-react/assets → assets}/patterns/bars/6.svg +0 -0
  389. /package/{packages/shader-lab-react/assets → assets}/patterns/candles/1.svg +0 -0
  390. /package/{packages/shader-lab-react/assets → assets}/patterns/candles/2.svg +0 -0
  391. /package/{packages/shader-lab-react/assets → assets}/patterns/candles/3.svg +0 -0
  392. /package/{packages/shader-lab-react/assets → assets}/patterns/candles/4.svg +0 -0
  393. /package/{packages/shader-lab-react/assets → assets}/patterns/shapes/1.svg +0 -0
  394. /package/{packages/shader-lab-react/assets → assets}/patterns/shapes/2.svg +0 -0
  395. /package/{packages/shader-lab-react/assets → assets}/patterns/shapes/3.svg +0 -0
  396. /package/{packages/shader-lab-react/assets → assets}/patterns/shapes/4.svg +0 -0
  397. /package/{packages/shader-lab-react/assets → assets}/patterns/shapes/5.svg +0 -0
  398. /package/{packages/shader-lab-react/assets → assets}/patterns/shapes/6.svg +0 -0
  399. /package/{packages/shader-lab-react/assets → assets}/textures/blue-noise.png +0 -0
@@ -1,1177 +0,0 @@
1
- "use client"
2
-
3
- import {
4
- CopyIcon,
5
- FileArrowDownIcon,
6
- FolderIcon,
7
- UploadSimpleIcon,
8
- X,
9
- } from "@phosphor-icons/react"
10
- import { AnimatePresence, motion, useReducedMotion } from "motion/react"
11
- import {
12
- type ChangeEvent,
13
- type DragEvent,
14
- type ReactNode,
15
- useCallback,
16
- useEffect,
17
- useMemo,
18
- useRef,
19
- useState,
20
- } from "react"
21
- import { createPortal } from "react-dom"
22
- import { Button } from "@/components/ui/button"
23
- import { GlassPanel } from "@/components/ui/glass-panel"
24
- import { IconButton } from "@/components/ui/icon-button"
25
- import { Typography } from "@/components/ui/typography"
26
- import { cn } from "@/lib/cn"
27
- import {
28
- ASPECT_PRESET_LABELS,
29
- type ExportAspectPreset,
30
- type ExportQualityPreset,
31
- exportStillImage,
32
- exportVideo,
33
- getAspectRatioForPreset,
34
- getDimensionsForPreset,
35
- getSupportedVideoMimeType,
36
- type VideoExportFormat,
37
- } from "@/lib/editor/export"
38
- import {
39
- applyLabProjectFile,
40
- buildLabProjectFile,
41
- parseLabProjectFile,
42
- } from "@/lib/editor/project-file"
43
- import {
44
- buildShaderExportConfig,
45
- validateShaderExportSupport,
46
- } from "@/lib/editor/shader-export"
47
- import { generateShaderExportSnippet } from "@/lib/editor/shader-export-snippet"
48
- import {
49
- useAssetStore,
50
- useEditorStore,
51
- useLayerStore,
52
- useTimelineStore,
53
- } from "@/store"
54
-
55
- type ExportTab = "image" | "project" | "shader" | "video"
56
-
57
- const QUALITY_LABELS: Record<ExportQualityPreset, string> = {
58
- draft: "Draft",
59
- high: "High",
60
- standard: "Standard",
61
- ultra: "Ultra",
62
- }
63
-
64
- const ASPECT_PRESETS: ExportAspectPreset[] = [
65
- "original",
66
- "1:1",
67
- "4:5",
68
- "16:9",
69
- "9:16",
70
- ]
71
- const QUALITY_PRESETS: ExportQualityPreset[] = [
72
- "draft",
73
- "standard",
74
- "high",
75
- "ultra",
76
- ]
77
- const VIDEO_FPS_PRESETS = [24, 30, 60] as const
78
-
79
- interface EditorExportDialogProps {
80
- open: boolean
81
- onOpenChange: (open: boolean) => void
82
- }
83
-
84
- export function EditorExportDialog({
85
- open,
86
- onOpenChange,
87
- }: EditorExportDialogProps) {
88
- const reduceMotion = useReducedMotion() ?? false
89
- const compositionSize = useEditorStore((state) => state.canvasSize)
90
- const assets = useAssetStore((state) => state.assets)
91
- const layers = useLayerStore((state) => state.layers)
92
- const timelineDuration = useTimelineStore((state) => state.duration)
93
- const timelineLoop = useTimelineStore((state) => state.loop)
94
- const timelineTracks = useTimelineStore((state) => state.tracks)
95
- const [activeTab, setActiveTab] = useState<ExportTab>("image")
96
- const [mounted, setMounted] = useState(false)
97
- const [isDraggingImport, setIsDraggingImport] = useState(false)
98
- const [isWorking, setIsWorking] = useState(false)
99
- const [statusMessage, setStatusMessage] = useState<string | null>(null)
100
- const [errorMessage, setErrorMessage] = useState<string | null>(null)
101
- const [contentHeight, setContentHeight] = useState<number | null>(null)
102
- const [imageAspect, setImageAspect] = useState<ExportAspectPreset>("original")
103
- const [imageQuality, setImageQuality] =
104
- useState<ExportQualityPreset>("standard")
105
- const [imageSize, setImageSize] = useState(() =>
106
- getDimensionsForPreset(
107
- useEditorStore.getState().canvasSize,
108
- "original",
109
- "standard"
110
- )
111
- )
112
- const [videoAspect, setVideoAspect] = useState<ExportAspectPreset>("original")
113
- const [videoQuality, setVideoQuality] =
114
- useState<ExportQualityPreset>("standard")
115
- const [videoSize, setVideoSize] = useState(() =>
116
- getDimensionsForPreset(
117
- useEditorStore.getState().canvasSize,
118
- "original",
119
- "standard"
120
- )
121
- )
122
- const [videoDuration, setVideoDuration] = useState(6)
123
- const [videoFps, setVideoFps] = useState(30)
124
- const [videoFormat, setVideoFormat] = useState<VideoExportFormat>("webm")
125
- const [isCopyingShader, setIsCopyingShader] = useState(false)
126
- const importInputRef = useRef<HTMLInputElement | null>(null)
127
- const measureRef = useRef<HTMLDivElement | null>(null)
128
-
129
- const mp4Supported = Boolean(getSupportedVideoMimeType("mp4"))
130
- const webmSupported = Boolean(getSupportedVideoMimeType("webm"))
131
- const shaderExportIssues = useMemo(
132
- () => validateShaderExportSupport(layers, assets),
133
- [assets, layers]
134
- )
135
- const shaderSnippet = useMemo(() => {
136
- if (shaderExportIssues.length > 0) {
137
- return null
138
- }
139
-
140
- return generateShaderExportSnippet(
141
- buildShaderExportConfig({
142
- assets,
143
- composition: compositionSize,
144
- layers,
145
- timeline: {
146
- duration: timelineDuration,
147
- loop: timelineLoop,
148
- tracks: timelineTracks,
149
- },
150
- })
151
- )
152
- }, [
153
- assets,
154
- compositionSize,
155
- layers,
156
- shaderExportIssues,
157
- timelineDuration,
158
- timelineLoop,
159
- timelineTracks,
160
- ])
161
-
162
- useEffect(() => {
163
- setMounted(true)
164
- }, [])
165
-
166
- useEffect(() => {
167
- const node = measureRef.current
168
-
169
- if (!node) {
170
- return
171
- }
172
-
173
- const updateHeight = () => {
174
- setContentHeight(Math.ceil(node.getBoundingClientRect().height))
175
- }
176
-
177
- updateHeight()
178
-
179
- const observer = new ResizeObserver(() => {
180
- updateHeight()
181
- })
182
-
183
- observer.observe(node)
184
-
185
- return () => {
186
- observer.disconnect()
187
- }
188
- }, [])
189
-
190
- useEffect(() => {
191
- setImageSize(
192
- getDimensionsForPreset(compositionSize, imageAspect, imageQuality)
193
- )
194
- }, [compositionSize, imageAspect, imageQuality])
195
-
196
- useEffect(() => {
197
- setVideoSize(
198
- getDimensionsForPreset(compositionSize, videoAspect, videoQuality)
199
- )
200
- }, [compositionSize, videoAspect, videoQuality])
201
-
202
- useEffect(() => {
203
- if (!open) {
204
- return
205
- }
206
-
207
- const handleKeyDown = (event: KeyboardEvent) => {
208
- if (event.key === "Escape") {
209
- onOpenChange(false)
210
- }
211
- }
212
-
213
- window.addEventListener("keydown", handleKeyDown)
214
-
215
- return () => {
216
- window.removeEventListener("keydown", handleKeyDown)
217
- }
218
- }, [onOpenChange, open])
219
-
220
- const clearFeedback = useCallback(() => {
221
- setErrorMessage(null)
222
- setStatusMessage(null)
223
- }, [])
224
-
225
- const setNextTab = useCallback(
226
- (nextTab: ExportTab) => {
227
- if (nextTab === activeTab) {
228
- return
229
- }
230
-
231
- clearFeedback()
232
- setActiveTab(nextTab)
233
- },
234
- [activeTab, clearFeedback]
235
- )
236
-
237
- function updateImageWidth(nextWidth: number) {
238
- const width = Math.max(1, Math.round(nextWidth))
239
- const ratio = getAspectRatioForPreset(compositionSize, imageAspect)
240
-
241
- setImageSize({
242
- height: Math.max(1, Math.round(width / ratio)),
243
- width,
244
- })
245
- }
246
-
247
- function updateImageHeight(nextHeight: number) {
248
- const height = Math.max(1, Math.round(nextHeight))
249
- const ratio = getAspectRatioForPreset(compositionSize, imageAspect)
250
-
251
- setImageSize({
252
- height,
253
- width: Math.max(1, Math.round(height * ratio)),
254
- })
255
- }
256
-
257
- function updateVideoWidth(nextWidth: number) {
258
- const width = Math.max(1, Math.round(nextWidth))
259
- const ratio = getAspectRatioForPreset(compositionSize, videoAspect)
260
-
261
- setVideoSize({
262
- height: Math.max(1, Math.round(width / ratio)),
263
- width,
264
- })
265
- }
266
-
267
- function updateVideoHeight(nextHeight: number) {
268
- const height = Math.max(1, Math.round(nextHeight))
269
- const ratio = getAspectRatioForPreset(compositionSize, videoAspect)
270
-
271
- setVideoSize({
272
- height,
273
- width: Math.max(1, Math.round(height * ratio)),
274
- })
275
- }
276
-
277
- async function handleImageExport() {
278
- clearFeedback()
279
- setIsWorking(true)
280
-
281
- try {
282
- const currentTime = useTimelineStore.getState().currentTime
283
- const blob = await exportStillImage(buildRenderProjectState(), {
284
- aspectPreset: imageAspect,
285
- qualityPreset: imageQuality,
286
- time: currentTime,
287
- width: imageSize.width,
288
- height: imageSize.height,
289
- })
290
-
291
- downloadBlob(blob, buildDownloadName("png"))
292
- setStatusMessage(
293
- `PNG exported at ${imageSize.width}×${imageSize.height}.`
294
- )
295
- } catch (error) {
296
- setErrorMessage(
297
- error instanceof Error ? error.message : "Image export failed."
298
- )
299
- } finally {
300
- setIsWorking(false)
301
- }
302
- }
303
-
304
- async function handleVideoExport() {
305
- clearFeedback()
306
- setIsWorking(true)
307
-
308
- try {
309
- const currentTime = useTimelineStore.getState().currentTime
310
- const blob = await exportVideo(buildRenderProjectState(), {
311
- aspectPreset: videoAspect,
312
- duration: Math.max(0.25, videoDuration),
313
- format: videoFormat,
314
- fps: Math.max(1, videoFps),
315
- qualityPreset: videoQuality,
316
- startTime: currentTime,
317
- width: videoSize.width,
318
- height: videoSize.height,
319
- })
320
-
321
- downloadBlob(blob, buildDownloadName(videoFormat))
322
- setStatusMessage(
323
- `${videoFormat.toUpperCase()} exported at ${videoSize.width}×${videoSize.height}.`
324
- )
325
- } catch (error) {
326
- setErrorMessage(
327
- error instanceof Error ? error.message : "Video export failed."
328
- )
329
- } finally {
330
- setIsWorking(false)
331
- }
332
- }
333
-
334
- async function handleProjectExport() {
335
- clearFeedback()
336
-
337
- try {
338
- const projectFile = buildLabProjectFile()
339
- const blob = new Blob([JSON.stringify(projectFile, null, 2)], {
340
- type: "application/json",
341
- })
342
-
343
- downloadBlob(blob, buildDownloadName("lab"))
344
- setStatusMessage("Shader Lab project exported.")
345
- } catch (error) {
346
- setErrorMessage(
347
- error instanceof Error ? error.message : "Project export failed."
348
- )
349
- }
350
- }
351
-
352
- async function handleProjectImport(file: File) {
353
- clearFeedback()
354
- setIsWorking(true)
355
-
356
- try {
357
- const input = await file.text()
358
- const projectFile = parseLabProjectFile(input)
359
- const result = applyLabProjectFile(
360
- projectFile,
361
- useAssetStore.getState().assets
362
- )
363
-
364
- setStatusMessage(
365
- result.missingAssetCount > 0
366
- ? `Project imported. ${result.missingAssetCount} media layer(s) need relinking.`
367
- : "Project imported."
368
- )
369
- onOpenChange(false)
370
- } catch (error) {
371
- setErrorMessage(
372
- error instanceof Error ? error.message : "Project import failed."
373
- )
374
- } finally {
375
- setIsWorking(false)
376
- setIsDraggingImport(false)
377
- }
378
- }
379
-
380
- async function handleShaderCopy() {
381
- clearFeedback()
382
-
383
- if (!shaderSnippet) {
384
- setErrorMessage(
385
- shaderExportIssues[0]?.message ??
386
- "Shader export is not available for this project."
387
- )
388
- return
389
- }
390
-
391
- setIsCopyingShader(true)
392
-
393
- try {
394
- await copyToClipboard(shaderSnippet)
395
- setStatusMessage("Shader snippet copied to clipboard.")
396
- } catch (error) {
397
- setErrorMessage(
398
- error instanceof Error
399
- ? error.message
400
- : "Could not copy shader snippet."
401
- )
402
- } finally {
403
- setIsCopyingShader(false)
404
- }
405
- }
406
-
407
- function handleImportChange(event: ChangeEvent<HTMLInputElement>) {
408
- const file = event.target.files?.[0]
409
- event.currentTarget.value = ""
410
-
411
- if (!file) {
412
- return
413
- }
414
-
415
- void handleProjectImport(file)
416
- }
417
-
418
- function handleDrop(event: DragEvent<HTMLLabelElement>) {
419
- event.preventDefault()
420
- setIsDraggingImport(false)
421
-
422
- const file = event.dataTransfer.files?.[0]
423
-
424
- if (!file) {
425
- return
426
- }
427
-
428
- void handleProjectImport(file)
429
- }
430
-
431
- if (!mounted) {
432
- return null
433
- }
434
-
435
- return createPortal(
436
- <AnimatePresence initial={false}>
437
- {open ? (
438
- <div className="fixed inset-0 z-90" role="presentation">
439
- <motion.button
440
- animate={{ opacity: 1 }}
441
- aria-label="Close export dialog"
442
- className="absolute inset-0 w-full border-0 bg-[rgb(4_5_7_/_0.56)]"
443
- exit={{ opacity: 0 }}
444
- initial={{ opacity: 0 }}
445
- onClick={() => onOpenChange(false)}
446
- transition={{
447
- duration: reduceMotion ? 0.12 : 0.18,
448
- ease: "easeOut",
449
- }}
450
- type="button"
451
- />
452
-
453
- <div className="absolute top-[76px] left-1/2 w-[min(560px,calc(100vw-32px))] max-w-[min(560px,calc(100vw-32px))] -translate-x-1/2">
454
- <motion.div
455
- animate={
456
- reduceMotion ? { opacity: 1 } : { opacity: 1, scale: 1, y: 0 }
457
- }
458
- className="w-full"
459
- exit={
460
- reduceMotion
461
- ? { opacity: 0 }
462
- : { opacity: 0, scale: 0.985, y: -10 }
463
- }
464
- initial={
465
- reduceMotion
466
- ? { opacity: 0 }
467
- : { opacity: 0, scale: 0.985, y: 10 }
468
- }
469
- transition={
470
- reduceMotion
471
- ? { duration: 0.12, ease: "easeOut" }
472
- : { duration: 0.22, ease: [0.22, 1, 0.36, 1] }
473
- }
474
- >
475
- <GlassPanel
476
- aria-modal="true"
477
- className="max-h-[calc(100vh-112px)] overflow-hidden p-0"
478
- role="dialog"
479
- variant="panel"
480
- >
481
- <div className="flex items-center justify-between border-b border-[var(--ds-border-divider)] px-4 pt-[14px] pb-3">
482
- <Typography as="h2" className="leading-5" variant="title">
483
- Export
484
- </Typography>
485
- <IconButton
486
- aria-label="Close export dialog"
487
- className="h-7 w-7"
488
- onClick={() => onOpenChange(false)}
489
- variant="default"
490
- >
491
- <X size={18} weight="bold" />
492
- </IconButton>
493
- </div>
494
-
495
- <div className="flex gap-1.5 border-b border-[var(--ds-border-divider)] px-4 py-[10px]">
496
- {(["image", "video", "shader", "project"] as const).map(
497
- (tab) => (
498
- <button
499
- className={cn(
500
- "inline-flex min-h-7 items-center justify-center rounded-[var(--ds-radius-control)] border border-transparent px-[10px] leading-none transition-[background-color,border-color,color] duration-160 ease-[var(--ease-out-cubic)] hover:bg-[var(--ds-color-surface-subtle)] hover:border-[var(--ds-border-subtle)]",
501
- activeTab === tab &&
502
- "bg-[var(--ds-color-surface-active)] border-[var(--ds-border-active)]"
503
- )}
504
- key={tab}
505
- onClick={() => setNextTab(tab)}
506
- type="button"
507
- >
508
- <Typography
509
- as="span"
510
- tone={activeTab === tab ? "primary" : "tertiary"}
511
- variant="label"
512
- >
513
- {tab}
514
- </Typography>
515
- </button>
516
- )
517
- )}
518
- </div>
519
-
520
- <motion.div
521
- animate={
522
- contentHeight === null
523
- ? { height: "auto" }
524
- : { height: contentHeight }
525
- }
526
- className="overflow-hidden px-4 pt-[14px] pb-4"
527
- transition={
528
- reduceMotion
529
- ? { duration: 0.12, ease: "easeOut" }
530
- : { duration: 0.24, ease: [0.22, 1, 0.36, 1] }
531
- }
532
- >
533
- <div
534
- aria-hidden="true"
535
- className="pointer-events-none absolute top-0 left-0 -z-1 w-full invisible"
536
- >
537
- <div className="w-full" ref={measureRef}>
538
- {activeTab === "image" ? (
539
- <ImageTabContent
540
- imageAspect={imageAspect}
541
- imageQuality={imageQuality}
542
- imageSize={imageSize}
543
- isWorking={isWorking}
544
- onExport={handleImageExport}
545
- onImageAspectChange={setImageAspect}
546
- onImageHeightChange={updateImageHeight}
547
- onImageQualityChange={setImageQuality}
548
- onImageWidthChange={updateImageWidth}
549
- />
550
- ) : null}
551
- {activeTab === "video" ? (
552
- <VideoTabContent
553
- isWorking={isWorking}
554
- mp4Supported={mp4Supported}
555
- onExport={handleVideoExport}
556
- onVideoAspectChange={setVideoAspect}
557
- onVideoDurationChange={setVideoDuration}
558
- onVideoFpsChange={setVideoFps}
559
- onVideoFormatChange={setVideoFormat}
560
- onVideoHeightChange={updateVideoHeight}
561
- onVideoQualityChange={setVideoQuality}
562
- onVideoWidthChange={updateVideoWidth}
563
- videoAspect={videoAspect}
564
- videoDuration={videoDuration}
565
- videoFormat={videoFormat}
566
- videoFps={videoFps}
567
- videoQuality={videoQuality}
568
- videoSize={videoSize}
569
- webmSupported={webmSupported}
570
- />
571
- ) : null}
572
- {activeTab === "project" ? (
573
- <ProjectTabContent
574
- importInputRef={importInputRef}
575
- isDraggingImport={isDraggingImport}
576
- isWorking={isWorking}
577
- onDragStateChange={setIsDraggingImport}
578
- onExport={handleProjectExport}
579
- onFileChange={handleImportChange}
580
- onImportBrowse={() => importInputRef.current?.click()}
581
- onImportDrop={handleDrop}
582
- />
583
- ) : null}
584
- {activeTab === "shader" ? (
585
- <ShaderTabContent
586
- isCopying={isCopyingShader}
587
- issues={shaderExportIssues}
588
- onCopy={handleShaderCopy}
589
- snippet={shaderSnippet}
590
- />
591
- ) : null}
592
- </div>
593
- </div>
594
-
595
- <div className="relative">
596
- <AnimatePresence initial={false} mode="wait">
597
- <motion.div
598
- animate={{ opacity: 1 }}
599
- className="w-full"
600
- exit={{ opacity: 0 }}
601
- initial={{ opacity: 0 }}
602
- key={activeTab}
603
- transition={
604
- reduceMotion
605
- ? { duration: 0.12, ease: "easeOut" }
606
- : { duration: 0.2, ease: [0.22, 1, 0.36, 1] }
607
- }
608
- >
609
- {activeTab === "image" ? (
610
- <ImageTabContent
611
- imageAspect={imageAspect}
612
- imageQuality={imageQuality}
613
- imageSize={imageSize}
614
- isWorking={isWorking}
615
- onExport={handleImageExport}
616
- onImageAspectChange={setImageAspect}
617
- onImageHeightChange={updateImageHeight}
618
- onImageQualityChange={setImageQuality}
619
- onImageWidthChange={updateImageWidth}
620
- />
621
- ) : null}
622
- {activeTab === "video" ? (
623
- <VideoTabContent
624
- isWorking={isWorking}
625
- mp4Supported={mp4Supported}
626
- onExport={handleVideoExport}
627
- onVideoAspectChange={setVideoAspect}
628
- onVideoDurationChange={setVideoDuration}
629
- onVideoFpsChange={setVideoFps}
630
- onVideoFormatChange={setVideoFormat}
631
- onVideoHeightChange={updateVideoHeight}
632
- onVideoQualityChange={setVideoQuality}
633
- onVideoWidthChange={updateVideoWidth}
634
- videoAspect={videoAspect}
635
- videoDuration={videoDuration}
636
- videoFormat={videoFormat}
637
- videoFps={videoFps}
638
- videoQuality={videoQuality}
639
- videoSize={videoSize}
640
- webmSupported={webmSupported}
641
- />
642
- ) : null}
643
- {activeTab === "project" ? (
644
- <ProjectTabContent
645
- importInputRef={importInputRef}
646
- isDraggingImport={isDraggingImport}
647
- isWorking={isWorking}
648
- onDragStateChange={setIsDraggingImport}
649
- onExport={handleProjectExport}
650
- onFileChange={handleImportChange}
651
- onImportBrowse={() =>
652
- importInputRef.current?.click()
653
- }
654
- onImportDrop={handleDrop}
655
- />
656
- ) : null}
657
- {activeTab === "shader" ? (
658
- <ShaderTabContent
659
- isCopying={isCopyingShader}
660
- issues={shaderExportIssues}
661
- onCopy={handleShaderCopy}
662
- snippet={shaderSnippet}
663
- />
664
- ) : null}
665
- </motion.div>
666
- </AnimatePresence>
667
- </div>
668
- </motion.div>
669
-
670
- {errorMessage ? (
671
- <Typography
672
- className="mx-4 mb-4 rounded-[var(--ds-radius-control)] border border-[rgb(255_74_74_/_0.18)] bg-[rgb(255_74_74_/_0.08)] px-3 py-[10px] leading-[14px] text-[rgb(255_191_191_/_0.92)]"
673
- variant="caption"
674
- >
675
- {errorMessage}
676
- </Typography>
677
- ) : null}
678
- {statusMessage ? (
679
- <Typography
680
- className="mx-4 mb-4 rounded-[var(--ds-radius-control)] border border-white/9 bg-white/6 px-3 py-[10px] leading-[14px]"
681
- tone="secondary"
682
- variant="caption"
683
- >
684
- {statusMessage}
685
- </Typography>
686
- ) : null}
687
- </GlassPanel>
688
- </motion.div>
689
- </div>
690
- </div>
691
- ) : null}
692
- </AnimatePresence>,
693
- document.body
694
- )
695
- }
696
-
697
- function ImageTabContent({
698
- imageAspect,
699
- imageQuality,
700
- imageSize,
701
- isWorking,
702
- onExport,
703
- onImageAspectChange,
704
- onImageHeightChange,
705
- onImageQualityChange,
706
- onImageWidthChange,
707
- }: {
708
- imageAspect: ExportAspectPreset
709
- imageQuality: ExportQualityPreset
710
- imageSize: { height: number; width: number }
711
- isWorking: boolean
712
- onExport: () => Promise<void>
713
- onImageAspectChange: (preset: ExportAspectPreset) => void
714
- onImageHeightChange: (value: number) => void
715
- onImageQualityChange: (preset: ExportQualityPreset) => void
716
- onImageWidthChange: (value: number) => void
717
- }) {
718
- return (
719
- <section className="flex flex-col gap-[14px]">
720
- <FieldLabel label="Aspect">
721
- <PresetRow>
722
- {ASPECT_PRESETS.map((preset) => (
723
- <PillButton
724
- active={imageAspect === preset}
725
- key={preset}
726
- label={ASPECT_PRESET_LABELS[preset]}
727
- onClick={() => onImageAspectChange(preset)}
728
- />
729
- ))}
730
- </PresetRow>
731
- </FieldLabel>
732
-
733
- <FieldLabel label="Quality">
734
- <PresetRow>
735
- {QUALITY_PRESETS.map((preset) => (
736
- <PillButton
737
- active={imageQuality === preset}
738
- key={preset}
739
- label={QUALITY_LABELS[preset]}
740
- onClick={() => onImageQualityChange(preset)}
741
- />
742
- ))}
743
- </PresetRow>
744
- </FieldLabel>
745
-
746
- <DimensionFields
747
- height={imageSize.height}
748
- onHeightChange={onImageHeightChange}
749
- onWidthChange={onImageWidthChange}
750
- width={imageSize.width}
751
- />
752
-
753
- <Typography className="leading-[14px]" tone="muted" variant="caption">
754
- Uses the current playhead frame.
755
- </Typography>
756
-
757
- <Button disabled={isWorking} onClick={() => void onExport()}>
758
- <FileArrowDownIcon size={16} weight="bold" />
759
- Export PNG
760
- </Button>
761
- </section>
762
- )
763
- }
764
-
765
- function VideoTabContent({
766
- isWorking,
767
- mp4Supported,
768
- onExport,
769
- onVideoAspectChange,
770
- onVideoDurationChange,
771
- onVideoFpsChange,
772
- onVideoFormatChange,
773
- onVideoHeightChange,
774
- onVideoQualityChange,
775
- onVideoWidthChange,
776
- videoAspect,
777
- videoDuration,
778
- videoFormat,
779
- videoFps,
780
- videoQuality,
781
- videoSize,
782
- webmSupported,
783
- }: {
784
- isWorking: boolean
785
- mp4Supported: boolean
786
- onExport: () => Promise<void>
787
- onVideoAspectChange: (preset: ExportAspectPreset) => void
788
- onVideoDurationChange: (value: number) => void
789
- onVideoFpsChange: (value: number) => void
790
- onVideoFormatChange: (format: VideoExportFormat) => void
791
- onVideoHeightChange: (value: number) => void
792
- onVideoQualityChange: (preset: ExportQualityPreset) => void
793
- onVideoWidthChange: (value: number) => void
794
- videoAspect: ExportAspectPreset
795
- videoDuration: number
796
- videoFormat: VideoExportFormat
797
- videoFps: number
798
- videoQuality: ExportQualityPreset
799
- videoSize: { height: number; width: number }
800
- webmSupported: boolean
801
- }) {
802
- return (
803
- <section className="flex flex-col gap-[14px]">
804
- <FieldLabel label="Format">
805
- <PresetRow>
806
- <PillButton
807
- active={videoFormat === "webm"}
808
- disabled={!webmSupported}
809
- label="WebM"
810
- onClick={() => onVideoFormatChange("webm")}
811
- />
812
- <PillButton
813
- active={videoFormat === "mp4"}
814
- disabled={!mp4Supported}
815
- label="MP4"
816
- onClick={() => onVideoFormatChange("mp4")}
817
- />
818
- </PresetRow>
819
- </FieldLabel>
820
-
821
- <FieldLabel label="Aspect">
822
- <PresetRow>
823
- {ASPECT_PRESETS.map((preset) => (
824
- <PillButton
825
- active={videoAspect === preset}
826
- key={preset}
827
- label={ASPECT_PRESET_LABELS[preset]}
828
- onClick={() => onVideoAspectChange(preset)}
829
- />
830
- ))}
831
- </PresetRow>
832
- </FieldLabel>
833
-
834
- <FieldLabel label="Quality">
835
- <PresetRow>
836
- {QUALITY_PRESETS.map((preset) => (
837
- <PillButton
838
- active={videoQuality === preset}
839
- key={preset}
840
- label={QUALITY_LABELS[preset]}
841
- onClick={() => onVideoQualityChange(preset)}
842
- />
843
- ))}
844
- </PresetRow>
845
- </FieldLabel>
846
-
847
- <DimensionFields
848
- height={videoSize.height}
849
- onHeightChange={onVideoHeightChange}
850
- onWidthChange={onVideoWidthChange}
851
- width={videoSize.width}
852
- />
853
-
854
- <div className="grid gap-[10px] min-[900px]:grid-cols-2">
855
- <FieldLabel label="FPS">
856
- <PresetRow>
857
- {VIDEO_FPS_PRESETS.map((fps) => (
858
- <PillButton
859
- active={videoFps === fps}
860
- key={fps}
861
- label={`${fps}`}
862
- onClick={() => onVideoFpsChange(fps)}
863
- />
864
- ))}
865
- </PresetRow>
866
- </FieldLabel>
867
-
868
- <FieldLabel label="Duration">
869
- <NumberInput
870
- min={0.25}
871
- onChange={onVideoDurationChange}
872
- step={0.25}
873
- value={videoDuration}
874
- />
875
- </FieldLabel>
876
- </div>
877
-
878
- <Typography className="leading-[14px]" tone="muted" variant="caption">
879
- Starts from the current playhead position.
880
- </Typography>
881
-
882
- <Button
883
- disabled={isWorking || !getSupportedVideoMimeType(videoFormat)}
884
- onClick={() => void onExport()}
885
- >
886
- <FileArrowDownIcon size={16} weight="bold" />
887
- Export {videoFormat.toUpperCase()}
888
- </Button>
889
- </section>
890
- )
891
- }
892
-
893
- function ProjectTabContent({
894
- importInputRef,
895
- isDraggingImport,
896
- isWorking,
897
- onDragStateChange,
898
- onExport,
899
- onFileChange,
900
- onImportBrowse,
901
- onImportDrop,
902
- }: {
903
- importInputRef: React.RefObject<HTMLInputElement | null>
904
- isDraggingImport: boolean
905
- isWorking: boolean
906
- onDragStateChange: (dragging: boolean) => void
907
- onExport: () => Promise<void>
908
- onFileChange: (event: ChangeEvent<HTMLInputElement>) => void
909
- onImportBrowse: () => void
910
- onImportDrop: (event: DragEvent<HTMLLabelElement>) => void
911
- }) {
912
- return (
913
- <section className="flex flex-col gap-[14px]">
914
- <Button disabled={isWorking} onClick={() => void onExport()}>
915
- <FileArrowDownIcon size={16} weight="bold" />
916
- Export .lab file
917
- </Button>
918
-
919
- <label
920
- className={cn(
921
- "grid items-center gap-3 rounded-[var(--ds-radius-panel)] border border-dashed border-[var(--ds-border-divider)] bg-[var(--ds-color-surface-subtle)] p-[14px] min-[900px]:grid-cols-[auto_1fr_auto]",
922
- isDraggingImport &&
923
- "border-[var(--ds-border-hover)] bg-[var(--ds-color-surface-active)]"
924
- )}
925
- onDragEnter={() => onDragStateChange(true)}
926
- onDragLeave={() => onDragStateChange(false)}
927
- onDragOver={(event) => {
928
- event.preventDefault()
929
-
930
- if (!isDraggingImport) {
931
- onDragStateChange(true)
932
- }
933
- }}
934
- onDrop={onImportDrop}
935
- >
936
- <input
937
- accept=".lab,application/json"
938
- className="hidden"
939
- onChange={onFileChange}
940
- ref={importInputRef}
941
- type="file"
942
- />
943
-
944
- <UploadSimpleIcon size={20} weight="bold" />
945
- <div>
946
- <Typography className="leading-4" variant="label">
947
- Import .lab configuration
948
- </Typography>
949
- <Typography className="mt-1" tone="tertiary" variant="caption">
950
- Drag and drop here. This will replace your current setup.
951
- </Typography>
952
- </div>
953
-
954
- <IconButton
955
- disabled={isWorking}
956
- onClick={(event) => {
957
- event.preventDefault()
958
- onImportBrowse()
959
- }}
960
- variant="active"
961
- >
962
- <FolderIcon size={20} />
963
- </IconButton>
964
- </label>
965
- </section>
966
- )
967
- }
968
-
969
- function ShaderTabContent({
970
- isCopying,
971
- issues,
972
- onCopy,
973
- snippet,
974
- }: {
975
- isCopying: boolean
976
- issues: { layerId?: string; message: string }[]
977
- onCopy: () => Promise<void>
978
- snippet: string | null
979
- }) {
980
- const canCopy = Boolean(snippet) && issues.length === 0
981
-
982
- return (
983
- <section className="flex flex-col gap-[14px]">
984
- <Typography className="leading-[14px]" tone="muted" variant="caption">
985
- Install with{" "}
986
- <code className="rounded-[6px] border border-white/9 bg-white/6 px-[5px] py-px font-[var(--ds-font-mono)] text-[11px]">
987
- bun add @basementstudio/shader-lab three
988
- </code>
989
- , then paste this component into your React app.
990
- </Typography>
991
-
992
- {issues.length > 0 ? (
993
- <div className="flex flex-col gap-2 rounded-[var(--ds-radius-panel)] border border-[rgb(255_74_74_/_0.14)] bg-[rgb(255_74_74_/_0.06)] p-3">
994
- {issues.map((issue) => (
995
- <Typography
996
- key={`${issue.layerId ?? "global"}:${issue.message}`}
997
- variant="caption"
998
- >
999
- {issue.message}
1000
- </Typography>
1001
- ))}
1002
- </div>
1003
- ) : null}
1004
-
1005
- <FieldLabel label="Snippet">
1006
- <pre className="m-0 max-h-[280px] overflow-auto rounded-[var(--ds-radius-panel)] border border-[var(--ds-border-divider)] bg-white/4 p-3 font-[var(--ds-font-mono)] text-[11px] leading-[1.55] whitespace-pre-wrap break-words">
1007
- <code>
1008
- {snippet ?? "// Shader export is blocked for this project."}
1009
- </code>
1010
- </pre>
1011
- </FieldLabel>
1012
-
1013
- <Button disabled={!canCopy || isCopying} onClick={() => void onCopy()}>
1014
- <CopyIcon size={16} weight="bold" />
1015
- {isCopying ? "Copying..." : "Copy snippet"}
1016
- </Button>
1017
- </section>
1018
- )
1019
- }
1020
-
1021
- function FieldLabel({
1022
- children,
1023
- label,
1024
- }: {
1025
- children: ReactNode
1026
- label: string
1027
- }) {
1028
- return (
1029
- <div className="flex flex-col gap-2">
1030
- <Typography className="uppercase" tone="secondary" variant="overline">
1031
- {label}
1032
- </Typography>
1033
- {children}
1034
- </div>
1035
- )
1036
- }
1037
-
1038
- function PresetRow({ children }: { children: ReactNode }) {
1039
- return <div className="flex flex-wrap gap-1.5">{children}</div>
1040
- }
1041
-
1042
- function PillButton({
1043
- active,
1044
- disabled = false,
1045
- label,
1046
- onClick,
1047
- }: {
1048
- active: boolean
1049
- disabled?: boolean
1050
- label: string
1051
- onClick: () => void
1052
- }) {
1053
- return (
1054
- <button
1055
- className={cn(
1056
- "inline-flex min-h-7 items-center justify-center rounded-[var(--ds-radius-control)] border border-[var(--ds-border-divider)] bg-[var(--ds-color-surface-control)] px-[10px] leading-none transition-[background-color,border-color,color] duration-160 ease-[var(--ease-out-cubic)] hover:not-disabled:bg-white/8 hover:not-disabled:border-[var(--ds-border-hover)] disabled:cursor-not-allowed disabled:opacity-42",
1057
- active &&
1058
- "bg-[var(--ds-color-surface-active)] border-[var(--ds-border-active)]"
1059
- )}
1060
- disabled={disabled}
1061
- onClick={onClick}
1062
- type="button"
1063
- >
1064
- <Typography
1065
- as="span"
1066
- tone={active ? "primary" : "secondary"}
1067
- variant="label"
1068
- >
1069
- {label}
1070
- </Typography>
1071
- </button>
1072
- )
1073
- }
1074
-
1075
- function DimensionFields({
1076
- height,
1077
- onHeightChange,
1078
- onWidthChange,
1079
- width,
1080
- }: {
1081
- height: number
1082
- onHeightChange: (value: number) => void
1083
- onWidthChange: (value: number) => void
1084
- width: number
1085
- }) {
1086
- return (
1087
- <div className="grid gap-[10px] min-[900px]:grid-cols-2">
1088
- <FieldLabel label="Width">
1089
- <NumberInput min={1} onChange={onWidthChange} step={1} value={width} />
1090
- </FieldLabel>
1091
- <FieldLabel label="Height">
1092
- <NumberInput
1093
- min={1}
1094
- onChange={onHeightChange}
1095
- step={1}
1096
- value={height}
1097
- />
1098
- </FieldLabel>
1099
- </div>
1100
- )
1101
- }
1102
-
1103
- function NumberInput({
1104
- min,
1105
- onChange,
1106
- step,
1107
- value,
1108
- }: {
1109
- min: number
1110
- onChange: (value: number) => void
1111
- step: number
1112
- value: number
1113
- }) {
1114
- return (
1115
- <input
1116
- className="min-h-9 rounded-[var(--ds-radius-control)] border border-[var(--ds-border-divider)] bg-[var(--ds-color-surface-control)] px-[10px] py-2 font-[var(--ds-font-mono)] text-[12px] leading-4 text-[var(--ds-color-text-primary)]"
1117
- min={min}
1118
- onChange={(event) => {
1119
- const nextValue = Number.parseFloat(event.currentTarget.value)
1120
-
1121
- if (Number.isFinite(nextValue)) {
1122
- onChange(nextValue)
1123
- }
1124
- }}
1125
- step={step}
1126
- type="number"
1127
- value={value}
1128
- />
1129
- )
1130
- }
1131
-
1132
- function buildRenderProjectState() {
1133
- const timelineState = useTimelineStore.getState()
1134
-
1135
- return {
1136
- assets: useAssetStore.getState().assets,
1137
- compositionSize: useEditorStore.getState().canvasSize,
1138
- layers: useLayerStore.getState().layers,
1139
- timeline: {
1140
- currentTime: timelineState.currentTime,
1141
- duration: timelineState.duration,
1142
- isPlaying: timelineState.isPlaying,
1143
- loop: timelineState.loop,
1144
- selectedKeyframeId: timelineState.selectedKeyframeId,
1145
- selectedTrackId: timelineState.selectedTrackId,
1146
- tracks: structuredClone(timelineState.tracks),
1147
- },
1148
- }
1149
- }
1150
-
1151
- function buildDownloadName(extension: string): string {
1152
- const stamp = new Date()
1153
- .toISOString()
1154
- .replaceAll(":", "-")
1155
- .replace(/\..+$/, "")
1156
- return `shader-lab-${stamp}.${extension}`
1157
- }
1158
-
1159
- function downloadBlob(blob: Blob, fileName: string) {
1160
- const url = URL.createObjectURL(blob)
1161
- const anchor = document.createElement("a")
1162
- anchor.href = url
1163
- anchor.download = fileName
1164
- anchor.click()
1165
-
1166
- window.setTimeout(() => {
1167
- URL.revokeObjectURL(url)
1168
- }, 0)
1169
- }
1170
-
1171
- async function copyToClipboard(value: string) {
1172
- if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) {
1173
- throw new Error("Clipboard access is not available in this browser.")
1174
- }
1175
-
1176
- await navigator.clipboard.writeText(value)
1177
- }