@basementstudio/shader-lab 0.1.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 (261) hide show
  1. package/.biome/plugins/README.md +21 -0
  2. package/.biome/plugins/no-anchor-element.grit +12 -0
  3. package/.biome/plugins/no-relative-parent-imports.grit +10 -0
  4. package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  5. package/.changeset/README.md +17 -0
  6. package/.changeset/config.json +11 -0
  7. package/.editorconfig +40 -0
  8. package/.env.example +81 -0
  9. package/.gitattributes +19 -0
  10. package/.github/workflows/canary.yml +80 -0
  11. package/.github/workflows/ci.yml +37 -0
  12. package/.github/workflows/release.yml +56 -0
  13. package/.tldrignore +84 -0
  14. package/.vscode/extensions.json +20 -0
  15. package/.vscode/settings.json +105 -0
  16. package/README.md +119 -0
  17. package/biome.json +249 -0
  18. package/bun.lock +1224 -0
  19. package/next.config.ts +131 -0
  20. package/package.json +73 -0
  21. package/packages/shader-lab-react/CHANGELOG.md +9 -0
  22. package/packages/shader-lab-react/README.md +119 -0
  23. package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
  24. package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
  25. package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
  26. package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
  27. package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
  28. package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
  29. package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
  30. package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
  31. package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
  32. package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
  33. package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
  34. package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
  35. package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
  36. package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
  37. package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
  38. package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
  39. package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
  40. package/packages/shader-lab-react/package.json +36 -0
  41. package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
  42. package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
  43. package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
  44. package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
  45. package/packages/shader-lab-react/src/easings.ts +4 -0
  46. package/packages/shader-lab-react/src/index.ts +35 -0
  47. package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
  48. package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
  49. package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
  50. package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
  51. package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
  52. package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
  53. package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
  54. package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
  55. package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
  56. package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
  57. package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
  58. package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
  59. package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
  60. package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
  61. package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
  62. package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
  63. package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
  64. package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
  65. package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
  66. package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
  67. package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
  68. package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
  69. package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
  70. package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
  71. package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
  72. package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
  73. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
  74. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
  75. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
  76. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
  77. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
  78. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
  79. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
  80. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
  81. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
  82. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
  83. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
  84. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
  85. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
  86. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
  87. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
  88. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
  89. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
  90. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
  91. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
  92. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
  93. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
  94. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
  95. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
  96. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
  97. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
  98. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
  99. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
  100. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
  101. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
  102. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
  103. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
  104. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
  105. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
  106. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
  107. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
  108. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
  109. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  110. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
  111. package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
  112. package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
  113. package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
  114. package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
  115. package/packages/shader-lab-react/src/timeline.ts +283 -0
  116. package/packages/shader-lab-react/src/types/editor.ts +5 -0
  117. package/packages/shader-lab-react/src/types.ts +141 -0
  118. package/packages/shader-lab-react/tsconfig.build.json +8 -0
  119. package/packages/shader-lab-react/tsconfig.json +21 -0
  120. package/postcss.config.mjs +5 -0
  121. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
  122. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
  123. package/public/assets/patterns/bars/1.svg +3 -0
  124. package/public/assets/patterns/bars/2.svg +3 -0
  125. package/public/assets/patterns/bars/3.svg +3 -0
  126. package/public/assets/patterns/bars/4.svg +3 -0
  127. package/public/assets/patterns/bars/5.svg +3 -0
  128. package/public/assets/patterns/bars/6.svg +3 -0
  129. package/public/assets/patterns/candles/1.svg +3 -0
  130. package/public/assets/patterns/candles/2.svg +3 -0
  131. package/public/assets/patterns/candles/3.svg +3 -0
  132. package/public/assets/patterns/candles/4.svg +3 -0
  133. package/public/assets/patterns/shapes/1.svg +3 -0
  134. package/public/assets/patterns/shapes/2.svg +3 -0
  135. package/public/assets/patterns/shapes/3.svg +3 -0
  136. package/public/assets/patterns/shapes/4.svg +4 -0
  137. package/public/assets/patterns/shapes/5.svg +3 -0
  138. package/public/assets/patterns/shapes/6.svg +4 -0
  139. package/public/fonts/geist/Geist-Mono.woff2 +0 -0
  140. package/public/textures/blue-noise.png +0 -0
  141. package/public/textures/crt-mask.png +0 -0
  142. package/src/app/design/page.tsx +398 -0
  143. package/src/app/favicon.ico +0 -0
  144. package/src/app/globals.css +280 -0
  145. package/src/app/layout.tsx +89 -0
  146. package/src/app/page.tsx +20 -0
  147. package/src/app/robots.ts +13 -0
  148. package/src/app/sitemap.ts +13 -0
  149. package/src/components/editor/editor-canvas-viewport.tsx +116 -0
  150. package/src/components/editor/editor-export-dialog.tsx +1177 -0
  151. package/src/components/editor/editor-timeline-overlay.tsx +983 -0
  152. package/src/components/editor/editor-topbar.tsx +287 -0
  153. package/src/components/editor/layer-sidebar.tsx +738 -0
  154. package/src/components/editor/properties-sidebar-content.tsx +574 -0
  155. package/src/components/editor/properties-sidebar-fields.tsx +389 -0
  156. package/src/components/editor/properties-sidebar-utils.ts +178 -0
  157. package/src/components/editor/properties-sidebar.tsx +421 -0
  158. package/src/components/ui/button/index.tsx +57 -0
  159. package/src/components/ui/color-picker/index.tsx +358 -0
  160. package/src/components/ui/glass-panel/index.tsx +45 -0
  161. package/src/components/ui/icon-button/index.tsx +46 -0
  162. package/src/components/ui/select/index.tsx +136 -0
  163. package/src/components/ui/slider/index.tsx +192 -0
  164. package/src/components/ui/toggle/index.tsx +34 -0
  165. package/src/components/ui/typography/index.tsx +61 -0
  166. package/src/components/ui/xy-pad/index.tsx +160 -0
  167. package/src/features/editor/components/editor-export-dialog.module.css +271 -0
  168. package/src/hooks/use-editor-renderer.ts +182 -0
  169. package/src/lib/app.ts +6 -0
  170. package/src/lib/cn.ts +7 -0
  171. package/src/lib/easings.ts +240 -0
  172. package/src/lib/editor/config/layer-registry.ts +2434 -0
  173. package/src/lib/editor/custom-shader/shared.ts +28 -0
  174. package/src/lib/editor/export.ts +420 -0
  175. package/src/lib/editor/history.ts +71 -0
  176. package/src/lib/editor/layers.ts +76 -0
  177. package/src/lib/editor/parameter-schema.ts +75 -0
  178. package/src/lib/editor/project-file.ts +145 -0
  179. package/src/lib/editor/shader-export-snippet.ts +37 -0
  180. package/src/lib/editor/shader-export.ts +315 -0
  181. package/src/lib/editor/timeline/evaluate.ts +252 -0
  182. package/src/lib/editor/view-transform.ts +58 -0
  183. package/src/lib/fonts.ts +28 -0
  184. package/src/renderer/ascii-atlas.ts +83 -0
  185. package/src/renderer/ascii-pass.ts +416 -0
  186. package/src/renderer/blend-modes.ts +229 -0
  187. package/src/renderer/contracts.ts +161 -0
  188. package/src/renderer/create-webgpu-renderer.ts +48 -0
  189. package/src/renderer/crt-pass.ts +1040 -0
  190. package/src/renderer/custom-shader-pass.ts +117 -0
  191. package/src/renderer/custom-shader-runtime.ts +309 -0
  192. package/src/renderer/dither-textures.ts +99 -0
  193. package/src/renderer/dithering-pass.ts +322 -0
  194. package/src/renderer/gradient-pass.ts +520 -0
  195. package/src/renderer/halftone-pass.ts +932 -0
  196. package/src/renderer/ink-pass.ts +683 -0
  197. package/src/renderer/live-pass.ts +194 -0
  198. package/src/renderer/media-pass.ts +187 -0
  199. package/src/renderer/media-texture.ts +66 -0
  200. package/src/renderer/particle-grid-pass.ts +389 -0
  201. package/src/renderer/pass-node-factory.ts +33 -0
  202. package/src/renderer/pass-node.ts +209 -0
  203. package/src/renderer/pattern-atlas.ts +97 -0
  204. package/src/renderer/pattern-pass.ts +552 -0
  205. package/src/renderer/pipeline-manager.ts +343 -0
  206. package/src/renderer/pixel-sorting-pass.ts +277 -0
  207. package/src/renderer/project-clock.ts +57 -0
  208. package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
  209. package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
  210. package/src/renderer/shaders/tsl/noise/common.ts +30 -0
  211. package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
  212. package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
  213. package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
  214. package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
  215. package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
  216. package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
  217. package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
  218. package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
  219. package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
  220. package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
  221. package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
  222. package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
  223. package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
  224. package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
  225. package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
  226. package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
  227. package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
  228. package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
  229. package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
  230. package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
  231. package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
  232. package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
  233. package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
  234. package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
  235. package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
  236. package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
  237. package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
  238. package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
  239. package/src/renderer/shaders/tsl/utils/index.ts +47 -0
  240. package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
  241. package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
  242. package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
  243. package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
  244. package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
  245. package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
  246. package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  247. package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
  248. package/src/renderer/text-pass.ts +176 -0
  249. package/src/store/asset-store.ts +193 -0
  250. package/src/store/editor-store.ts +223 -0
  251. package/src/store/history-store.ts +172 -0
  252. package/src/store/index.ts +31 -0
  253. package/src/store/layer-store.ts +675 -0
  254. package/src/store/timeline-store.ts +572 -0
  255. package/src/types/assets.d.ts +6 -0
  256. package/src/types/css.d.ts +21 -0
  257. package/src/types/editor.ts +357 -0
  258. package/src/types/react.d.ts +15 -0
  259. package/src/types/three-tsl.d.ts +146 -0
  260. package/src/types/three-webgpu.d.ts +51 -0
  261. package/tsconfig.json +49 -0
@@ -0,0 +1,163 @@
1
+ import { type CSSProperties, useEffect, useRef, useState } from "react"
2
+ import { buildRendererFrame, type RendererSize } from "./renderer/contracts"
3
+ import {
4
+ browserSupportsWebGPU,
5
+ createWebGPURenderer,
6
+ } from "./renderer/create-webgpu-renderer"
7
+ import type { ShaderLabConfig } from "./types"
8
+
9
+ export interface ShaderLabCompositionProps {
10
+ className?: string
11
+ config: ShaderLabConfig
12
+ onRuntimeError?: (message: string | null) => void
13
+ style?: CSSProperties
14
+ }
15
+
16
+ export function ShaderLabComposition({
17
+ className,
18
+ config,
19
+ onRuntimeError,
20
+ style,
21
+ }: ShaderLabCompositionProps) {
22
+ const canvasRef = useRef<HTMLCanvasElement | null>(null)
23
+ const frameRef = useRef<number | null>(null)
24
+ const lastTimeRef = useRef<number | null>(null)
25
+ const [runtimeError, setRuntimeError] = useState<string | null>(null)
26
+
27
+ useEffect(() => {
28
+ const canvas = canvasRef.current
29
+
30
+ if (!canvas) {
31
+ return
32
+ }
33
+
34
+ if (!browserSupportsWebGPU()) {
35
+ const message = "WebGPU is not available in this browser."
36
+ setRuntimeError(message)
37
+ onRuntimeError?.(message)
38
+ return
39
+ }
40
+
41
+ let cancelled = false
42
+ let rendererPromise: Promise<
43
+ Awaited<ReturnType<typeof createWebGPURenderer>>
44
+ > | null = null
45
+ let runtimeRenderer: Awaited<
46
+ ReturnType<typeof createWebGPURenderer>
47
+ > | null = null
48
+
49
+ const handleRuntimeError = (message: string | null) => {
50
+ setRuntimeError(message)
51
+ onRuntimeError?.(message)
52
+ }
53
+
54
+ const getViewportSize = (): RendererSize => {
55
+ const bounds = canvas.getBoundingClientRect()
56
+
57
+ return {
58
+ height: Math.max(1, Math.round(bounds.height)),
59
+ width: Math.max(1, Math.round(bounds.width)),
60
+ }
61
+ }
62
+
63
+ const renderFrame = (now: number) => {
64
+ if (cancelled || !runtimeRenderer) {
65
+ return
66
+ }
67
+
68
+ const previousTime = lastTimeRef.current ?? now
69
+ const delta = Math.max(0, (now - previousTime) / 1000)
70
+ lastTimeRef.current = now
71
+
72
+ const devicePixelRatio = window.devicePixelRatio || 1
73
+ const viewportSize = getViewportSize()
74
+
75
+ runtimeRenderer.resize(viewportSize, devicePixelRatio)
76
+ runtimeRenderer.render(
77
+ buildRendererFrame(
78
+ config,
79
+ now / 1000,
80
+ delta,
81
+ devicePixelRatio,
82
+ viewportSize
83
+ )
84
+ )
85
+
86
+ frameRef.current = window.requestAnimationFrame(renderFrame)
87
+ }
88
+
89
+ rendererPromise = createWebGPURenderer(canvas, handleRuntimeError)
90
+
91
+ void rendererPromise
92
+ .then(async (renderer) => {
93
+ if (cancelled) {
94
+ renderer.dispose()
95
+ return
96
+ }
97
+
98
+ runtimeRenderer = renderer
99
+ await renderer.initialize()
100
+ handleRuntimeError(null)
101
+ frameRef.current = window.requestAnimationFrame(renderFrame)
102
+ })
103
+ .catch((error) => {
104
+ handleRuntimeError(
105
+ error instanceof Error
106
+ ? error.message
107
+ : "Failed to initialize the Shader Lab runtime renderer."
108
+ )
109
+ })
110
+
111
+ return () => {
112
+ cancelled = true
113
+ lastTimeRef.current = null
114
+
115
+ if (frameRef.current !== null) {
116
+ window.cancelAnimationFrame(frameRef.current)
117
+ frameRef.current = null
118
+ }
119
+
120
+ void rendererPromise?.then((renderer) => {
121
+ renderer.dispose()
122
+ })
123
+ }
124
+ }, [config, onRuntimeError])
125
+
126
+ return (
127
+ <div
128
+ className={className}
129
+ data-shader-lab-composition="true"
130
+ style={{
131
+ aspectRatio: `${config.composition.width} / ${config.composition.height}`,
132
+ overflow: "hidden",
133
+ position: "relative",
134
+ width: "100%",
135
+ ...style,
136
+ }}
137
+ >
138
+ <canvas
139
+ ref={canvasRef}
140
+ style={{ display: "block", height: "100%", width: "100%" }}
141
+ />
142
+ {runtimeError ? (
143
+ <div
144
+ style={{
145
+ alignItems: "center",
146
+ background: "rgba(10, 13, 16, 0.82)",
147
+ color: "rgba(255,255,255,0.92)",
148
+ display: "flex",
149
+ fontFamily: "monospace",
150
+ fontSize: 12,
151
+ inset: 0,
152
+ justifyContent: "center",
153
+ padding: 16,
154
+ position: "absolute",
155
+ textAlign: "center",
156
+ }}
157
+ >
158
+ {runtimeError}
159
+ </div>
160
+ ) : null}
161
+ </div>
162
+ )
163
+ }
@@ -0,0 +1,283 @@
1
+ import { easings } from "./easings"
2
+ import type {
3
+ ShaderLabAnimatedPropertyBinding,
4
+ ShaderLabLayerConfig,
5
+ ShaderLabParameterValue,
6
+ ShaderLabTimelineInterpolation,
7
+ ShaderLabTimelineTrack,
8
+ } from "./types"
9
+
10
+ export interface EvaluatedLayerState {
11
+ layerId: string
12
+ params: Record<string, ShaderLabParameterValue>
13
+ properties: Partial<Record<"hue" | "opacity" | "saturation" | "visible", boolean | number>>
14
+ }
15
+
16
+ type NumericTuple = [number, number] | [number, number, number]
17
+
18
+ function cloneParameterValue(value: ShaderLabParameterValue): ShaderLabParameterValue {
19
+ if (Array.isArray(value)) {
20
+ return [...value] as ShaderLabParameterValue
21
+ }
22
+
23
+ return value
24
+ }
25
+
26
+ function isNumericTuple(value: ShaderLabParameterValue): value is NumericTuple {
27
+ return (
28
+ Array.isArray(value) &&
29
+ (value.length === 2 || value.length === 3) &&
30
+ value.every((entry) => typeof entry === "number")
31
+ )
32
+ }
33
+
34
+ function parseHexColor(value: string): [number, number, number] | null {
35
+ const normalized = value.trim().toLowerCase()
36
+
37
+ if (!normalized.startsWith("#")) {
38
+ return null
39
+ }
40
+
41
+ const hex = normalized.slice(1)
42
+
43
+ if (hex.length === 6 || hex.length === 8) {
44
+ const r = Number.parseInt(hex.slice(0, 2), 16)
45
+ const g = Number.parseInt(hex.slice(2, 4), 16)
46
+ const b = Number.parseInt(hex.slice(4, 6), 16)
47
+
48
+ if ([r, g, b].some(Number.isNaN)) {
49
+ return null
50
+ }
51
+
52
+ return [r, g, b]
53
+ }
54
+
55
+ return null
56
+ }
57
+
58
+ function toHex(channel: number): string {
59
+ return Math.round(Math.min(255, Math.max(0, channel)))
60
+ .toString(16)
61
+ .padStart(2, "0")
62
+ }
63
+
64
+ function rgbToHex(r: number, g: number, b: number): string {
65
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`
66
+ }
67
+
68
+ function lerp(a: number, b: number, progress: number): number {
69
+ return a + (b - a) * progress
70
+ }
71
+
72
+ function resolveProgress(progress: number, interpolation: ShaderLabTimelineInterpolation): number {
73
+ if (interpolation === "step") {
74
+ return 0
75
+ }
76
+
77
+ if (interpolation === "smooth") {
78
+ return easings.easeInOutCubic(progress)
79
+ }
80
+
81
+ return progress
82
+ }
83
+
84
+ function interpolateValue(
85
+ from: ShaderLabParameterValue,
86
+ to: ShaderLabParameterValue,
87
+ progress: number,
88
+ interpolation: ShaderLabTimelineInterpolation,
89
+ ): ShaderLabParameterValue {
90
+ if (interpolation === "step") {
91
+ return cloneParameterValue(from)
92
+ }
93
+
94
+ const eased = resolveProgress(progress, interpolation)
95
+
96
+ if (typeof from === "number" && typeof to === "number") {
97
+ return lerp(from, to, eased)
98
+ }
99
+
100
+ if (typeof from === "boolean" && typeof to === "boolean") {
101
+ return eased < 0.5 ? from : to
102
+ }
103
+
104
+ if (typeof from === "string" && typeof to === "string") {
105
+ const leftColor = parseHexColor(from)
106
+ const rightColor = parseHexColor(to)
107
+
108
+ if (!(leftColor && rightColor)) {
109
+ return eased < 0.5 ? from : to
110
+ }
111
+
112
+ return rgbToHex(
113
+ lerp(leftColor[0], rightColor[0], eased),
114
+ lerp(leftColor[1], rightColor[1], eased),
115
+ lerp(leftColor[2], rightColor[2], eased),
116
+ )
117
+ }
118
+
119
+ if (isNumericTuple(from) && isNumericTuple(to) && from.length === to.length) {
120
+ return from.map((entry, index) => lerp(entry, to[index] ?? entry, eased)) as ShaderLabParameterValue
121
+ }
122
+
123
+ return eased < 0.5 ? cloneParameterValue(from) : cloneParameterValue(to)
124
+ }
125
+
126
+ function evaluateTrackAtTime(
127
+ track: ShaderLabTimelineTrack,
128
+ time: number,
129
+ ): ShaderLabParameterValue | null {
130
+ if (!track.enabled || track.keyframes.length === 0) {
131
+ return null
132
+ }
133
+
134
+ if (track.keyframes.length === 1) {
135
+ const onlyKeyframe = track.keyframes[0]
136
+ return onlyKeyframe ? cloneParameterValue(onlyKeyframe.value) : null
137
+ }
138
+
139
+ const firstKeyframe = track.keyframes[0]
140
+ const lastKeyframe = track.keyframes[track.keyframes.length - 1]
141
+
142
+ if (!(firstKeyframe && lastKeyframe)) {
143
+ return null
144
+ }
145
+
146
+ if (time <= firstKeyframe.time) {
147
+ return cloneParameterValue(firstKeyframe.value)
148
+ }
149
+
150
+ if (time >= lastKeyframe.time) {
151
+ return cloneParameterValue(lastKeyframe.value)
152
+ }
153
+
154
+ for (let index = 1; index < track.keyframes.length; index += 1) {
155
+ const nextKeyframe = track.keyframes[index]
156
+ const previousKeyframe = track.keyframes[index - 1]
157
+
158
+ if (!(nextKeyframe && previousKeyframe) || time > nextKeyframe.time) {
159
+ continue
160
+ }
161
+
162
+ const span = Math.max(nextKeyframe.time - previousKeyframe.time, 1e-6)
163
+ const progress = Math.max(0, Math.min(1, (time - previousKeyframe.time) / span))
164
+
165
+ return interpolateValue(
166
+ previousKeyframe.value,
167
+ nextKeyframe.value,
168
+ progress,
169
+ track.interpolation,
170
+ )
171
+ }
172
+
173
+ return cloneParameterValue(lastKeyframe.value)
174
+ }
175
+
176
+ function applyBindingOverride(
177
+ state: EvaluatedLayerState,
178
+ binding: ShaderLabAnimatedPropertyBinding,
179
+ value: ShaderLabParameterValue,
180
+ ): void {
181
+ if (binding.kind === "param") {
182
+ state.params[binding.key] = cloneParameterValue(value)
183
+ return
184
+ }
185
+
186
+ if (binding.property === "visible" && typeof value === "boolean") {
187
+ state.properties.visible = value
188
+ return
189
+ }
190
+
191
+ if (
192
+ (binding.property === "opacity" ||
193
+ binding.property === "hue" ||
194
+ binding.property === "saturation") &&
195
+ typeof value === "number"
196
+ ) {
197
+ state.properties[binding.property] = value
198
+ }
199
+ }
200
+
201
+ export function evaluateTimelineForLayers(
202
+ layers: ShaderLabLayerConfig[],
203
+ tracks: ShaderLabTimelineTrack[],
204
+ time: number,
205
+ ): EvaluatedLayerState[] {
206
+ if (tracks.length === 0) {
207
+ return []
208
+ }
209
+
210
+ const layerStates = new Map<string, EvaluatedLayerState>()
211
+ const layerIds = new Set(layers.map((layer) => layer.id))
212
+
213
+ for (const track of tracks) {
214
+ if (!layerIds.has(track.layerId)) {
215
+ continue
216
+ }
217
+
218
+ const value = evaluateTrackAtTime(track, time)
219
+
220
+ if (value === null) {
221
+ continue
222
+ }
223
+
224
+ let state = layerStates.get(track.layerId)
225
+
226
+ if (!state) {
227
+ state = {
228
+ layerId: track.layerId,
229
+ params: {},
230
+ properties: {},
231
+ }
232
+ layerStates.set(track.layerId, state)
233
+ }
234
+
235
+ applyBindingOverride(state, track.binding, value)
236
+ }
237
+
238
+ return [...layerStates.values()]
239
+ }
240
+
241
+ export function resolveEvaluatedLayers(
242
+ layers: ShaderLabLayerConfig[],
243
+ tracks: ShaderLabTimelineTrack[],
244
+ time: number,
245
+ ): ShaderLabLayerConfig[] {
246
+ const evaluatedStates = evaluateTimelineForLayers(layers, tracks, time)
247
+ const evaluatedById = new Map(evaluatedStates.map((state) => [state.layerId, state]))
248
+
249
+ return layers.map((layer) => {
250
+ const evaluated = evaluatedById.get(layer.id)
251
+
252
+ if (!evaluated) {
253
+ return {
254
+ ...layer,
255
+ params: { ...layer.params },
256
+ }
257
+ }
258
+
259
+ return {
260
+ ...layer,
261
+ hue:
262
+ typeof evaluated.properties.hue === "number"
263
+ ? evaluated.properties.hue
264
+ : layer.hue,
265
+ opacity:
266
+ typeof evaluated.properties.opacity === "number"
267
+ ? evaluated.properties.opacity
268
+ : layer.opacity,
269
+ params: {
270
+ ...layer.params,
271
+ ...evaluated.params,
272
+ },
273
+ saturation:
274
+ typeof evaluated.properties.saturation === "number"
275
+ ? evaluated.properties.saturation
276
+ : layer.saturation,
277
+ visible:
278
+ typeof evaluated.properties.visible === "boolean"
279
+ ? evaluated.properties.visible
280
+ : layer.visible,
281
+ }
282
+ })
283
+ }
@@ -0,0 +1,5 @@
1
+ import type { ShaderLabParameterValue } from "../types"
2
+
3
+ export type LayerParameterValues = Record<string, ShaderLabParameterValue>
4
+ export type LayerCompositeMode = "filter" | "mask"
5
+
@@ -0,0 +1,141 @@
1
+ export type ShaderLabParameterValue =
2
+ | number
3
+ | string
4
+ | boolean
5
+ | [number, number]
6
+ | [number, number, number]
7
+
8
+ export type ShaderLabLayerKind = "effect" | "source"
9
+
10
+ export type ShaderLabSourceLayerType =
11
+ | "custom-shader"
12
+ | "gradient"
13
+ | "image"
14
+ | "live"
15
+ | "text"
16
+ | "video"
17
+
18
+ export type ShaderLabEffectLayerType =
19
+ | "ascii"
20
+ | "crt"
21
+ | "dithering"
22
+ | "halftone"
23
+ | "ink"
24
+ | "particle-grid"
25
+ | "pattern"
26
+ | "pixel-sorting"
27
+
28
+ export type ShaderLabLayerType =
29
+ | ShaderLabEffectLayerType
30
+ | ShaderLabSourceLayerType
31
+
32
+ export type ShaderLabBlendMode =
33
+ | "normal"
34
+ | "multiply"
35
+ | "screen"
36
+ | "overlay"
37
+ | "darken"
38
+ | "lighten"
39
+ | "color-dodge"
40
+ | "color-burn"
41
+ | "hard-light"
42
+ | "soft-light"
43
+ | "difference"
44
+ | "exclusion"
45
+ | "hue"
46
+ | "saturation"
47
+ | "color"
48
+ | "luminosity"
49
+
50
+ export type ShaderLabCompositeMode = "filter" | "mask"
51
+
52
+ export type ShaderLabAssetSource =
53
+ | {
54
+ fileName?: string
55
+ kind: "image"
56
+ src: string
57
+ }
58
+ | {
59
+ fileName?: string
60
+ kind: "video"
61
+ src: string
62
+ }
63
+
64
+ export type ShaderLabInlineSketchSource = {
65
+ code: string
66
+ entryExport: string
67
+ fileName?: string
68
+ mode: "inline"
69
+ }
70
+
71
+ export type ShaderLabModuleSketchSource = {
72
+ entryExport?: string
73
+ mode: "module"
74
+ sketch: unknown
75
+ }
76
+
77
+ export type ShaderLabSketchSource =
78
+ | ShaderLabInlineSketchSource
79
+ | ShaderLabModuleSketchSource
80
+
81
+ export type ShaderLabTimelineInterpolation = "linear" | "smooth" | "step"
82
+
83
+ export type ShaderLabAnimatedPropertyBinding =
84
+ | {
85
+ kind: "layer"
86
+ label: string
87
+ property: "hue" | "opacity" | "saturation" | "visible"
88
+ valueType: "boolean" | "number"
89
+ }
90
+ | {
91
+ key: string
92
+ kind: "param"
93
+ label: string
94
+ valueType: "boolean" | "color" | "number" | "select" | "vec2" | "vec3"
95
+ }
96
+
97
+ export interface ShaderLabTimelineKeyframe {
98
+ id: string
99
+ time: number
100
+ value: ShaderLabParameterValue
101
+ }
102
+
103
+ export interface ShaderLabTimelineTrack {
104
+ binding: ShaderLabAnimatedPropertyBinding
105
+ enabled: boolean
106
+ id: string
107
+ interpolation: ShaderLabTimelineInterpolation
108
+ keyframes: ShaderLabTimelineKeyframe[]
109
+ layerId: string
110
+ }
111
+
112
+ export interface ShaderLabTimelineConfig {
113
+ duration: number
114
+ loop: boolean
115
+ tracks: ShaderLabTimelineTrack[]
116
+ }
117
+
118
+ export interface ShaderLabLayerConfig {
119
+ asset?: ShaderLabAssetSource
120
+ blendMode: ShaderLabBlendMode
121
+ compositeMode: ShaderLabCompositeMode
122
+ hue: number
123
+ id: string
124
+ kind: ShaderLabLayerKind
125
+ name: string
126
+ opacity: number
127
+ params: Record<string, ShaderLabParameterValue>
128
+ saturation: number
129
+ sketch?: ShaderLabSketchSource
130
+ type: ShaderLabLayerType
131
+ visible: boolean
132
+ }
133
+
134
+ export interface ShaderLabConfig {
135
+ composition: {
136
+ height: number
137
+ width: number
138
+ }
139
+ layers: ShaderLabLayerConfig[]
140
+ timeline: ShaderLabTimelineConfig
141
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "emitDeclarationOnly": false,
5
+ "noEmit": false,
6
+ "tsBuildInfoFile": "./tsconfig.build.tsbuildinfo"
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": ".",
5
+ "composite": true,
6
+ "declaration": true,
7
+ "emitDeclarationOnly": true,
8
+ "jsx": "react-jsx",
9
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
10
+ "module": "ESNext",
11
+ "moduleResolution": "bundler",
12
+ "noEmit": true,
13
+ "outDir": "./dist",
14
+ "paths": {
15
+ "@/*": ["./src/*"]
16
+ },
17
+ "plugins": [],
18
+ "tsBuildInfoFile": "./tsconfig.tsbuildinfo"
19
+ },
20
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.d.ts"]
21
+ }
@@ -0,0 +1,5 @@
1
+ export default {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ }